BK-LOG(1)

πŸ“¦ version 7.3.3, in alpine 3.24

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 -n here 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) or bk help range for 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 -d but read the dspec from a file.
  • -D β€” do not skip files in the BitKeeper/deleted directory. (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 -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. (yep, second -n of the page, the manpage said "reuse the letter, it ate" πŸ’€)
  • -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, 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:

  1. <repo>/BitKeeper/etc/dspec-changes
  2. <product>/BitKeeper/etc/dspec-changes (BitKeeper/Nested only)
  3. β€˜bk dotbkβ€˜/dspec-changes
  4. /etc/BitKeeper/etc/dspec-changes
  5. β€˜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 $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 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 β€” 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 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 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 and 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 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 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, 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
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms