NAME
bk log − print file revision history and/or
metadata
SYNOPSIS
bk log [−dDfS] [−cd]
[−Cr] [−L[url]]
[−rr]
[file ... | −]
DESCRIPTION
The bk log command is used to extract revision
history and or metadata from a file or set of files. The
default behavior is to print a summary of each revision to
each of the specified files. There are options to restrict
the set of revisions to print, a very commonly used one is
−r+, which restricts the set to the most recent
revision.
With no options bk log output defaults to giving information on all revisions of all files in the present directory that are under BitKeeper control. Output is given as follows: the name of the file and range of revisions is followed by a detailed account of each revision. Revision number, revision date and time, user who made that revision, what the relative path from root of repository is to that file, the comments that go with that revision, and documents if the file has been renamed.
OPTIONS
−n A numeric argument that limits the
number of deltas printed per file.
−−begin=script The dspec v2 language
(see that section below and the examples) is awk like and
has optional begin/end sections. This option allows you to
specify the body of a begin section (if the file also has a
begin section then both are run; order is undefined). It is
typically used with an on disk dspec in a dspec file that
has been written in a way to do one thing by default and
another if a variable is set to non-zero.
−cdate Cut-off dates. See range
specifications (below) or bk help range for details.
−Crev Make the range be all revs that are
the same cset as rev.
−dspec Override the default output format
(see below).
−−dspecf=file Like −d
but read the dspec from a file.
−D Do not skip files in the
BitKeeper/deleted directory.
−f Print the changes in forward (oldest to newest)
order. The default is backward.
−L[url] Show all deltas unique to this
repository relative to the (outgoing) parent or url
if one was specified. May not be combined with
−c or −r.
−−lattice Restrict the deltas to those on
the lattice between the two range endpoints. Unlike a range,
the lower bound is included in the output.
−−longest Restrict the deltas to those on
the longest line between the two range endpoints. Unlike a
range, the lower bound is included in the output.
−n Add a newline to each printed record.
−rrev Specify a revision, or part of a
range. (Or key or changeset revision. See bk help
terms under “rev argument.”)
−S
−−standalone Use with -L in a nested
component when you want the component to act like a
standalone repository.
RANGE
SPECIFICATIONS
−r+ prints the most recent delta
−r1.3..1.6 prints all deltas that are in
1.6’s history but are not in 1.3’s history.
−c2006/07..2006 prints all deltas from July 1 2006
to Dec 31 2006
−c2006..2006 prints all deltas from Jan 1 2006 to
Dec 31 2006
−c−1d.. prints all deltas made in the last
24 hours; similarly for s, m, h,
d, M, and Y for seconds,minutes, hours,
days, months, and years.
DEFAULT
OUTPUT FORMAT
The bk changes, and bk log commands have a
default output format which may be modified on a per user or
per repository basis. The default formats are named as
follows:
dspec-changes Specifies the format for bk changes [-v]
output (without -vv).
dspec-changes-vv Specifies the format for bk changes -vv
output (verbose with diffs).
dspec-log Specifies the format for bk log output.
There are other dspec-* files, if you want some fun look at dspec-*-json, if you understand those you understand that dspecs are a little programming language. The shipped files live in ‘bk bin‘/dspec-*
These files can live in multiple places, this is the search order for "dspec-changes", the first one found is used:
<repo>/BitKeeper/etc/dspec-changes
<product>/BitKeeper/etc/dspec-changes
(BitKeeper/Nested only)
‘bk dotbk‘/dspec-changes
/etc/BitKeeper/etc/dspec-changes
‘bk bin‘/dspec-changes
MODIFYING THE
OUTPUT FORMAT
There are many different pieces of information in a
BitKeeper file and virtually all of them can be extracted
using a non-default output format.
To extract specific information, a “dspec” (data specification) string is provided and where there are keywords surrounded by colons the keywords are expanded much like printf(3). This can lead to many useful one liners; want to see which file is the most busy in a repository?
bk -U log -r+ -nd’:DS: :GFILE’ | sort -nr | head
If you are familiar with awk(1) the processing here is similar. In awk, each line is a record, here each delta is a record. For each delta, you can provide a dspec which says what it is you want to print from that delta. Looking at the dspec-* files in the installation directory is recommended, there are $begin/$end sections that mimic awk.
The dspecs can contain the following set of escaped characters which will be replaced with the specified string:
|
\b |
Backspace | |
|
\f |
Form feed | |
|
\n |
Newline | |
|
\r |
Carriage return | |
|
\t |
Tab | |
|
\123 |
3 digit octal value (pad with leading zeros) | |
|
\<c> |
c for any other character "c" |
In almost all cases, a trailing newline is not provided by any of the keywords and one should be provided as needed. The newline can be added in line or you can specify the −n option which will add a newline after each delta is processed.
Multi-line keywords are normally printed as is, but you can format each line separately using a $each() loop (see ITERATIVE OUTPUT below).
CONDITIONAL
OUTPUT
The dspec can produce output conditionally. The
following prints the revisions of foo.c that were
made by joe:
bk log -nd’$if(:USER:=joe){:REV:}’ foo.c
CONDITIONAL
STATEMENTS
$if(expr){anything}
prints anything if expr is true. If expr is a keyword, i.e., :MERGE: , then the keyword is examined and returns true if it has a value. anything can contain keywords, i.e., :REV: .
$unless(expr){anything}
prints anything unless expr is true.
$if(expr){stuff if true}$else{stuff if false}
both $if and $unless can have an optional $else clause that prints if the preceding clause was not printed.
CONDITIONAL
OPERATORS
strings lhs=rhs true if lhs
is identical to rhs.
lhs!=rhs
true if lhs is different than rhs.
str=˜glob true if str matches
glob.
str=˜/regexp/ true if str
matches the regular expression; /regexp/i does a
case-insensitive match and /glob/g does a glob match.
Note: spaces immediately before and after the operator
are ignored. To match against such a leading or trailing
space, escape it.
numbers lhs −eq rhs equality;
lhs −ge rhs
equal or greater than;
lhs −gt rhs greater than;
lhs −le rhs equal or less
than;
lhs −lt rhs less than.
Note: spaces are required on both sides of the
operator.
COMPOUND
EXPRESSIONS
As of BitKeeper release 4.1, dspecs support logical
AND (A && B), logical OR (A || B), and parenthesized
subexpressions ((A && B) || (C && D)). Note:
spaces immediately before and after the operator are
ignored. To match against such a leading or trailing space,
escape it.
ITERATIVE
OUTPUT
Some keywords, such as comments or tags, may be multi-line.
To print a prefix in front of each of these lines, the dspec
is:
bk log -d’$each(:C:){C (:C:)\n}’ foo.c
This iteration works for all keywords, but normally is only used for keywords that can contain multiple lines like ALL_TAGS , C , TAGGED , and TAGS .
The $first() builtin can be used to print only the first line of one of these multi-line keywords. Some development efforts tend to use the first line of commit comments as a summary of the change; the rest are more details. The dspec that ships with BitKeeper for bk changes uses this feature to implement bk changes --short. To print the first line of each comment in a changeset:
bk changes -nd’$first(:C:)’
VARIABLES
When the bk log command is run over multiple
revisions of one or more files, the data specification
("dspec") is evaluated once for each revision, and
it sometimes is useful to use dspec variables, denoted as
’$0’, ’$1’, up to ’$9’,
to remember values across revisions or files. A variable is
assigned a value with:
${0=<dspec>}
where <dspec> is a recursively evaluated data specification. Once assigned, a variable retains its value for the remainder of the command (or until reassigned). Variables start out as empty strings (evaluate to zero in numeric context), and when re-assigned the old value is discarded.
DSPEC VERSION
2
This little printf like language outgrew itself, having
if/then/else and other control flow was too much on just one
line. We came up with a version 2 language that allows you
to have dspecs that look like a programming language. All of
the output from BitKeeper commands is generated via the v2
language in external files (see dspec-* in the bin
directory). All of the commands that take dspecs also can
take a file containing the dspec; that’s how the
BitKeeper commands work, they just load those files.
To switch into the new language a dspec has to begin like so:
# dspec-v2
The language is awk like, comments start with "#" and go to the end of the line. Whitespace is ignored unless it is inside double quotes, then it is reproduced exactly. Keywords are expanded inside of double quotes as are the escaped characters mentioned above. Control flow and begin/end blocks (all the $something) are unquoted.
Like awk, there are optional begin/end blocks:
$begin {
"[\n"
}
$end {
$if($0 -eq 1) {
"\n"
}
"]\n"
}
Those are from dspec-changes-json-v which is the dspec that is used for bk changes -v --json which (surprise!) produces json format output. If you want to understand this language that is a good file to read.
Other than variables, which are expanded inside double quotes, none of the $word ($if/$else/$first/$each/etc) expand inside double quotes. This can be surprising, you might think the bk changes --short dspec is:
":MD5SUM: $first(:C:)\n"
but that does not work, this will:
":MD5SUM: " $first(:C:) "\n"
In many cases, all keywords will expand in begin/end blocks, those are run in the context of the first/last delta selected. If a range is selected that has no matching deltas (think bk changes -L for example) then none of the keywords will be expanded, the current system expands keywords if and only if there is a valid delta in play.
A simple dspec-v2 is the bk log output format:
":DPN:
:REV:\n"
" :D_: :T::TZ: :USERHOST: +:LI: -:LD:\n"
$each(:C:) { # comments
" (:C:)\n"
}
"\n"
$if (:TAGS:) {
$each(:TAGS:) {
" TAG: (:TAGS:)\n"
}
"\n"
}
That says:
line 1: print the path name and the revision
line 2: indent the date/time/timezone user@host +lines added
-lines deleted
line 3-5: indent each line of the comments
line 6: blank line
line 7-11: if there are tags, print them with another blank
line
KEYWORDS
Some keywords are per repository and are marked with
R in the T column below. Some keywords are per
file and are marked with F in the T column
below; other keywords are per delta and are marked with
D. Some keywords only make sense if they are
changesets, they are marked with C. Those keywords
that can be either on a changeset or on a file are marked
with D.
Name T What is printed ==========================================================
|
:AGE: |
D |
D’s age, i.e., seven hours, two weeks, etc. |
||
|
:ALL_TAGS: |
C |
The tag[s] if this changeset has or ever had them |
||
|
:ATTACHED_ID: |
F |
package id to be used for new deltas |
||
|
:BAM: |
F |
true if the file is managed as a BAM file |
||
|
:C: |
D |
D’s comments |
||
|
:CHANGESET: |
F |
true if ChangeSet file, false for user files |
||
|
:COMMENTS: |
D |
comments portion of :PRS: |
||
|
:COMPRESSION: |
D |
D’s compression (gzip|none) |
||
|
:CSETKEY: |
D |
delta key if D is at a changeset boundary |
||
|
:CSETREV: |
D |
revision of first cset boundary after D |
||
|
:D: |
D |
D’s date as YYYY/MM/DD |
||
|
:D_: |
D |
D’s date as YYYY-MM-DD |
||
|
:DANGLING: |
D |
D’s rev if & only if D is a dangling delta |
||
|
:DI: |
D |
D’s includes/excludes as +I,I/-X,X (serials) |
||
|
:DIFFS: |
D |
D’s changes in the form of traditional diffs |
||
|
:DIFFS_U: |
D |
D’s changes in the form of unified diffs |
||
|
:DIFFS_UP: |
D |
D’s changes in the form of unified/procedural diffs |
||
|
:DL: |
D |
lines inserted/deleted/unchanged in D |
||
|
:DM: |
D |
month part of D’s date (Jan..Dec) |
||
|
:DOMAIN: |
D |
the domain part of the hostname of D |
||
|
:DP: |
D |
the serial number of the parent of D |
||
|
:DPN: |
D |
the pathname of g.file as of D |
||
|
:DS: |
D |
the serial number of D |
||
|
:DSUM: |
D |
D’s 16 bit unsigned checksum (%05u) |
||
|
:DSUMMARY: |
D |
first line of :PRS: |
||
|
:DT: |
D |
D’s type: (D|R|T) meaning (Data|Removed|Tag) |
||
|
:Dd: |
D |
day part of D’s date as DD |
||
|
:Dm: |
D |
month part of D’s date as MM |
||
|
:Dn: |
D |
serial numbers of D’s includes, if any |
||
|
:Dt: |
D |
D’s data as :DT::REV::D::T::USER::DS::DP: |
||
|
:Dx: |
D |
serial numbers of D’s excludes, if any |
||
|
:Dy: |
D |
year part of D’s date as YY or YYYY |
||
|
:ENC: |
F |
current encoding scheme (ascii|uuencode|BAM) |
||
|
:EVEN: |
D |
True on every other delta processed |
||
|
:F: |
F |
basename of the BitKeeper file |
||
|
:FLAGS: |
D |
file flags as of D in words (HASH, YEAR4...) |
||
|
:FSUM: |
F |
16 bit unsigned checksum of the s.file |
||
|
:FUDGE: |
D |
time stamp fudge used make time monotonic |
||
|
:FULLHOST: |
D |
same as :HOST: or :HOST:/:REALHOST: |
||
|
:FULLUSER: |
D |
same as :USER: or :USER:/:REALUSER: |
||
|
:G: |
F |
basename of the gfile |
||
|
:GB: |
D |
file as of version D |
||
|
:GCA: |
D |
find the graph GCA for D’s parents |
||
|
:GCA2: |
D |
find the set GCA for D’s parents |
||
|
:GFILE: |
F |
pathname of the gfile |
||
|
:GREV: |
F |
for a file with conflicts, the GCA of the unmerged tips |
||
|
:HASHCOUNT: |
D |
count of key/value pairs in D if & only if hash file |
||
|
:HOST: |
D |
hostname where D was made; could be BK_HOST |
||
|
:HTML_AGE: |
D |
age in a form suitable for web pages |
||
|
:HTML_C: |
D |
comments in a form suitable for web pages |
||
|
:ID: |
D |
the package id in effect when the delta was made |
||
|
:IMPORTER: |
D |
name of the importer of D, if D was an emailed patch |
||
|
:KEY: |
D |
BitKeeper key of D |
||
|
:KID: |
D |
D’s direct kid in D’s branch |
||
|
:KIDS: |
D |
all of D’s direct kids in the graph |
||
|
:L: |
D |
the second field in D’s rev (R.L.B.S) |
||
|
:LD: |
D |
lines deleted in D (%u) |
||
|
:LI: |
D |
lines inserted in D (%u) |
||
|
:LREV: |
F |
for a file with conflicts, the LOCAL unmerged tip |
||
|
:LU: |
D |
lines unchanged in D (%u) |
||
|
:Ld: |
D |
lines deleted in D (%05u) |
||
|
:Li: |
D |
lines inserted in D (%05u) |
||
|
:Lu: |
D |
lines unchanged in D (%05u) |
||
|
:MD5KEY: |
D |
crypto based BitKeeper key of D |
||
|
:MERGE: |
D |
D’s rev if & only if D has a merge parent |
||
|
:MGP: |
D |
D’s merge parent’s serial number |
||
|
:MODE: |
D |
D’s file modes as an octal (777) |
||
|
:MPARENT: |
D |
D’s merge parent’s revision |
||
|
:N: |
D |
Number of deltas, use instead of DS, DS may have gaps. |
||
|
:NEXT: |
D |
next entry after D in delta table |
||
|
:ODD: |
D |
True on every other delta processed |
||
|
:PACKAGE_ID: |
R |
per repository unique id (like bk id) |
||
|
:PARENT: |
D |
D’s parent’s revision |
||
|
:PREV: |
D |
previous entry before D in delta table |
||
|
:PRODUCT_ID: |
R |
per repository unique id (like bk -P id) |
||
|
:PRS: |
D |
old-style bk prs default output |
||
|
:R: |
D |
the first field in D’s rev (R.L.B.S) |
||
|
:RANDOM: |
F |
random bits part of ROOTKEY |
||
|
:REALHOST: |
D |
real host where D was made, regardless of BK_HOST |
||
|
:REALUSER: |
D |
real programmer who made D, not assumed identity |
||
|
:RENAME: |
D |
D’s path if different from parent’s path |
||
|
:REPO_ID: |
R |
per repository unique id (like bk id -r) |
||
|
:REPOTYPE: |
R |
repotype: product|component|standalone |
||
|
:REV: |
D |
D’s revision number |
||
|
:RI: |
D |
revision numbers of D’s includes/excludes |
||
|
:ROOTKEY: |
F |
key of the 1.0 delta, file’s internal name |
||
|
:RREV: |
F |
for a file with conflicts, the REMOTE unmerged tip |
||
|
:RWXMODE: |
D |
D’s file modes as ascii (-rwxrwxrwx) |
||
|
:Rn: |
D |
revision numbers of D’s includes |
||
|
:Rx: |
D |
revision numbers of D’s excludes |
||
|
:S: |
D |
last field in D’s rev (R.L.B.S) |
||
|
:SFILE: |
F |
pathname of s.file |
||
|
:SIBLINGS: |
D |
rev sibling’s pointer in D |
||
|
:SPN: |
D |
pathname of s.file as of D |
||
|
:SYMLINK: |
D |
value of D’s symlink target |
||
|
:T: |
D |
time of D as HH:MM:SS |
||
|
:TAGGED: |
C |
The tag[s] iff currently on this changeset |
||
|
:TAGS: |
C |
Tag[s] if on, or were on, this changeset (with notes) |
||
|
:TIME_T: |
D |
D’s date as GMT time_t, TZ and Fudge adjusted |
||
|
:TIP: |
D |
D’s rev if D is at the tip (TOT) |
||
|
:TZ: |
D |
offset from GMT as +/-HH:MM |
||
|
:Th: |
D |
hour part of D’s date as HH |
||
|
:Tm: |
D |
minute part of D’s date as MM |
||
|
:Ts: |
D |
seconds part of D’s date as SS |
||
|
:USER: |
D |
programmer who made D; could be assumed identity |
||
|
:UTC: |
D |
D’s time stamp as YYYYMMDDHHMMSS in GMT |
||
|
:UTC-FUDGE: |
D |
like UTC but without the date fudge |
||
|
:VERSION: |
F |
file format version |
NOTES
Keywords marked with type C or D above can optionally be
restricted to a given revision as follows:
|
:KW|1.0: |
# as of a literal revision | ||
|
:KW|PARENT: |
# as of the current rev’s parent | ||
|
:KW|MPARENT: |
# as of the current rev’s merge parent (if any) |
For example:
bk log -hnd’$if(:DPN|PARENT: != :DPN:){rename :DPN|PARENT: => :DPN:}’
Things that can go in the second part include a specific revision (eg 1.0), PARENT, and MPARENT.
EXAMPLES
To extract a key, which is a stable name for a
changeset:
bk changes -r1.234 -nd:MD5KEY:
To count up all the deltas in all user files:
bk -U log -nd:REV: | wc -l
List users who have made changes in a subdirectory in the last month:
bk -rSubDir log -c-1M.. -nd:USER: | sort -u
To see the number of merge changesets:
bk changes -nd’$if(:MERGE:){merge}’ | wc -l
To see the number of merges in user files:
bk -U log -nd’$if(:MERGE:){merge}’ | wc -l
Count up lines inserted by a particular user in all non-merge deltas:
bk -U log
-nd’$if(:USER: = joe && !:MERGE:){:LI:}’
|
awk ’{ lines += $1 } END { print lines }’
The default dspec for the bk changes command is below. This is an example of a dspec-v2 style of writing dspecs, much more pleasant for complex reports.
# dspec-v2
# The default dspec used by ’bk changes’ and ’bk changes -v’
":INDENT:"
# 2 spaces + 2 in component
":DPN:@:REV:"
${1=:USERHOST:} # 1 == user || user@host
$if (:CHANGESET: && !:COMPONENT_V:) {
", :Dy:-:Dm:-:Dd: :T::TZ:, $1"
${0=$1} # 0 == user of last cset
} $else {
$if($0 != $1){ # print user if different
|
", $1" |
}
}
$unless (:CHANGESET:) {
" +:LI: -:LD:" # lines added/deleted
}
"\n"
# bk changes
--short is an alias for
# bk changes --begin=’${9=1}’
# so $9 is the switch that decides between short/long form
$if ($9 -eq 0) { # comments (long form)
$each(:C:) {
":INDENT: (:C:)\n"
}
} $else { # comments (short form)
":INDENT: "
$first(:C:)
"\n"
}
$each(:TAGS:) {
" TAG: (:TAGS:)\n"
}
$if ($9 -eq 0) { # skip merge info for --short
$if (:MERGE:) { # merge shows both parents
":INDENT: MERGE: :MPARENT:\n"
}
}
"\n"
SEE ALSO
bk-range
CATEGORY
File