BK-LOG(1)
bk log(none) β BitKeeper User's Manual π¦β¨
okay bestie welcome to the bk log page, this one is a whole journey, grab a strawberry drink π and let's go
β¨ NAME β¨
bk log β print file revision history and/or metadata π
Fine. Pure. Iconic. It prints your file's whole little life story and/or the metadata that goes with it.
πΈ SYNOPSIS πΈ
bk log [-dDfS] [-cd] [-Cr] [-L[url]] [-rr] [file ... | -]
π DESCRIPTION π
So the bk log command is the one you use to extract revision history and or metadata from a file or a whole set of files π«Ά. The default behavior β like, if you just vibe and type it β is to print a summary of each revision to each of the specified files. There are options to restrict the set of revisions it prints, and the one everybody and their bestie uses is -r+, which restricts the set to the most recent revision only. lowkey a classic.
With no options, bk log output defaults to giving you information on all revisions of all files in the present directory that are under BitKeeper control π. The output goes like this: the name of the file and the range of revisions, followed by a detailed account of each revision. That account has the revision number, the revision date and time, the 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. fr fr it's giving full detective dossier π
π OPTIONS π
-nβ a numeric argument that limits the number of deltas printed per file. yes it's-nhere and yes it comes back later, this page has layers π--begin=scriptβ the dspec v2 language (see that section below and the examples) is awk like and has optional begin/end sections. This option lets you specify the body of a begin section (and if the file also has a begin section then both are run; the order is undefined π). It's typically used with an on disk dspec in a dspec file that's been written in a way to do one thing by default and another if a variable is set to non-zero. sneaky, love that for you.-cdateβ cut-off dates π . See range specifications (below) orbk help rangefor details.-Crevβ makes the range be all revs that are the same cset as rev.-dspecβ override the default output format (see below).--dspecf=fileβ like-dbut read the dspec from a file.-Dβ do not skip files in theBitKeeper/deleteddirectory. (default is to skip them, so this is your "show me the ghosts" flag π»)-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-cor-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. (yep, second-nof the page, the manpage said "reuse the letter, it ate" π)-rrevβ specify a revision, or part of a range. (Or key or changeset revision. Seebk help termsunder "rev argument.")-S,--standaloneβ use with-Lin 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 in1.6's history but are not in1.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 fors,m,h,d,M, andYfor 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 forbk changes [-v]output (without-vv).dspec-changes-vvβ specifies the format forbk changes -vvoutput (verbose with diffs).dspec-logβ specifies the format forbk logoutput.
There are other dspec-* files, and 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, and 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're 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$ifand$unlesscan have an optional$elseclause 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/idoes a case-insensitive match and/glob/gdoes 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 come from dspec-changes-json-v, aka the dspec behind bk changes -v --json, which (plot twist) makes json output. If you actually want to learn this little language, that file is a genuinely good read. π
Other than variables β which do expand inside double quotes β none of the $word things ($if/$else/$first/$each/etc) expand inside double quotes. This is the kind of thing that jumpscares you. You might think the bk changes --short dspec is:
":MD5SUM: $first(:C:)\n"
...but nope, that does not work. This does:
":MD5SUM: " $first(:C:) "\n"
In lots of cases, all keywords expand inside begin/end blocks, and those run in the context of the first/last delta selected. If you select a range with no matching deltas (think bk changes -L, for example), then none of the keywords expand β the current system expands keywords if and only if thereβs 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, and+lines added/-lines deleted - lines 3β5: indent each line of the comments
- line 6: blank line
- lines 7β11: if there are tags, print them with another blank line
π¦ KEYWORDS π¦
Okay so some keywords are per repository and get an R in the T column below. Some are per file and get an F. Other keywords are per delta and get a D. Some only make sense on changesets, and those get a C. Keywords that can be either on a changeset or on a file are marked D too.
Hereβs the whole keyword menu, with the T tag up front:
:AGE:β D β how old D is, i.e.seven hours,two weeks, etc.:ALL_TAGS:β C β the tag[s] if this changeset has them or ever had them:ATTACHED_ID:β F β package id to be used for new deltas:BAM:β F βtrueif the file is managed as a BAM file:C:β D β Dβs comments:CHANGESET:β F βtrueif ChangeSet file,falsefor 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 asYYYY/MM/DD:D_:β D β Dβs date asYYYY-MM-DD:DANGLING:β D β Dβs rev if and 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 asDD:Dm:β D β month part of Dβs date asMM: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 asYYorYYYY:ENC:β F β current encoding scheme (ascii|uuencode|BAM):EVEN:β D βTrueon 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 and only if hash file:HOST:β D β hostname where D was made; could beBK_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 and 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 ofDS,DSmay have gaps:NEXT:β D β next entry after D in delta table:ODD:β D βTrueon every other delta processed:PACKAGE_ID:β R β per repository unique id (likebk id):PARENT:β D β Dβs parentβs revision:PREV:β D β previous entry before D in delta table:PRODUCT_ID:β R β per repository unique id (likebk -P id):PRS:β D β old-stylebk prsdefault output:R:β D β the first field in Dβs rev (R.L.B.S):RANDOM:β F β random bits part ofROOTKEY:REALHOST:β D β real host where D was made, regardless ofBK_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 (likebk 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 the1.0delta, 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 asHH: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 GMTtime_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 asHH:Tm:β D β minute part of Dβs date asMM:Ts:β D β seconds part of Dβs date asSS:USER:β D β programmer who made D; could be assumed identity:UTC:β D β Dβs time stamp asYYYYMMDDHHMMSSin GMT:UTCβFUDGE:β D β likeUTCbut 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, like this:
: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 πΈ
okay bestie, this is the part where it gets good π these are real recipes you can steal, no cap.
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, served fresh π¦
The default dspec for the bk changes command is below. This is an example of a dspecβv2 style of writing dspecs β and honestly? much more pleasant for complex reports. it's giving structured-data-girlboss energy β¨
# 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β go read this one next, bestie, it's the whole vibe π«Ά
π CATEGORY π
File
okay that's the whole page, you made it to the end, icon behavior fr fr ππ this edition came to you from BitKeeper Inc, version 1E1, dated none in the footer β yeah, the date literally says none, and honestly? relatable. slay π
β¨
π¦ where this page comes from
- alpine 3.24: bitkeeper-doc 7.3.3-r1 Β· Apache-2.0
- Copyright (c) 1998-2015 BitKeeper Inc
- Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms