Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Wed, 8 Nov 2006 00:34:30 +0000 (16:34 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 8 Nov 2006 00:34:30 +0000 (16:34 -0800)
* maint:
  remove an unneeded test

123 files changed:
.gitignore
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-blame.txt
Documentation/git-fetch-pack.txt
Documentation/git-for-each-ref.txt [new file with mode: 0644]
Documentation/git-grep.txt
Documentation/git-index-pack.txt
Documentation/git-pack-objects.txt
Documentation/git-pack-refs.txt [new file with mode: 0644]
Documentation/git-pickaxe.txt [new file with mode: 0644]
Documentation/git-pull.txt
Documentation/git-rebase.txt
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-show-ref.txt [new file with mode: 0644]
Documentation/git-update-ref.txt
Documentation/git.txt
Makefile
archive-zip.c
blame.c
builtin-annotate.c [new file with mode: 0644]
builtin-apply.c
builtin-branch.c [new file with mode: 0644]
builtin-fmt-merge-msg.c
builtin-for-each-ref.c [new file with mode: 0644]
builtin-grep.c
builtin-init-db.c
builtin-log.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-pack-refs.c [new file with mode: 0644]
builtin-pickaxe.c [new file with mode: 0644]
builtin-prune.c
builtin-push.c
builtin-repo-config.c
builtin-rev-parse.c
builtin-show-branch.c
builtin-show-ref.c [new file with mode: 0644]
builtin-symbolic-ref.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-update-ref.c
builtin.h
cache.h
check-builtins.sh [new file with mode: 0755]
combine-diff.c
config.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
contrib/emacs/vc-git.el
describe.c
diff.c
diff.h
diffcore-rename.c
environment.c
fetch-clone.c [deleted file]
fetch-pack.c
fetch.c
fsck-objects.c
git-annotate.perl [deleted file]
git-bisect.sh
git-branch.sh [deleted file]
git-checkout.sh
git-cherry.sh [deleted file]
git-clone.sh
git-commit.sh
git-cvsimport.perl
git-cvsserver.perl
git-fetch.sh
git-merge-one-file.sh
git-merge.sh
git-rebase.sh
git-repack.sh
git-resolve.sh
git-revert.sh
git-send-email.perl
git-svn.perl
git-svnimport.perl
git-tag.sh
git.c
gitweb/README
gitweb/gitweb.css
gitweb/gitweb.perl
grep.c
grep.h
http-push.c
index-pack.c
log-tree.c
merge-recursive.c
object.c
object.h
pack.h
quote.c
quote.h
receive-pack.c
refs.c
refs.h
revision.c
send-pack.c
server-info.c
setup.c
sha1_file.c
sha1_name.c
t/annotate-tests.sh
t/t1004-read-tree-m-u-wf.sh [new file with mode: 0755]
t/t1400-update-ref.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh [new file with mode: 0755]
t/t3401-rebase-partial.sh
t/t3402-rebase-merge.sh
t/t3403-rebase-skip.sh
t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
t/t5000-tar-tree.sh
t/t6021-merge-criss-cross.sh
t/t6022-merge-rename.sh
t/t8003-pickaxe.sh [new file with mode: 0755]
tree-diff.c
unpack-trees.c
upload-pack.c
wt-status.c

index 25eb463..4c8c8e4 100644 (file)
@@ -37,6 +37,7 @@ git-fetch
 git-fetch-pack
 git-findtags
 git-fmt-merge-msg
+git-for-each-ref
 git-format-patch
 git-fsck-objects
 git-get-tar-commit-id
@@ -74,6 +75,7 @@ git-name-rev
 git-mv
 git-pack-redundant
 git-pack-objects
+git-pack-refs
 git-parse-remote
 git-patch-id
 git-peek-remote
@@ -105,6 +107,7 @@ git-shortlog
 git-show
 git-show-branch
 git-show-index
+git-show-ref
 git-ssh-fetch
 git-ssh-pull
 git-ssh-push
index ee51fe3..9d3c71c 100644 (file)
@@ -71,12 +71,16 @@ core.preferSymlinkRefs::
        expect HEAD to be a symbolic link.
 
 core.logAllRefUpdates::
-       If true, `git-update-ref` will append a line to
-       "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
-       of the update.  If the file does not exist it will be
-       created automatically.  This information can be used to
-       determine what commit was the tip of a branch "2 days ago".
-       This value is false by default (no logging).
+       Updates to a ref <ref> is logged to the file
+       "$GIT_DIR/logs/<ref>", by appending the new and old
+       SHA1, the date/time and the reason of the update, but
+       only when the file exists.  If this configuration
+       variable is set to true, missing "$GIT_DIR/logs/<ref>"
+       file is automatically created for branch heads.
+
+       This information can be used to determine what commit
+       was the tip of a branch "2 days ago".  This value is
+       false by default (no automated creation of log files).
 
 core.repositoryFormatVersion::
        Internal variable identifying the repository format and layout
@@ -242,6 +246,10 @@ remote.<name>.push::
        The default set of "refspec" for gitlink:git-push[1]. See
        gitlink:git-push[1].
 
+repack.usedeltabaseoffset::
+       Allow gitlink:git-repack[1] to create packs that uses
+       delta-base offset.  Defaults to false.
+
 show.difftree::
        The default gitlink:git-diff-tree[1] arguments to be used
        for gitlink:git-show[1].
@@ -293,7 +301,16 @@ imap::
        The configuration variables in the 'imap' section are described
        in gitlink:git-imap-send[1].
 
-receive.denyNonFastforwads::
+receive.unpackLimit::
+       If the number of objects received in a push is below this
+       limit then the objects will be unpacked into loose object
+       files. However if the number of received objects equals or
+       exceeds this limit then the received pack will be stored as
+       a pack, after adding any missing delta bases.  Storing the
+       pack from a push can make the push operation complete faster,
+       especially on slow filesystems.
+
+receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
        not a fast forward. Use this to prevent such an update via a push,
        even if that push is forced. This configuration variable is
index 7b7b9e8..e112172 100644 (file)
        The width of the filename part can be controlled by
        giving another width to it separated by a comma.
 
+--numstat::
+       Similar to \--stat, but shows number of added and
+       deleted lines in decimal notation and pathname without
+       abbreviation, to make it more machine friendly.
+
 --summary::
        Output a condensed summary of extended header information
        such as creations, renames and mode changes.
index e1f8944..9891c1d 100644 (file)
@@ -7,7 +7,7 @@ git-blame - Show what revision and author last modified each line of a file
 
 SYNOPSIS
 --------
-'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>]
+'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] <file> [<rev>]
 
 DESCRIPTION
 -----------
@@ -45,10 +45,47 @@ OPTIONS
 -S, --rev-file <revs-file>::
        Use revs from revs-file instead of calling gitlink:git-rev-list[1].
 
+-f, --show-name::
+       Show filename in the original commit.  By default
+       filename is shown if there is any line that came from a
+       file with different name, due to rename detection.
+
+-n, --show-number::
+       Show line number in the original commit (Default: off).
+
+-p, --porcelain::
+       Show in a format designed for machine consumption.
+
 -h, --help::
        Show help message.
 
 
+THE PORCELAIN FORMAT
+--------------------
+
+In this format, each line is output after a header; the
+header at the minumum has the first line which has:
+
+- 40-byte SHA-1 of the commit the line is attributed to;
+- the line number of the line in the original file;
+- the line number of the line in the final file;
+- on a line that starts a group of line from a different
+  commit than the previous one, the number of lines in this
+  group.  On subsequent lines this field is absent.
+
+This header line is followed by the following information
+at least once for each commit:
+
+- author name ("author"), email ("author-mail"), time
+  ("author-time"), and timezone ("author-tz"); similarly
+  for committer.
+- filename in the commit the line is attributed to.
+- the first line of the commit log message ("summary").
+
+The contents of the actual line is output after the above
+header, prefixed by a TAB. This is to allow adding more
+header elements later.
+
 SEE ALSO
 --------
 gitlink:git-annotate[1]
index bff9aa6..3e6cd88 100644 (file)
@@ -32,7 +32,8 @@ OPTIONS
 -k::
        Do not invoke 'git-unpack-objects' on received data, but
        create a single packfile out of it instead, and store it
-       in the object database.
+       in the object database. If provided twice then the pack is
+       locked against repacking.
 
 --exec=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
new file mode 100644 (file)
index 0000000..2bf6aef
--- /dev/null
@@ -0,0 +1,185 @@
+git-for-each-ref(1)
+===================
+
+NAME
+----
+git-for-each-ref - Output information on each ref
+
+SYNOPSIS
+--------
+'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python] [--sort=<key>]\* [--format=<format>] [<pattern>]
+
+DESCRIPTION
+-----------
+
+Iterate over all refs that match `<pattern>` and show them
+according to the given `<format>`, after sorting them according
+to the given set of `<key>`.  If `<max>` is given, stop after
+showing that many refs.  The interporated values in `<format>`
+can optionally be quoted as string literals in the specified
+host language allowing their direct evaluation in that language.
+
+OPTIONS
+-------
+<count>::
+       By default the command shows all refs that match
+       `<pattern>`.  This option makes it stop after showing
+       that many refs.
+
+<key>::
+       A field name to sort on.  Prefix `-` to sort in
+       descending order of the value.  When unspecified,
+       `refname` is used.  More than one sort keys can be
+       given.
+
+<format>::
+       A string that interpolates `%(fieldname)` from the
+       object pointed at by a ref being shown.  If `fieldname`
+       is prefixed with an asterisk (`*`) and the ref points
+       at a tag object, the value for the field in the object
+       tag refers is used.  When unspecified, defaults to
+       `%(objectname) SPC %(objecttype) TAB %(refname)`.
+       It also interpolates `%%` to `%`, and `%xx` where `xx`
+       are hex digits interpolates to character with hex code
+       `xx`; for example `%00` interpolates to `\0` (NUL),
+       `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+
+<pattern>::
+       If given, the name of the ref is matched against this
+       using fnmatch(3).  Refs that do not match the pattern
+       are not shown.
+
+--shell, --perl, --python::
+       If given, strings that substitute `%(fieldname)`
+       placeholders are quoted as string literals suitable for
+       the specified host language.  This is meant to produce
+       a scriptlet that can directly be `eval`ed.
+
+
+FIELD NAMES
+-----------
+
+Various values from structured fields in referenced objects can
+be used to interpolate into the resulting output, or as sort
+keys.
+
+For all objects, the following names can be used:
+
+refname::
+       The name of the ref (the part after $GIT_DIR/refs/).
+
+objecttype::
+       The type of the object (`blob`, `tree`, `commit`, `tag`).
+
+objectsize::
+       The size of the object (the same as `git-cat-file -s` reports).
+
+objectname::
+       The object name (aka SHA-1).
+
+In addition to the above, for commit and tag objects, the header
+field names (`tree`, `parent`, `object`, `type`, and `tag`) can
+be used to specify the value in the header field.
+
+Fields that have name-email-date tuple as its value (`author`,
+`committer`, and `tagger`) can be suffixed with `name`, `email`,
+and `date` to extract the named component.
+
+The first line of the message in a commit and tag object is
+`subject`, the remaining lines are `body`.  The whole message
+is `contents`.
+
+For sorting purposes, fields with numeric values sort in numeric
+order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
+All other fields are used to sort in their byte-value order.
+
+In any case, a field name that refers to a field inapplicable to
+the object referred by the ref does not cause an error.  It
+returns an empty string instead.
+
+
+EXAMPLES
+--------
+
+An example directly producing formatted text.  Show the most recent
+3 tagged commits::
+
+------------
+#!/bin/sh
+
+git-for-each-ref --count=3 --sort='-*authordate' \
+--format='From: %(*authorname) %(*authoremail)
+Subject: %(*subject)
+Date: %(*authordate)
+Ref: %(*refname)
+
+%(*body)
+' 'refs/tags'
+------------
+
+
+A simple example showing the use of shell eval on the output,
+demonstrating the use of --shell.  List the prefixes of all heads::
+------------
+#!/bin/sh
+
+git-for-each-ref --shell --format="ref=%(refname)" refs/heads | \
+while read entry
+do
+       eval "$entry"
+       echo `dirname $ref`
+done
+------------
+
+
+A bit more elaborate report on tags, demonstrating that the format
+may be an entire script::
+------------
+#!/bin/sh
+
+fmt='
+       r=%(refname)
+       t=%(*objecttype)
+       T=${r#refs/tags/}
+
+       o=%(*objectname)
+       n=%(*authorname)
+       e=%(*authoremail)
+       s=%(*subject)
+       d=%(*authordate)
+       b=%(*body)
+
+       kind=Tag
+       if test "z$t" = z
+       then
+               # could be a lightweight tag
+               t=%(objecttype)
+               kind="Lightweight tag"
+               o=%(objectname)
+               n=%(authorname)
+               e=%(authoremail)
+               s=%(subject)
+               d=%(authordate)
+               b=%(body)
+       fi
+       echo "$kind $T points at a $t object $o"
+       if test "z$t" = zcommit
+       then
+               echo "The commit was authored by $n $e
+at $d, and titled
+
+    $s
+
+Its message reads as:
+"
+               echo "$b" | sed -e "s/^/    /"
+               echo
+       fi
+'
+
+eval=`git-for-each-ref --shell --format="$fmt" \
+       --sort='*objecttype' \
+       --sort=-taggerdate \
+       refs/tags`
+eval "$eval"
+------------
index d8af4d9..bfbece9 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
           [-v | --invert-match] [-h|-H] [--full-name]
           [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
           [-n] [-l | --files-with-matches] [-L | --files-without-match]
-          [-c | --count]
+          [-c | --count] [--all-match]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
           [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...]
           [<tree>...]
@@ -96,6 +96,11 @@ OPTIONS
        higher precedence than `--or`.  `-e` has to be used for all
        patterns.
 
+--all-match::
+       When giving multiple pattern expressions combined with `--or`,
+       this flag is specified to limit the match to files that
+       have lines to match all of them.
+
 `<tree>...`::
        Search blobs in the trees for specified patterns.
 
@@ -111,6 +116,10 @@ git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \)::
        Looks for a line that has `#define` and either `MAX_PATH` or
        `PATH_MAX`.
 
+git grep --all-match -e NODE -e Unexpected::
+       Looks for a line that has `NODE` or `Unexpected` in
+       files that have lines that match both.
+
 Author
 ------
 Originally written by Linus Torvalds <torvalds@osdl.org>, later
index 71ce557..2229ee8 100644 (file)
@@ -8,7 +8,8 @@ git-index-pack - Build pack index file for an existing packed archive
 
 SYNOPSIS
 --------
-'git-index-pack' [-o <index-file>] <pack-file>
+'git-index-pack' [-v] [-o <index-file>] <pack-file>
+'git-index-pack' --stdin [--fix-thin] [--keep] [-v] [-o <index-file>] [<pack-file>]
 
 
 DESCRIPTION
@@ -21,6 +22,9 @@ objects/pack/ directory of a git repository.
 
 OPTIONS
 -------
+-v::
+       Be verbose about what is going on, including progress status.
+
 -o <index-file>::
        Write the generated pack index into the specified
        file.  Without this option the name of pack index
@@ -29,6 +33,52 @@ OPTIONS
        fails if the name of packed archive does not end
        with .pack).
 
+--stdin::
+       When this flag is provided, the pack is read from stdin
+       instead and a copy is then written to <pack-file>. If
+       <pack-file> is not specified, the pack is written to
+       objects/pack/ directory of the current git repository with
+       a default name determined from the pack content.  If
+       <pack-file> is not specified consider using --keep to
+       prevent a race condition between this process and
+       gitlink::git-repack[1] .
+
+--fix-thin::
+       It is possible for gitlink:git-pack-objects[1] to build
+       "thin" pack, which records objects in deltified form based on
+       objects not included in the pack to reduce network traffic.
+       Those objects are expected to be present on the receiving end
+       and they must be included in the pack for that pack to be self
+       contained and indexable. Without this option any attempt to
+       index a thin pack will fail. This option only makes sense in
+       conjunction with --stdin.
+
+--keep::
+       Before moving the index into its final destination
+       create an empty .keep file for the associated pack file.
+       This option is usually necessary with --stdin to prevent a
+       simultaneous gitlink:git-repack[1] process from deleting
+       the newly constructed pack and index before refs can be
+       updated to use objects contained in the pack.
+
+--keep='why'::
+       Like --keep create a .keep file before moving the index into
+       its final destination, but rather than creating an empty file
+       place 'why' followed by an LF into the .keep file.  The 'why'
+       message can later be searched for within all .keep files to
+       locate any which have outlived their usefulness.
+
+
+Note
+----
+
+Once the index has been created, the list of object names is sorted
+and the SHA1 hash of that list is printed to stdout. If --stdin was
+also used then this is prefixed by either "pack\t", or "keep\t" if a
+new .keep file was successfully created. This is useful to remove a
+.keep file used as a lock to prevent the race with gitlink:git-repack[1]
+mentioned above.
+
 
 Author
 ------
index 944c9e1..fdc6f97 100644 (file)
@@ -9,8 +9,8 @@ git-pack-objects - Create a packed archive of objects
 SYNOPSIS
 --------
 [verse]
-'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
-       [--local] [--incremental] [--window=N] [--depth=N]
+'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+       [--local] [--incremental] [--window=N] [--depth=N] [--all-progress]
        [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
 
 
@@ -99,6 +99,23 @@ base-name::
         Only create a packed archive if it would contain at
         least one object.
 
+--progress::
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if
+       the standard error stream is not directed to a terminal.
+
+--all-progress::
+       When --stdout is specified then progress report is
+       displayed during the object count and deltification phases
+       but inhibited during the write-out phase. The reason is
+       that in some cases the output stream is directly linked
+       to another command which may wish to display progress
+       status of its own as it processes incoming pack data.
+       This flag is like --progress except that it forces progress
+       report for the write-out phase as well even if --stdout is
+       used.
+
 -q::
        This flag makes the command not to report its progress
        on the standard error stream.
@@ -110,6 +127,17 @@ base-name::
        This flag tells the command not to reuse existing deltas
        but compute them from scratch.
 
+--delta-base-offset::
+       A packed archive can express base object of a delta as
+       either 20-byte object name or as an offset in the
+       stream, but older version of git does not understand the
+       latter.  By default, git-pack-objects only uses the
+       former format for better compatibility.  This option
+       allows the command to use the latter format for
+       compactness.  Depending on the average delta chain
+       length, this option typically shrinks the resulting
+       packfile by 3-5 per-cent.
+
 
 Author
 ------
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
new file mode 100644 (file)
index 0000000..5da5105
--- /dev/null
@@ -0,0 +1,54 @@
+git-pack-refs(1)
+================
+
+NAME
+----
+git-pack-refs - Pack heads and tags for efficient repository access
+
+SYNOPSIS
+--------
+'git-pack-refs' [--all] [--prune]
+
+DESCRIPTION
+-----------
+
+Traditionally, tips of branches and tags (collectively known as
+'refs') were stored one file per ref under `$GIT_DIR/refs`
+directory.  While many branch tips tend to be updated often,
+most tags and some branch tips are never updated.  When a
+repository has hundreds or thousands of tags, this
+one-file-per-ref format both wastes storage and hurts
+performance.
+
+This command is used to solve the storage and performance
+problem by stashing the refs in a single file,
+`$GIT_DIR/packed-refs`.  When a ref is missing from the
+traditional `$GIT_DIR/refs` hierarchy, it is looked up in this
+file and used if found.
+
+Subsequent updates to branches always creates new file under
+`$GIT_DIR/refs` hierarchy.
+
+OPTIONS
+-------
+
+\--all::
+
+The command by default packs all tags and leaves branch tips
+alone.  This is because branches are expected to be actively
+developed and packing their tips does not help performance.
+This option causes branch tips to be packed as well.  Useful for
+a repository with many branches of historical interests.
+
+\--prune::
+
+After packing the refs, remove loose refs under `$GIT_DIR/refs`
+hierarchy.  This should probably become default.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt
new file mode 100644 (file)
index 0000000..c08fdec
--- /dev/null
@@ -0,0 +1,162 @@
+git-pickaxe(1)
+==============
+
+NAME
+----
+git-pickaxe - Show what revision and author last modified each line of a file
+
+SYNOPSIS
+--------
+[verse]
+'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>]
+              [-M] [-C] [-C] [--since=<date>] [<rev>] [--] <file>
+
+DESCRIPTION
+-----------
+
+Annotates each line in the given file with information from the revision which
+last modified the line. Optionally, start annotating from the given revision.
+
+Also it can limit the range of lines annotated.
+
+This report doesn't tell you anything about lines which have been deleted or
+replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe"
+interface briefly mentioned in the following paragraph.
+
+Apart from supporting file annotation, git also supports searching the
+development history for when a code snippet occured in a change. This makes it
+possible to track when a code snippet was added to a file, moved or copied
+between files, and eventually deleted or replaced. It works by searching for
+a text string in the diff. A small example:
+
+-----------------------------------------------------------------------------
+$ git log --pretty=oneline -S'blame_usage'
+5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S <ancestry-file>
+ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output
+-----------------------------------------------------------------------------
+
+OPTIONS
+-------
+-c, --compatibility::
+       Use the same output mode as gitlink:git-annotate[1] (Default: off).
+
+-L n,m::
+       Annotate only the specified line range (lines count from 1).
+
+-l, --long::
+       Show long rev (Default: off).
+
+-t, --time::
+       Show raw timestamp (Default: off).
+
+-S, --rev-file <revs-file>::
+       Use revs from revs-file instead of calling gitlink:git-rev-list[1].
+
+-f, --show-name::
+       Show filename in the original commit.  By default
+       filename is shown if there is any line that came from a
+       file with different name, due to rename detection.
+
+-n, --show-number::
+       Show line number in the original commit (Default: off).
+
+-p, --porcelain::
+       Show in a format designed for machine consumption.
+
+-M::
+       Detect moving lines in the file as well.  When a commit
+       moves a block of lines in a file (e.g. the original file
+       has A and then B, and the commit changes it to B and
+       then A), traditional 'blame' algorithm typically blames
+       the lines that were moved up (i.e. B) to the parent and
+       assigns blame to the lines that were moved down (i.e. A)
+       to the child commit.  With this option, both groups of
+       lines are blamed on the parent.
+
+-C::
+       In addition to `-M`, detect lines copied from other
+       files that were modified in the same commit.  This is
+       useful when you reorganize your program and move code
+       around across files.  When this option is given twice,
+       the command looks for copies from all other files in the
+       parent for the commit that creates the file in addition.
+
+-h, --help::
+       Show help message.
+
+
+THE PORCELAIN FORMAT
+--------------------
+
+In this format, each line is output after a header; the
+header at the minumum has the first line which has:
+
+- 40-byte SHA-1 of the commit the line is attributed to;
+- the line number of the line in the original file;
+- the line number of the line in the final file;
+- on a line that starts a group of line from a different
+  commit than the previous one, the number of lines in this
+  group.  On subsequent lines this field is absent.
+
+This header line is followed by the following information
+at least once for each commit:
+
+- author name ("author"), email ("author-mail"), time
+  ("author-time"), and timezone ("author-tz"); similarly
+  for committer.
+- filename in the commit the line is attributed to.
+- the first line of the commit log message ("summary").
+
+The contents of the actual line is output after the above
+header, prefixed by a TAB. This is to allow adding more
+header elements later.
+
+
+SPECIFIYING RANGES
+------------------
+
+Unlike `git-blame` and `git-annotate` in older git, the extent
+of annotation can be limited to both line ranges and revision
+ranges.  When you are interested in finding the origin for
+ll. 40-60 for file `foo`, you can use `-L` option like this:
+
+       git pickaxe -L 40,60 foo
+
+When you are not interested in changes older than the version
+v2.6.18, or changes older than 3 weeks, you can use revision
+range specifiers  similar to `git-rev-list`:
+
+       git pickaxe v2.6.18.. -- foo
+       git pickaxe --since=3.weeks -- foo
+
+When revision range specifiers are used to limit the annotation,
+lines that have not changed since the range boundary (either the
+commit v2.6.18 or the most recent commit that is more than 3
+weeks old in the above example) are blamed for that range
+boundary commit.
+
+A particularly useful way is to see if an added file have lines
+created by copy-and-paste from existing files.  Sometimes this
+indicates that the developer was being sloppy and did not
+refactor the code properly.  You can first find the commit that
+introduced the file with:
+
+       git log --diff-filter=A --pretty=short -- foo
+
+and then annotate the change between the commit and its
+parents, using `commit{caret}!` notation:
+
+       git pickaxe -C -C -f $commit^! -- foo
+
+
+SEE ALSO
+--------
+gitlink:git-blame[1]
+
+AUTHOR
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 51577fc..2a5aea7 100644 (file)
@@ -3,7 +3,7 @@ git-pull(1)
 
 NAME
 ----
-git-pull - Pull and merge from another repository
+git-pull - Pull and merge from another repository or a local branch
 
 
 SYNOPSIS
index 878eb6f..03e867a 100644 (file)
@@ -7,7 +7,7 @@ git-rebase - Rebase local commits to a new head
 
 SYNOPSIS
 --------
-'git-rebase' [--merge] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-v] [--merge] [--onto <newbase>] <upstream> [<branch>]
 
 'git-rebase' --continue | --skip | --abort
 
@@ -170,6 +170,9 @@ OPTIONS
        is used instead (`git-merge-recursive` when merging a single
        head, `git-merge-octopus` otherwise).  This implies --merge.
 
+-v, \--verbose::
+       Display a diffstat of what changed upstream since the last rebase.
+
 include::merge-strategies.txt[]
 
 NOTES
index d2eaa09..0fa47e3 100644 (file)
@@ -67,6 +67,20 @@ OPTIONS
        The default value for both --window and --depth is 10.
 
 
+Configuration
+-------------
+
+When configuration variable `repack.UseDeltaBaseOffset` is set
+for the repository, the command passes `--delta-base-offset`
+option to `git-pack-objects`; this typically results in slightly
+smaller packs, but the generated packs are incompatible with
+versions of git older than (and including) v1.4.3; do not set
+the variable in a repository that older version of git needs to
+be able to read (this includes repositories from which packs can
+be copied out over http or rsync, and people who obtained packs
+that way can try to use older git with it).
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 8a1ab61..8199615 100644 (file)
@@ -3,19 +3,19 @@ git-repo-config(1)
 
 NAME
 ----
-git-repo-config - Get and set options in .git/config
+git-repo-config - Get and set repository or global options.
 
 
 SYNOPSIS
 --------
 [verse]
-'git-repo-config' [type] name [value [value_regex]]
-'git-repo-config' [type] --replace-all name [value [value_regex]]
-'git-repo-config' [type] --get name [value_regex]
-'git-repo-config' [type] --get-all name [value_regex]
-'git-repo-config' [type] --unset name [value_regex]
-'git-repo-config' [type] --unset-all name [value_regex]
-'git-repo-config' -l | --list
+'git-repo-config' [--global] [type] name [value [value_regex]]
+'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
+'git-repo-config' [--global] [type] --get name [value_regex]
+'git-repo-config' [--global] [type] --get-all name [value_regex]
+'git-repo-config' [--global] [type] --unset name [value_regex]
+'git-repo-config' [--global] [type] --unset-all name [value_regex]
+'git-repo-config' [--global] -l | --list
 
 DESCRIPTION
 -----------
@@ -41,8 +41,9 @@ This command will fail if:
 . Can not write to .git/config,
 . no section was provided,
 . the section or key is invalid,
-. you try to unset an option which does not exist, or
-. you try to unset/set an option for which multiple lines match.
+. you try to unset an option which does not exist,
+. you try to unset/set an option for which multiple lines match, or
+. you use --global option without $HOME being properly set.
 
 
 OPTIONS
@@ -64,14 +65,17 @@ OPTIONS
 --get-regexp::
        Like --get-all, but interprets the name as a regular expression.
 
+--global::
+       Use global ~/.gitconfig file rather than the repository .git/config.
+
 --unset::
-       Remove the line matching the key from .git/config.
+       Remove the line matching the key from config file.
 
 --unset-all::
-       Remove all matching lines from .git/config.
+       Remove all matching lines from config file.
 
 -l, --list::
-       List all variables set in .git/config.
+       List all variables set in config file.
 
 
 ENVIRONMENT
@@ -79,6 +83,7 @@ ENVIRONMENT
 
 GIT_CONFIG::
        Take the configuration from the given file instead of .git/config.
+       Using the "--global" option forces this to ~/.gitconfig.
 
 GIT_CONFIG_LOCAL::
        Currently the same as $GIT_CONFIG; when Git will support global
index ed938aa..4eaf5a0 100644 (file)
@@ -239,14 +239,21 @@ of `r1` and `r2` and is defined as
 It it the set of commits that are reachable from either one of
 `r1` or `r2` but not from both.
 
-Here are a few examples:
+Two other shorthands for naming a set that is formed by a commit
+and its parent commits exists.  `r1{caret}@` notation means all
+parents of `r1`.  `r1{caret}!` includes commit `r1` but excludes
+its all parents.
+
+Here are a handful examples:
 
    D                A B D
    D F              A B C D F
-   ^A G                    B D
+   ^A G             B D
    ^A F             B C F
    G...I            C D F G I
-   ^B G I          C D F G I
+   ^B G I           C D F G I
+   F^@              A B C
+   F^! H            D F H
 
 Author
 ------
index 481b3f5..4c8d907 100644 (file)
@@ -66,8 +66,13 @@ The options available are:
        all that is output.
 
 --smtp-server::
-       If set, specifies the outgoing SMTP server to use.  Defaults to
-       localhost.
+       If set, specifies the outgoing SMTP server to use.  A full
+       pathname of a sendmail-like program can be specified instead;
+       the program must support the `-i` option.  Default value can
+       be specified by the 'sendemail.smtpserver' configuration
+       option; the built-in default is `/usr/sbin/sendmail` or
+       `/usr/lib/sendmail` if such program is available, or
+       `localhost` otherwise.
 
 --subject::
        Specify the initial subject of the email thread.
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
new file mode 100644 (file)
index 0000000..5973a82
--- /dev/null
@@ -0,0 +1,156 @@
+git-show-ref(1)
+===============
+
+NAME
+----
+git-show-ref - List references in a local repository
+
+SYNOPSIS
+--------
+[verse]
+'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
+            [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>...
+
+DESCRIPTION
+-----------
+
+Displays references available in a local repository along with the associated
+commit IDs. Results can be filtered using a pattern and tags can be
+dereferenced into object IDs. Additionally, it can be used to test whether a
+particular ref exists.
+
+Use of this utility is encouraged in favor of directly accessing files under
+in the `.git` directory.
+
+OPTIONS
+-------
+
+-h, --head::
+
+       Show the HEAD reference.
+
+--tags, --heads::
+
+       Limit to only "refs/heads" and "refs/tags", respectively.  These
+       options are not mutually exclusive; when given both, references stored
+       in "refs/heads" and "refs/tags" are displayed.
+
+-d, --dereference::
+
+       Dereference tags into object IDs as well. They will be shown with "^{}"
+       appended.
+
+-s, --hash::
+
+       Only show the SHA1 hash, not the reference name. When also using
+       --dereference the dereferenced tag will still be shown after the SHA1.
+
+--verify::
+
+       Enable stricter reference checking by requiring an exact ref path.
+       Aside from returning an error code of 1, it will also print an error
+       message if '--quiet' was not specified.
+
+--abbrev, --abbrev=len::
+
+       Abbreviate the object name.  When using `--hash`, you do
+       not have to say `--hash --abbrev`; `--hash=len` would do.
+
+-q, --quiet::
+
+       Do not print any results to stdout. When combined with '--verify' this
+       can be used to silently check if a reference exists.
+
+<pattern>::
+
+       Show references matching one or more patterns.
+
+OUTPUT
+------
+
+The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'.
+
+-----------------------------------------------------------------------------
+$ git show-ref --head --dereference
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin
+3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c
+6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{}
+055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4
+423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{}
+...
+-----------------------------------------------------------------------------
+
+When using --hash (and not --dereference) the output format is: '<SHA-1 ID>'
+
+-----------------------------------------------------------------------------
+$ git show-ref --heads --hash
+2e3ba0114a1f52b47df29743d6915d056be13278
+185008ae97960c8d551adcd9e23565194651b5d1
+03adf42c988195b50e1a1935ba5fcbc39b2b029b
+...
+-----------------------------------------------------------------------------
+
+EXAMPLE
+-------
+
+To show all references called "master", whether tags or heads or anything
+else, and regardless of how deep in the reference naming hierarchy they are,
+use:
+
+-----------------------------------------------------------------------------
+       git show-ref master
+-----------------------------------------------------------------------------
+
+This will show "refs/heads/master" but also "refs/remote/other-repo/master",
+if such references exists.
+
+When using the '--verify' flag, the command requires an exact path:
+
+-----------------------------------------------------------------------------
+       git show-ref --verify refs/heads/master
+-----------------------------------------------------------------------------
+
+will only match the exact branch called "master".
+
+If nothing matches, gitlink:git-show-ref[1] will return an error code of 1,
+and in the case of verification, it will show an error message.
+
+For scripting, you can ask it to be quiet with the "--quiet" flag, which
+allows you to do things like
+
+-----------------------------------------------------------------------------
+       git-show-ref --quiet --verify -- "refs/heads/$headname" ||
+               echo "$headname is not a valid branch"
+-----------------------------------------------------------------------------
+
+to check whether a particular branch exists or not (notice how we don't
+actually want to show any results, and we want to use the full refname for it
+in order to not trigger the problem with ambiguous partial matches).
+
+To show only tags, or only proper branch heads, use "--tags" and/or "--heads"
+respectively (using both means that it shows tags and heads, but not other
+random references under the refs/ subdirectory).
+
+To do automatic tag object dereferencing, use the "-d" or "--dereference"
+flag, so you can do
+
+-----------------------------------------------------------------------------
+       git show-ref --tags --dereference
+-----------------------------------------------------------------------------
+
+to get a listing of all tags together with what they dereference.
+
+SEE ALSO
+--------
+gitlink:git-ls-remote[1], gitlink:git-peek-remote[1]
+
+AUTHORS
+-------
+Written by Linus Torvalds <torvalds@osdl.org>.
+Man page by Jonas Fonseca <fonseca@diku.dk>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index e062030..71bcb79 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
 
 DESCRIPTION
 -----------
@@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that
 the current value of the <ref> matches <oldvalue>.
 E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
 updates the master branch head to <newvalue> only if its current
-value is <oldvalue>.
+value is <oldvalue>.  You can specify 40 "0" or an empty string
+as <oldvalue> to make sure that the ref you are creating does
+not exist.
 
 It also allows a "ref" file to be a symbolic pointer to another
 ref file by starting with the four-byte header sequence of
@@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a
 ref symlink to some other tree, if you have copied a whole
 archive by creating a symlink tree).
 
+With `-d` flag, it deletes the named <ref> after verifying it
+still contains <oldvalue>.
+
+
 Logging Updates
 ---------------
 If config parameter "core.logAllRefUpdates" is true or the file
index b00607e..4facf23 100644 (file)
@@ -72,185 +72,6 @@ GIT COMMANDS
 We divide git into high level ("porcelain") commands and low level
 ("plumbing") commands.
 
-Low-level commands (plumbing)
------------------------------
-
-Although git includes its
-own porcelain layer, its low-level commands are sufficient to support
-development of alternative porcelains.  Developers of such porcelains
-might start by reading about gitlink:git-update-index[1] and
-gitlink:git-read-tree[1].
-
-We divide the low-level commands into commands that manipulate objects (in
-the repository, index, and working tree), commands that interrogate and
-compare objects, and commands that move objects and references between
-repositories.
-
-Manipulation commands
-~~~~~~~~~~~~~~~~~~~~~
-gitlink:git-apply[1]::
-       Reads a "diff -up1" or git generated patch file and
-       applies it to the working tree.
-
-gitlink:git-checkout-index[1]::
-       Copy files from the index to the working tree.
-
-gitlink:git-commit-tree[1]::
-       Creates a new commit object.
-
-gitlink:git-hash-object[1]::
-       Computes the object ID from a file.
-
-gitlink:git-index-pack[1]::
-       Build pack idx file for an existing packed archive.
-
-gitlink:git-init-db[1]::
-       Creates an empty git object database, or reinitialize an
-       existing one.
-
-gitlink:git-merge-index[1]::
-       Runs a merge for files needing merging.
-
-gitlink:git-mktag[1]::
-       Creates a tag object.
-
-gitlink:git-mktree[1]::
-       Build a tree-object from ls-tree formatted text.
-
-gitlink:git-pack-objects[1]::
-       Creates a packed archive of objects.
-
-gitlink:git-prune-packed[1]::
-       Remove extra objects that are already in pack files.
-
-gitlink:git-read-tree[1]::
-       Reads tree information into the index.
-
-gitlink:git-repo-config[1]::
-       Get and set options in .git/config.
-
-gitlink:git-unpack-objects[1]::
-       Unpacks objects out of a packed archive.
-
-gitlink:git-update-index[1]::
-       Registers files in the working tree to the index.
-
-gitlink:git-write-tree[1]::
-       Creates a tree from the index.
-
-
-Interrogation commands
-~~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-cat-file[1]::
-       Provide content or type/size information for repository objects.
-
-gitlink:git-describe[1]::
-       Show the most recent tag that is reachable from a commit.
-
-gitlink:git-diff-index[1]::
-       Compares content and mode of blobs between the index and repository.
-
-gitlink:git-diff-files[1]::
-       Compares files in the working tree and the index.
-
-gitlink:git-diff-stages[1]::
-       Compares two "merge stages" in the index.
-
-gitlink:git-diff-tree[1]::
-       Compares the content and mode of blobs found via two tree objects.
-
-gitlink:git-fsck-objects[1]::
-       Verifies the connectivity and validity of the objects in the database.
-
-gitlink:git-ls-files[1]::
-       Information about files in the index and the working tree.
-
-gitlink:git-ls-tree[1]::
-       Displays a tree object in human readable form.
-
-gitlink:git-merge-base[1]::
-       Finds as good common ancestors as possible for a merge.
-
-gitlink:git-name-rev[1]::
-       Find symbolic names for given revs.
-
-gitlink:git-pack-redundant[1]::
-       Find redundant pack files.
-
-gitlink:git-rev-list[1]::
-       Lists commit objects in reverse chronological order.
-
-gitlink:git-show-index[1]::
-       Displays contents of a pack idx file.
-
-gitlink:git-tar-tree[1]::
-       Creates a tar archive of the files in the named tree object.
-
-gitlink:git-unpack-file[1]::
-       Creates a temporary file with a blob's contents.
-
-gitlink:git-var[1]::
-       Displays a git logical variable.
-
-gitlink:git-verify-pack[1]::
-       Validates packed git archive files.
-
-In general, the interrogate commands do not touch the files in
-the working tree.
-
-
-Synching repositories
-~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-fetch-pack[1]::
-       Updates from a remote repository (engine for ssh and
-       local transport).
-
-gitlink:git-http-fetch[1]::
-       Downloads a remote git repository via HTTP by walking
-       commit chain.
-
-gitlink:git-local-fetch[1]::
-       Duplicates another git repository on a local system by
-       walking commit chain.
-
-gitlink:git-peek-remote[1]::
-       Lists references on a remote repository using
-       upload-pack protocol (engine for ssh and local
-       transport).
-
-gitlink:git-receive-pack[1]::
-       Invoked by 'git-send-pack' to receive what is pushed to it.
-
-gitlink:git-send-pack[1]::
-       Pushes to a remote repository, intelligently.
-
-gitlink:git-http-push[1]::
-       Push missing objects using HTTP/DAV.
-
-gitlink:git-shell[1]::
-       Restricted shell for GIT-only SSH access.
-
-gitlink:git-ssh-fetch[1]::
-       Pulls from a remote repository over ssh connection by
-       walking commit chain.
-
-gitlink:git-ssh-upload[1]::
-       Helper "server-side" program used by git-ssh-fetch.
-
-gitlink:git-update-server-info[1]::
-       Updates auxiliary information on a dumb server to help
-       clients discover references and packs on it.
-
-gitlink:git-upload-archive[1]::
-       Invoked by 'git-archive' to send a generated archive.
-
-gitlink:git-upload-pack[1]::
-       Invoked by 'git-fetch-pack' to push
-       what are asked for.
-
-
 High-level commands (porcelain)
 -------------------------------
 
@@ -320,8 +141,11 @@ gitlink:git-merge[1]::
 gitlink:git-mv[1]::
        Move or rename a file, a directory, or a symlink.
 
+gitlink:git-pack-refs[1]::
+       Pack heads and tags for efficient repository access.
+
 gitlink:git-pull[1]::
-       Fetch from and merge with a remote repository.
+       Fetch from and merge with a remote repository or a local branch.
 
 gitlink:git-push[1]::
        Update remote refs along with associated objects.
@@ -430,6 +254,9 @@ gitlink:git-annotate[1]::
 gitlink:git-blame[1]::
        Blame file lines on commits.
 
+gitlink:git-pickaxe[1]::
+       Find out where each line in a file came from.
+
 gitlink:git-check-ref-format[1]::
        Make sure ref name is well formed.
 
@@ -488,6 +315,191 @@ gitlink:git-stripspace[1]::
        Filter out empty lines.
 
 
+Low-level commands (plumbing)
+-----------------------------
+
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+development of alternative porcelains.  Developers of such porcelains
+might start by reading about gitlink:git-update-index[1] and
+gitlink:git-read-tree[1].
+
+We divide the low-level commands into commands that manipulate objects (in
+the repository, index, and working tree), commands that interrogate and
+compare objects, and commands that move objects and references between
+repositories.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+gitlink:git-apply[1]::
+       Reads a "diff -up1" or git generated patch file and
+       applies it to the working tree.
+
+gitlink:git-checkout-index[1]::
+       Copy files from the index to the working tree.
+
+gitlink:git-commit-tree[1]::
+       Creates a new commit object.
+
+gitlink:git-hash-object[1]::
+       Computes the object ID from a file.
+
+gitlink:git-index-pack[1]::
+       Build pack idx file for an existing packed archive.
+
+gitlink:git-init-db[1]::
+       Creates an empty git object database, or reinitialize an
+       existing one.
+
+gitlink:git-merge-index[1]::
+       Runs a merge for files needing merging.
+
+gitlink:git-mktag[1]::
+       Creates a tag object.
+
+gitlink:git-mktree[1]::
+       Build a tree-object from ls-tree formatted text.
+
+gitlink:git-pack-objects[1]::
+       Creates a packed archive of objects.
+
+gitlink:git-prune-packed[1]::
+       Remove extra objects that are already in pack files.
+
+gitlink:git-read-tree[1]::
+       Reads tree information into the index.
+
+gitlink:git-repo-config[1]::
+       Get and set options in .git/config.
+
+gitlink:git-unpack-objects[1]::
+       Unpacks objects out of a packed archive.
+
+gitlink:git-update-index[1]::
+       Registers files in the working tree to the index.
+
+gitlink:git-write-tree[1]::
+       Creates a tree from the index.
+
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-cat-file[1]::
+       Provide content or type/size information for repository objects.
+
+gitlink:git-describe[1]::
+       Show the most recent tag that is reachable from a commit.
+
+gitlink:git-diff-index[1]::
+       Compares content and mode of blobs between the index and repository.
+
+gitlink:git-diff-files[1]::
+       Compares files in the working tree and the index.
+
+gitlink:git-diff-stages[1]::
+       Compares two "merge stages" in the index.
+
+gitlink:git-diff-tree[1]::
+       Compares the content and mode of blobs found via two tree objects.
+
+gitlink:git-for-each-ref[1]::
+       Output information on each ref.
+
+gitlink:git-fsck-objects[1]::
+       Verifies the connectivity and validity of the objects in the database.
+
+gitlink:git-ls-files[1]::
+       Information about files in the index and the working tree.
+
+gitlink:git-ls-tree[1]::
+       Displays a tree object in human readable form.
+
+gitlink:git-merge-base[1]::
+       Finds as good common ancestors as possible for a merge.
+
+gitlink:git-name-rev[1]::
+       Find symbolic names for given revs.
+
+gitlink:git-pack-redundant[1]::
+       Find redundant pack files.
+
+gitlink:git-rev-list[1]::
+       Lists commit objects in reverse chronological order.
+
+gitlink:git-show-index[1]::
+       Displays contents of a pack idx file.
+
+gitlink:git-show-ref[1]::
+       List references in a local repository.
+
+gitlink:git-tar-tree[1]::
+       Creates a tar archive of the files in the named tree object.
+
+gitlink:git-unpack-file[1]::
+       Creates a temporary file with a blob's contents.
+
+gitlink:git-var[1]::
+       Displays a git logical variable.
+
+gitlink:git-verify-pack[1]::
+       Validates packed git archive files.
+
+In general, the interrogate commands do not touch the files in
+the working tree.
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-fetch-pack[1]::
+       Updates from a remote repository (engine for ssh and
+       local transport).
+
+gitlink:git-http-fetch[1]::
+       Downloads a remote git repository via HTTP by walking
+       commit chain.
+
+gitlink:git-local-fetch[1]::
+       Duplicates another git repository on a local system by
+       walking commit chain.
+
+gitlink:git-peek-remote[1]::
+       Lists references on a remote repository using
+       upload-pack protocol (engine for ssh and local
+       transport).
+
+gitlink:git-receive-pack[1]::
+       Invoked by 'git-send-pack' to receive what is pushed to it.
+
+gitlink:git-send-pack[1]::
+       Pushes to a remote repository, intelligently.
+
+gitlink:git-http-push[1]::
+       Push missing objects using HTTP/DAV.
+
+gitlink:git-shell[1]::
+       Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
+       Pulls from a remote repository over ssh connection by
+       walking commit chain.
+
+gitlink:git-ssh-upload[1]::
+       Helper "server-side" program used by git-ssh-fetch.
+
+gitlink:git-update-server-info[1]::
+       Updates auxiliary information on a dumb server to help
+       clients discover references and packs on it.
+
+gitlink:git-upload-archive[1]::
+       Invoked by 'git-archive' to send a generated archive.
+
+gitlink:git-upload-pack[1]::
+       Invoked by 'git-fetch-pack' to push
+       what are asked for.
+
+
 Configuration Mechanism
 -----------------------
 
index b52dd57..a042df4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -132,6 +132,8 @@ GITWEB_HOMETEXT = indextext.html
 GITWEB_CSS = gitweb.css
 GITWEB_LOGO = git-logo.png
 GITWEB_FAVICON = git-favicon.png
+GITWEB_SITE_HEADER =
+GITWEB_SITE_FOOTER =
 
 export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
 
@@ -156,8 +158,8 @@ BASIC_CFLAGS =
 BASIC_LDFLAGS =
 
 SCRIPT_SH = \
-       git-bisect.sh git-branch.sh git-checkout.sh \
-       git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
+       git-bisect.sh git-checkout.sh \
+       git-clean.sh git-clone.sh git-commit.sh \
        git-fetch.sh \
        git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
@@ -173,7 +175,7 @@ SCRIPT_SH = \
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
        git-shortlog.perl git-rerere.perl \
-       git-annotate.perl git-cvsserver.perl \
+       git-cvsserver.perl \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
@@ -207,7 +209,7 @@ PROGRAMS = \
 EXTRA_PROGRAMS =
 
 BUILT_INS = \
-       git-format-patch$X git-show$X git-whatchanged$X \
+       git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
        git-get-tar-commit-id$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
@@ -255,15 +257,17 @@ LIB_OBJS = \
        quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
-       fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
+       revision.o pager.o tree-walk.o xdiff-interface.o \
        write_or_die.o trace.o list-objects.o grep.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
+       builtin-annotate.o \
        builtin-apply.o \
        builtin-archive.o \
+       builtin-branch.o \
        builtin-cat-file.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
@@ -275,6 +279,7 @@ BUILTIN_OBJS = \
        builtin-diff-stages.o \
        builtin-diff-tree.o \
        builtin-fmt-merge-msg.o \
+       builtin-for-each-ref.o \
        builtin-grep.o \
        builtin-init-db.o \
        builtin-log.o \
@@ -285,6 +290,7 @@ BUILTIN_OBJS = \
        builtin-mv.o \
        builtin-name-rev.o \
        builtin-pack-objects.o \
+       builtin-pickaxe.o \
        builtin-prune.o \
        builtin-prune-packed.o \
        builtin-push.o \
@@ -303,7 +309,9 @@ BUILTIN_OBJS = \
        builtin-update-ref.o \
        builtin-upload-archive.o \
        builtin-verify-pack.o \
-       builtin-write-tree.o
+       builtin-write-tree.o \
+       builtin-show-ref.o \
+       builtin-pack-refs.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 EXTLIBS = -lz
@@ -670,6 +678,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
            -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
            -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
            -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+           -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+           -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
            $< >$@+
        chmod +x $@+
        mv $@+ $@
@@ -923,3 +933,8 @@ check-docs::
                *) echo "no link: $$v";; \
                esac ; \
        done | sort
+
+### Make sure built-ins do not have dups and listed in git.c
+#
+check-builtins::
+       ./check-builtins.sh
index 3ffdad6..28e7352 100644 (file)
@@ -145,6 +145,7 @@ static int write_zip_entry(const unsigned char *sha1,
 {
        struct zip_local_header header;
        struct zip_dir_header dirent;
+       unsigned long attr2;
        unsigned long compressed_size;
        unsigned long uncompressed_size;
        unsigned long crc;
@@ -172,12 +173,16 @@ static int write_zip_entry(const unsigned char *sha1,
 
        if (S_ISDIR(mode)) {
                method = 0;
+               attr2 = 16;
                result = READ_TREE_RECURSIVE;
                out = NULL;
                uncompressed_size = 0;
                compressed_size = 0;
-       } else if (S_ISREG(mode)) {
-               method = zlib_compression_level == 0 ? 0 : 8;
+       } else if (S_ISREG(mode) || S_ISLNK(mode)) {
+               method = 0;
+               attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
+               if (S_ISREG(mode) && zlib_compression_level != 0)
+                       method = 8;
                result = 0;
                buffer = read_sha1_file(sha1, type, &size);
                if (!buffer)
@@ -213,8 +218,8 @@ static int write_zip_entry(const unsigned char *sha1,
        }
 
        copy_le32(dirent.magic, 0x02014b50);
-       copy_le16(dirent.creator_version, 0);
-       copy_le16(dirent.version, 20);
+       copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
+       copy_le16(dirent.version, 10);
        copy_le16(dirent.flags, 0);
        copy_le16(dirent.compression_method, method);
        copy_le16(dirent.mtime, zip_time);
@@ -227,7 +232,7 @@ static int write_zip_entry(const unsigned char *sha1,
        copy_le16(dirent.comment_length, 0);
        copy_le16(dirent.disk, 0);
        copy_le16(dirent.attr1, 0);
-       copy_le32(dirent.attr2, 0);
+       copy_le32(dirent.attr2, attr2);
        copy_le32(dirent.offset, zip_offset);
        memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
        zip_dir_offset += sizeof(struct zip_dir_header);
@@ -236,7 +241,7 @@ static int write_zip_entry(const unsigned char *sha1,
        zip_dir_entries++;
 
        copy_le32(header.magic, 0x04034b50);
-       copy_le16(header.version, 20);
+       copy_le16(header.version, 10);
        copy_le16(header.flags, 0);
        copy_le16(header.compression_method, method);
        copy_le16(header.mtime, zip_time);
diff --git a/blame.c b/blame.c
index 3e227d2..1144c85 100644 (file)
--- a/blame.c
+++ b/blame.c
 #include "diffcore.h"
 #include "revision.h"
 #include "xdiff-interface.h"
+#include "quote.h"
 
 #ifndef DEBUG
 #define DEBUG 0
 #endif
 
-static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n"
-       "  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
-       "  -l, --long          Show long commit SHA1 (Default: off)\n"
-       "  -t, --time          Show raw timestamp (Default: off)\n"
-       "  -S, --revs-file     Use revisions from revs-file instead of calling git-rev-list\n"
-       "  -h, --help          This message";
+static const char blame_usage[] =
+"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] file [commit]\n"
+"  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  -t, --time          Show raw timestamp (Default: off)\n"
+"  -f, --show-name     Show original filename (Default: auto)\n"
+"  -n, --show-number   Show original linenumber (Default: off)\n"
+"  -p, --porcelain     Show in a format designed for machine consumption\n"
+"  -S revs-file        Use revisions from revs-file instead of calling git-rev-list\n"
+"  -h, --help          This message";
 
 static struct commit **blame_lines;
 static int num_blame_lines;
-static charblame_contents;
+static char *blame_contents;
 static int blame_len;
 
 struct util_info {
@@ -40,9 +45,10 @@ struct util_info {
        char *buf;
        unsigned long size;
        int num_lines;
-       const char* pathname;
+       const char *pathname;
+       unsigned meta_given:1;
 
-       voidtopo_data;
+       void *topo_data;
 };
 
 struct chunk {
@@ -61,6 +67,7 @@ static void get_blob(struct commit *commit);
 static int num_get_patch;
 static int num_commits;
 static int patch_time;
+static int num_read_blob;
 
 struct blame_diff_state {
        struct xdiff_emit_state xm;
@@ -158,11 +165,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
                                  unsigned mode, int stage);
 
 static unsigned char blob_sha1[20];
-static const charblame_file;
+static const char *blame_file;
 static int get_blob_sha1(struct tree *t, const char *pathname,
                         unsigned char *sha1)
 {
-       int i;
        const char *pathspec[2];
        blame_file = pathname;
        pathspec[0] = pathname;
@@ -170,12 +176,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
        hashclr(blob_sha1);
        read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
 
-       for (i = 0; i < 20; i++) {
-               if (blob_sha1[i] != 0)
-                       break;
-       }
-
-       if (i == 20)
+       if (is_null_sha1(blob_sha1))
                return -1;
 
        hashcpy(sha1, blob_sha1);
@@ -206,6 +207,7 @@ static void get_blob(struct commit *commit)
                return;
 
        info->buf = read_sha1_file(info->sha1, type, &info->size);
+       num_read_blob++;
 
        assert(!strcmp(type, blob_type));
 }
@@ -244,7 +246,8 @@ static void print_map(struct commit *cmit, struct commit *other)
                if (i < util->num_lines) {
                        num = util->line_map[i];
                        printf("%d\t", num);
-               } else
+               }
+               else
                        printf("\t");
 
                if (i < util2->num_lines) {
@@ -252,7 +255,8 @@ static void print_map(struct commit *cmit, struct commit *other)
                        printf("%d\t", num2);
                        if (num != -1 && num2 != num)
                                printf("---");
-               } else
+               }
+               else
                        printf("\t");
 
                printf("\n");
@@ -271,12 +275,12 @@ static void fill_line_map(struct commit *commit, struct commit *other,
        int cur_chunk = 0;
        int i1, i2;
 
-       if (p->num && DEBUG)
-               print_patch(p);
-
-       if (DEBUG)
+       if (DEBUG) {
+               if (p->num)
+                       print_patch(p);
                printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
                       util2->num_lines);
+       }
 
        for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
                struct chunk *chunk = NULL;
@@ -298,7 +302,8 @@ static void fill_line_map(struct commit *commit, struct commit *other,
                                i2 += chunk->len2;
 
                        cur_chunk++;
-               } else {
+               }
+               else {
                        if (i2 >= util2->num_lines)
                                break;
 
@@ -332,19 +337,15 @@ static int map_line(struct commit *commit, int line)
        return info->line_map[line];
 }
 
-static struct util_infoget_util(struct commit *commit)
+static struct util_info *get_util(struct commit *commit)
 {
        struct util_info *util = commit->util;
 
        if (util)
                return util;
 
-       util = xmalloc(sizeof(struct util_info));
-       util->buf = NULL;
-       util->size = 0;
-       util->line_map = NULL;
+       util = xcalloc(1, sizeof(struct util_info));
        util->num_lines = -1;
-       util->pathname = NULL;
        commit->util = util;
        return util;
 }
@@ -374,7 +375,7 @@ static void alloc_line_map(struct commit *commit)
                if (util->buf[i] == '\n')
                        util->num_lines++;
        }
-       if(util->buf[util->size - 1] != '\n')
+       if (util->buf[util->size - 1] != '\n')
                util->num_lines++;
 
        util->line_map = xmalloc(sizeof(int) * util->num_lines);
@@ -383,9 +384,9 @@ static void alloc_line_map(struct commit *commit)
                util->line_map[i] = -1;
 }
 
-static void init_first_commit(struct commit* commit, const char* filename)
+static void init_first_commit(struct commit *commit, const char *filename)
 {
-       struct util_infoutil = commit->util;
+       struct util_info *util = commit->util;
        int i;
 
        util->pathname = filename;
@@ -400,18 +401,17 @@ static void init_first_commit(struct commit* commit, const char* filename)
                util->line_map[i] = i;
 }
 
-
 static void process_commits(struct rev_info *rev, const char *path,
-                           struct commit** initial)
+                           struct commit **initial)
 {
        int i;
-       struct util_infoutil;
+       struct util_info *util;
        int lines_left;
        int *blame_p;
        int *new_lines;
        int new_lines_len;
 
-       struct commitcommit = get_revision(rev);
+       struct commit *commit = get_revision(rev);
        assert(commit);
        init_first_commit(commit, path);
 
@@ -447,7 +447,7 @@ static void process_commits(struct rev_info *rev, const char *path,
                     parents != NULL; parents = parents->next)
                        num_parents++;
 
-               if(num_parents == 0)
+               if (num_parents == 0)
                        *initial = commit;
 
                if (fill_util_info(commit))
@@ -508,13 +508,12 @@ static void process_commits(struct rev_info *rev, const char *path,
        } while ((commit = get_revision(rev)) != NULL);
 }
 
-
-static int compare_tree_path(struct rev_info* revs,
-                            struct commit* c1, struct commit* c2)
+static int compare_tree_path(struct rev_info *revs,
+                            struct commit *c1, struct commit *c2)
 {
        int ret;
-       const charpaths[2];
-       struct util_infoutil = c2->util;
+       const char *paths[2];
+       struct util_info *util = c2->util;
        paths[0] = util->pathname;
        paths[1] = NULL;
 
@@ -525,12 +524,11 @@ static int compare_tree_path(struct rev_info* revs,
        return ret;
 }
 
-
-static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
-                                  const char* path)
+static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1,
+                                  const char *path)
 {
        int ret;
-       const charpaths[2];
+       const char *paths[2];
        paths[0] = path;
        paths[1] = NULL;
 
@@ -541,9 +539,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
        return ret;
 }
 
-static const char* find_rename(struct commit* commit, struct commit* parent)
+static const char *find_rename(struct commit *commit, struct commit *parent)
 {
-       struct util_infocutil = commit->util;
+       struct util_info *cutil = commit->util;
        struct diff_options diff_opts;
        const char *paths[1];
        int i;
@@ -569,9 +567,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent)
        for (i = 0; i < diff_queued_diff.nr; i++) {
                struct diff_filepair *p = diff_queued_diff.queue[i];
 
-               if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
+               if (p->status == 'R' &&
+                   !strcmp(p->one->path, cutil->pathname)) {
                        if (DEBUG)
-                               printf("rename %s -> %s\n", p->one->path, p->two->path);
+                               printf("rename %s -> %s\n",
+                                      p->one->path, p->two->path);
                        return p->two->path;
                }
        }
@@ -587,7 +587,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
                return;
 
        if (!commit->parents) {
-               struct util_infoutil = commit->util;
+               struct util_info *util = commit->util;
                if (!same_tree_as_empty_path(revs, commit->tree,
                                             util->pathname))
                        commit->object.flags |= TREECHANGE;
@@ -613,17 +613,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
 
                case REV_TREE_NEW:
                {
-
-                       struct util_info* util = commit->util;
+                       struct util_info *util = commit->util;
                        if (revs->remove_empty_trees &&
                            same_tree_as_empty_path(revs, p->tree,
                                                    util->pathname)) {
-                               const charnew_name = find_rename(commit, p);
+                               const char *new_name = find_rename(commit, p);
                                if (new_name) {
-                                       struct util_infoputil = get_util(p);
+                                       struct util_info *putil = get_util(p);
                                        if (!putil->pathname)
                                                putil->pathname = xstrdup(new_name);
-                               } else {
+                               }
+                               else {
                                        *pp = parent->next;
                                        continue;
                                }
@@ -644,47 +644,106 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
        commit->object.flags |= TREECHANGE;
 }
 
-
 struct commit_info
 {
-       charauthor;
-       charauthor_mail;
+       char *author;
+       char *author_mail;
        unsigned long author_time;
-       char* author_tz;
+       char *author_tz;
+
+       /* filled only when asked for details */
+       char *committer;
+       char *committer_mail;
+       unsigned long committer_time;
+       char *committer_tz;
+
+       char *summary;
 };
 
-static void get_commit_info(struct commit* commit, struct commit_info* ret)
+static void get_ac_line(const char *inbuf, const char *what,
+                       int bufsz, char *person, char **mail,
+                       unsigned long *time, char **tz)
 {
        int len;
-       char* tmp;
-       static char author_buf[1024];
-
-       tmp = strstr(commit->buffer, "\nauthor ") + 8;
-       len = strchr(tmp, '\n') - tmp;
-       ret->author = author_buf;
-       memcpy(ret->author, tmp, len);
+       char *tmp, *endp;
+
+       tmp = strstr(inbuf, what);
+       if (!tmp)
+               goto error_out;
+       tmp += strlen(what);
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               len = strlen(tmp);
+       else
+               len = endp - tmp;
+       if (bufsz <= len) {
+       error_out:
+               /* Ugh */
+               person = *mail = *tz = "(unknown)";
+               *time = 0;
+               return;
+       }
+       memcpy(person, tmp, len);
 
-       tmp = ret->author;
+       tmp = person;
        tmp += len;
        *tmp = 0;
-       while(*tmp != ' ')
+       while (*tmp != ' ')
                tmp--;
-       ret->author_tz = tmp+1;
+       *tz = tmp+1;
 
        *tmp = 0;
-       while(*tmp != ' ')
+       while (*tmp != ' ')
                tmp--;
-       ret->author_time = strtoul(tmp, NULL, 10);
+       *time = strtoul(tmp, NULL, 10);
 
        *tmp = 0;
-       while(*tmp != ' ')
+       while (*tmp != ' ')
                tmp--;
-       ret->author_mail = tmp + 1;
-
+       *mail = tmp + 1;
        *tmp = 0;
 }
 
-static const char* format_time(unsigned long time, const char* tz_str,
+static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed)
+{
+       int len;
+       char *tmp, *endp;
+       static char author_buf[1024];
+       static char committer_buf[1024];
+       static char summary_buf[1024];
+
+       ret->author = author_buf;
+       get_ac_line(commit->buffer, "\nauthor ",
+                   sizeof(author_buf), author_buf, &ret->author_mail,
+                   &ret->author_time, &ret->author_tz);
+
+       if (!detailed)
+               return;
+
+       ret->committer = committer_buf;
+       get_ac_line(commit->buffer, "\ncommitter ",
+                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+                   &ret->committer_time, &ret->committer_tz);
+
+       ret->summary = summary_buf;
+       tmp = strstr(commit->buffer, "\n\n");
+       if (!tmp) {
+       error_out:
+               sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+               return;
+       }
+       tmp += 2;
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               goto error_out;
+       len = endp - tmp;
+       if (len >= sizeof(summary_buf))
+               goto error_out;
+       memcpy(summary_buf, tmp, len);
+       summary_buf[len] = 0;
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
                               int show_raw_time)
 {
        static char time_buf[128];
@@ -709,15 +768,15 @@ static const char* format_time(unsigned long time, const char* tz_str,
        return time_buf;
 }
 
-static void topo_setter(struct commit* c, void* data)
+static void topo_setter(struct commit *c, void *data)
 {
-       struct util_infoutil = c->util;
+       struct util_info *util = c->util;
        util->topo_data = data;
 }
 
-static void* topo_getter(struct commit* c)
+static void *topo_getter(struct commit *c)
 {
-       struct util_infoutil = c->util;
+       struct util_info *util = c->util;
        return util->topo_data;
 }
 
@@ -740,6 +799,101 @@ static int read_ancestry(const char *graft_file,
        return 0;
 }
 
+static int lineno_width(int lines)
+{
+       int i, width;
+
+       for (width = 1, i = 10; i <= lines + 1; width++)
+               i *= 10;
+       return width;
+}
+
+static int find_orig_linenum(struct util_info *u, int lineno)
+{
+       int i;
+
+       for (i = 0; i < u->num_lines; i++)
+               if (lineno == u->line_map[i])
+                       return i + 1;
+       return 0;
+}
+
+static void emit_meta(struct commit *c, int lno,
+                     int sha1_len, int compatibility, int porcelain,
+                     int show_name, int show_number, int show_raw_time,
+                     int longest_file, int longest_author,
+                     int max_digits, int max_orig_digits)
+{
+       struct util_info *u;
+       int lineno;
+       struct commit_info ci;
+
+       u = c->util;
+       lineno = find_orig_linenum(u, lno);
+
+       if (porcelain) {
+               int group_size = -1;
+               struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1];
+               if (cc != c) {
+                       /* This is the beginning of this group */
+                       int i;
+                       for (i = lno + 1; i < num_blame_lines; i++)
+                               if (blame_lines[i] != c)
+                                       break;
+                       group_size = i - lno;
+               }
+               if (0 < group_size)
+                       printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1),
+                              lineno, lno + 1, group_size);
+               else
+                       printf("%s %d %d\n", sha1_to_hex(c->object.sha1),
+                              lineno, lno + 1);
+               if (!u->meta_given) {
+                       get_commit_info(c, &ci, 1);
+                       printf("author %s\n", ci.author);
+                       printf("author-mail %s\n", ci.author_mail);
+                       printf("author-time %lu\n", ci.author_time);
+                       printf("author-tz %s\n", ci.author_tz);
+                       printf("committer %s\n", ci.committer);
+                       printf("committer-mail %s\n", ci.committer_mail);
+                       printf("committer-time %lu\n", ci.committer_time);
+                       printf("committer-tz %s\n", ci.committer_tz);
+                       printf("filename ");
+                       if (quote_c_style(u->pathname, NULL, NULL, 0))
+                               quote_c_style(u->pathname, NULL, stdout, 0);
+                       else
+                               fputs(u->pathname, stdout);
+                       printf("\nsummary %s\n", ci.summary);
+
+                       u->meta_given = 1;
+               }
+               putchar('\t');
+               return;
+       }
+
+       get_commit_info(c, &ci, 0);
+       fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+       if (compatibility) {
+               printf("\t(%10s\t%10s\t%d)", ci.author,
+                      format_time(ci.author_time, ci.author_tz,
+                                  show_raw_time),
+                      lno + 1);
+       }
+       else {
+               if (show_name)
+                       printf(" %-*.*s", longest_file, longest_file,
+                              u->pathname);
+               if (show_number)
+                       printf(" %*d", max_orig_digits,
+                              lineno);
+               printf(" (%-*.*s %10s %*d) ",
+                      longest_author, longest_author, ci.author,
+                      format_time(ci.author_time, ci.author_tz,
+                                  show_raw_time),
+                      max_digits, lno + 1);
+       }
+}
+
 int main(int argc, const char **argv)
 {
        int i;
@@ -752,38 +906,43 @@ int main(int argc, const char **argv)
        int compatibility = 0;
        int show_raw_time = 0;
        int options = 1;
-       struct commitstart_commit;
+       struct commit *start_commit;
 
-       const charargs[10];
+       const char *args[10];
        struct rev_info rev;
 
        struct commit_info ci;
        const char *buf;
-       int max_digits;
-       int longest_file, longest_author;
-       int found_rename;
+       int max_digits, max_orig_digits;
+       int longest_file, longest_author, longest_file_lines;
+       int show_name = 0;
+       int show_number = 0;
+       int porcelain = 0;
 
-       const charprefix = setup_git_directory();
+       const char *prefix = setup_git_directory();
        git_config(git_default_config);
 
-       for(i = 1; i < argc; i++) {
-               if(options) {
-                       if(!strcmp(argv[i], "-h") ||
+       for (i = 1; i < argc; i++) {
+               if (options) {
+                       if (!strcmp(argv[i], "-h") ||
                           !strcmp(argv[i], "--help"))
                                usage(blame_usage);
-                       else if(!strcmp(argv[i], "-l") ||
-                               !strcmp(argv[i], "--long")) {
+                       if (!strcmp(argv[i], "-l") ||
+                           !strcmp(argv[i], "--long")) {
                                sha1_len = 40;
                                continue;
-                       } else if(!strcmp(argv[i], "-c") ||
-                                 !strcmp(argv[i], "--compatibility")) {
+                       }
+                       if (!strcmp(argv[i], "-c") ||
+                           !strcmp(argv[i], "--compatibility")) {
                                compatibility = 1;
                                continue;
-                       } else if(!strcmp(argv[i], "-t") ||
-                                 !strcmp(argv[i], "--time")) {
+                       }
+                       if (!strcmp(argv[i], "-t") ||
+                           !strcmp(argv[i], "--time")) {
                                show_raw_time = 1;
                                continue;
-                       } else if(!strcmp(argv[i], "-S")) {
+                       }
+                       if (!strcmp(argv[i], "-S")) {
                                if (i + 1 < argc &&
                                    !read_ancestry(argv[i + 1], &sha1_p)) {
                                        compatibility = 1;
@@ -791,33 +950,51 @@ int main(int argc, const char **argv)
                                        continue;
                                }
                                usage(blame_usage);
-                       } else if(!strcmp(argv[i], "--")) {
+                       }
+                       if (!strcmp(argv[i], "-f") ||
+                           !strcmp(argv[i], "--show-name")) {
+                               show_name = 1;
+                               continue;
+                       }
+                       if (!strcmp(argv[i], "-n") ||
+                           !strcmp(argv[i], "--show-number")) {
+                               show_number = 1;
+                               continue;
+                       }
+                       if (!strcmp(argv[i], "-p") ||
+                           !strcmp(argv[i], "--porcelain")) {
+                               porcelain = 1;
+                               sha1_len = 40;
+                               show_raw_time = 1;
+                               continue;
+                       }
+                       if (!strcmp(argv[i], "--")) {
                                options = 0;
                                continue;
-                       } else if(argv[i][0] == '-')
+                       }
+                       if (argv[i][0] == '-')
                                usage(blame_usage);
-                       else
-                               options = 0;
+                       options = 0;
                }
 
-               if(!options) {
-                       if(!filename)
+               if (!options) {
+                       if (!filename)
                                filename = argv[i];
-                       else if(!commit)
+                       else if (!commit)
                                commit = argv[i];
                        else
                                usage(blame_usage);
                }
        }
 
-       if(!filename)
+       if (!filename)
                usage(blame_usage);
        if (commit && sha1_p)
                usage(blame_usage);
-       else if(!commit)
+       else if (!commit)
                commit = "HEAD";
 
-       if(prefix)
+       if (prefix)
                sprintf(filename_buf, "%s%s", prefix, filename);
        else
                strcpy(filename_buf, filename);
@@ -835,7 +1012,6 @@ int main(int argc, const char **argv)
                return 1;
        }
 
-
        init_revisions(&rev, setup_git_directory());
        rev.remove_empty_trees = 1;
        rev.topo_order = 1;
@@ -853,68 +1029,56 @@ int main(int argc, const char **argv)
        prepare_revision_walk(&rev);
        process_commits(&rev, filename, &initial);
 
+       for (i = 0; i < num_blame_lines; i++)
+               if (!blame_lines[i])
+                       blame_lines[i] = initial;
+
        buf = blame_contents;
-       for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
-               i *= 10;
+       max_digits = lineno_width(num_blame_lines);
 
        longest_file = 0;
        longest_author = 0;
-       found_rename = 0;
+       longest_file_lines = 0;
        for (i = 0; i < num_blame_lines; i++) {
                struct commit *c = blame_lines[i];
-               struct util_info* u;
-               if (!c)
-                       c = initial;
+               struct util_info *u;
                u = c->util;
 
-               if (!found_rename && strcmp(filename, u->pathname))
-                       found_rename = 1;
+               if (!show_name && strcmp(filename, u->pathname))
+                       show_name = 1;
                if (longest_file < strlen(u->pathname))
                        longest_file = strlen(u->pathname);
-               get_commit_info(c, &ci);
+               if (longest_file_lines < u->num_lines)
+                       longest_file_lines = u->num_lines;
+               get_commit_info(c, &ci, 0);
                if (longest_author < strlen(ci.author))
                        longest_author = strlen(ci.author);
        }
 
-       for (i = 0; i < num_blame_lines; i++) {
-               struct commit *c = blame_lines[i];
-               struct util_info* u;
+       max_orig_digits = lineno_width(longest_file_lines);
 
-               if (!c)
-                       c = initial;
-
-               u = c->util;
-               get_commit_info(c, &ci);
-               fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
-               if(compatibility) {
-                       printf("\t(%10s\t%10s\t%d)", ci.author,
-                              format_time(ci.author_time, ci.author_tz,
-                                          show_raw_time),
-                              i+1);
-               } else {
-                       if (found_rename)
-                               printf(" %-*.*s", longest_file, longest_file,
-                                      u->pathname);
-                       printf(" (%-*.*s %10s %*d) ",
-                              longest_author, longest_author, ci.author,
-                              format_time(ci.author_time, ci.author_tz,
-                                          show_raw_time),
-                              max_digits, i+1);
-               }
+       for (i = 0; i < num_blame_lines; i++) {
+               emit_meta(blame_lines[i], i,
+                         sha1_len, compatibility, porcelain,
+                         show_name, show_number, show_raw_time,
+                         longest_file, longest_author,
+                         max_digits, max_orig_digits);
 
-               if(i == num_blame_lines - 1) {
+               if (i == num_blame_lines - 1) {
                        fwrite(buf, blame_len - (buf - blame_contents),
                               1, stdout);
-                       if(blame_contents[blame_len-1] != '\n')
+                       if (blame_contents[blame_len-1] != '\n')
                                putc('\n', stdout);
-               } else {
-                       char* next_buf = strchr(buf, '\n') + 1;
+               }
+               else {
+                       char *next_buf = strchr(buf, '\n') + 1;
                        fwrite(buf, next_buf - buf, 1, stdout);
                        buf = next_buf;
                }
        }
 
        if (DEBUG) {
+               printf("num read blob: %d\n", num_read_blob);
                printf("num get patch: %d\n", num_get_patch);
                printf("num commits: %d\n", num_commits);
                printf("patch time: %f\n", patch_time / 1000000.0);
diff --git a/builtin-annotate.c b/builtin-annotate.c
new file mode 100644 (file)
index 0000000..25ad473
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * "git annotate" builtin alias
+ *
+ * Copyright (C) 2006 Ryan Anderson
+ */
+#include "git-compat-util.h"
+#include "exec_cmd.h"
+
+int cmd_annotate(int argc, const char **argv, const char *prefix)
+{
+       const char **nargv;
+       int i;
+       nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+       nargv[0] = "blame";
+       nargv[1] = "-c";
+
+       for (i = 1; i < argc; i++) {
+               nargv[i+1] = argv[i];
+       }
+       nargv[argc + 1] = NULL;
+
+       return execv_git_cmd(nargv);
+}
+
index 6ec22b8..aad5526 100644 (file)
@@ -360,7 +360,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
 static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
 {
        if (!orig_name && !isnull)
-               return find_name(line, NULL, 1, 0);
+               return find_name(line, NULL, 1, TERM_TAB);
 
        if (orig_name) {
                int len;
@@ -370,7 +370,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
                len = strlen(name);
                if (isnull)
                        die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
-               another = find_name(line, NULL, 1, 0);
+               another = find_name(line, NULL, 1, TERM_TAB);
                if (!another || memcmp(another, name, len))
                        die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
                free(another);
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644 (file)
index 0000000..368b68e
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+
+static const char builtin_branch_usage[] =
+"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]";
+
+
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int in_merge_bases(const unsigned char *sha1,
+                         struct commit *rev1,
+                         struct commit *rev2)
+{
+       struct commit_list *bases, *b;
+       int ret = 0;
+
+       bases = get_merge_bases(rev1, rev2, 1);
+       for (b = bases; b; b = b->next) {
+               if (!hashcmp(sha1, b->item->object.sha1)) {
+                       ret = 1;
+                       break;
+               }
+       }
+
+       free_commit_list(bases);
+       return ret;
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+       struct commit *rev, *head_rev;
+       unsigned char sha1[20];
+       char *name;
+       int i;
+
+       head_rev = lookup_commit_reference(head_sha1);
+       for (i = 0; i < argc; i++) {
+               if (!strcmp(head, argv[i]))
+                       die("Cannot delete the branch you are currently on.");
+
+               name = xstrdup(mkpath("refs/heads/%s", argv[i]));
+               if (!resolve_ref(name, sha1, 1, NULL))
+                       die("Branch '%s' not found.", argv[i]);
+
+               rev = lookup_commit_reference(sha1);
+               if (!rev || !head_rev)
+                       die("Couldn't look up commit objects.");
+
+               /* This checks whether the merge bases of branch and
+                * HEAD contains branch -- which means that the HEAD
+                * contains everything in both.
+                */
+
+               if (!force &&
+                   !in_merge_bases(sha1, rev, head_rev)) {
+                       fprintf(stderr,
+                               "The branch '%s' is not a strict subset of your current HEAD.\n"
+                               "If you are sure you want to delete it, run 'git branch -D %s'.\n",
+                               argv[i], argv[i]);
+                       exit(1);
+               }
+
+               if (delete_ref(name, sha1))
+                       printf("Error deleting branch '%s'\n", argv[i]);
+               else
+                       printf("Deleted branch %s.\n", argv[i]);
+
+               free(name);
+       }
+}
+
+static int ref_index, ref_alloc;
+static char **ref_list;
+
+static int append_ref(const char *refname, const unsigned char *sha1, int flags,
+               void *cb_data)
+{
+       if (ref_index >= ref_alloc) {
+               ref_alloc = alloc_nr(ref_alloc);
+               ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *));
+       }
+
+       ref_list[ref_index++] = xstrdup(refname);
+
+       return 0;
+}
+
+static int ref_cmp(const void *r1, const void *r2)
+{
+       return strcmp(*(char **)r1, *(char **)r2);
+}
+
+static void print_ref_list(int remote_only)
+{
+       int i;
+       char c;
+
+       if (remote_only)
+               for_each_remote_ref(append_ref, NULL);
+       else
+               for_each_branch_ref(append_ref, NULL);
+
+       qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
+
+       for (i = 0; i < ref_index; i++) {
+               c = ' ';
+               if (!strcmp(ref_list[i], head))
+                       c = '*';
+
+               printf("%c %s\n", c, ref_list[i]);
+       }
+}
+
+static void create_branch(const char *name, const char *start,
+                         int force, int reflog)
+{
+       struct ref_lock *lock;
+       struct commit *commit;
+       unsigned char sha1[20];
+       char ref[PATH_MAX], msg[PATH_MAX + 20];
+
+       snprintf(ref, sizeof ref, "refs/heads/%s", name);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid branch name.", name);
+
+       if (resolve_ref(ref, sha1, 1, NULL)) {
+               if (!force)
+                       die("A branch named '%s' already exists.", name);
+               else if (!strcmp(head, name))
+                       die("Cannot force update the current branch.");
+       }
+
+       if (get_sha1(start, sha1) ||
+           (commit = lookup_commit_reference(sha1)) == NULL)
+               die("Not a valid branch point: '%s'.", start);
+       hashcpy(sha1, commit->object.sha1);
+
+       lock = lock_any_ref_for_update(ref, NULL);
+       if (!lock)
+               die("Failed to lock ref for update: %s.", strerror(errno));
+
+       if (reflog) {
+               log_all_ref_updates = 1;
+               snprintf(msg, sizeof msg, "branch: Created from %s", start);
+       }
+
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               die("Failed to write ref: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+       int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+       int reflog = 0;
+       int i;
+
+       git_config(git_default_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-d")) {
+                       delete = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-D")) {
+                       delete = 1;
+                       force_delete = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force_create = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-r")) {
+                       remote_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-l")) {
+                       reflog = 1;
+                       continue;
+               }
+               usage(builtin_branch_usage);
+       }
+
+       head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
+       if (!head)
+               die("Failed to resolve HEAD as a valid ref.");
+       if (strncmp(head, "refs/heads/", 11))
+               die("HEAD not found below refs/heads!");
+       head += 11;
+
+       if (delete)
+               delete_branches(argc - i, argv + i, force_delete);
+       else if (i == argc)
+               print_ref_list(remote_only);
+       else if (i == argc - 1)
+               create_branch(argv[i], head, force_create, reflog);
+       else if (i == argc - 2)
+               create_branch(argv[i], argv[i + 1], force_create, reflog);
+       else
+               usage(builtin_branch_usage);
+
+       return 0;
+}
index c407c03..3d3097d 100644 (file)
@@ -249,7 +249,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
        FILE *in = stdin;
        const char *sep = "";
        unsigned char head_sha1[20];
-       const char *head, *current_branch;
+       const char *current_branch;
 
        git_config(fmt_merge_msg_config);
 
@@ -277,10 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
                usage(fmt_merge_msg_usage);
 
        /* get current branch */
-       head = xstrdup(git_path("HEAD"));
-       current_branch = resolve_ref(head, head_sha1, 1);
-       current_branch += strlen(head) - 4;
-       free((char *)head);
+       current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
        if (!strncmp(current_branch, "refs/heads/", 11))
                current_branch += 11;
 
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
new file mode 100644 (file)
index 0000000..173bf38
--- /dev/null
@@ -0,0 +1,896 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include <fnmatch.h>
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 3
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+       const char *s;
+       unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+       struct ref_sort *next;
+       int atom; /* index into used_atom array */
+       unsigned reverse : 1;
+};
+
+struct refinfo {
+       char *refname;
+       unsigned char objectname[20];
+       struct atom_value *value;
+};
+
+static struct {
+       const char *name;
+       cmp_type cmp_type;
+} valid_atom[] = {
+       { "refname" },
+       { "objecttype" },
+       { "objectsize", FIELD_ULONG },
+       { "objectname" },
+       { "tree" },
+       { "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */
+       { "numparent", FIELD_ULONG },
+       { "object" },
+       { "type" },
+       { "tag" },
+       { "author" },
+       { "authorname" },
+       { "authoremail" },
+       { "authordate", FIELD_TIME },
+       { "committer" },
+       { "committername" },
+       { "committeremail" },
+       { "committerdate", FIELD_TIME },
+       { "tagger" },
+       { "taggername" },
+       { "taggeremail" },
+       { "taggerdate", FIELD_TIME },
+       { "creator" },
+       { "creatordate", FIELD_TIME },
+       { "subject" },
+       { "body" },
+       { "contents" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects.  refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+       const char *sp;
+       char *n;
+       int i, at;
+
+       sp = atom;
+       if (*sp == '*' && sp < ep)
+               sp++; /* deref */
+       if (ep <= sp)
+               die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+       /* Do we have the atom already used elsewhere? */
+       for (i = 0; i < used_atom_cnt; i++) {
+               int len = strlen(used_atom[i]);
+               if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+                       return i;
+       }
+
+       /* Is the atom a valid one? */
+       for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+               int len = strlen(valid_atom[i].name);
+               if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len))
+                       break;
+       }
+
+       if (ARRAY_SIZE(valid_atom) <= i)
+               die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+       /* Add it in, including the deref prefix */
+       at = used_atom_cnt;
+       used_atom_cnt++;
+       used_atom = xrealloc(used_atom,
+                            (sizeof *used_atom) * used_atom_cnt);
+       used_atom_type = xrealloc(used_atom_type,
+                                 (sizeof(*used_atom_type) * used_atom_cnt));
+       n = xmalloc(ep - atom + 1);
+       memcpy(n, atom, ep - atom);
+       n[ep-atom] = 0;
+       used_atom[at] = n;
+       used_atom_type[at] = valid_atom[i].cmp_type;
+       return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+       while (*cp) {
+               if (*cp == '%') {
+                       /* %( is the start of an atom;
+                        * %% is a quoteed per-cent.
+                        */
+                       if (cp[1] == '(')
+                               return cp;
+                       else if (cp[1] == '%')
+                               cp++; /* skip over two % */
+                       /* otherwise this is a singleton, literal % */
+               }
+               cp++;
+       }
+       return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static void verify_format(const char *format)
+{
+       const char *cp, *sp;
+       for (cp = format; *cp && (sp = find_next(cp)); ) {
+               const char *ep = strchr(sp, ')');
+               if (!ep)
+                       die("malformatted format string %s", sp);
+               /* sp points at "%(" and ep points at the closing ")" */
+               parse_atom(sp + 2, ep);
+               cp = ep + 1;
+       }
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object".  If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+       char type[20];
+       void *buf = read_sha1_file(sha1, type, sz);
+
+       if (buf)
+               *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+       else
+               *obj = NULL;
+       return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "objecttype"))
+                       v->s = type_names[obj->type];
+               else if (!strcmp(name, "objectsize")) {
+                       char *s = xmalloc(40);
+                       sprintf(s, "%lu", sz);
+                       v->ul = sz;
+                       v->s = s;
+               }
+               else if (!strcmp(name, "objectname")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(obj->sha1));
+                       v->s = s;
+               }
+       }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct tag *tag = (struct tag *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tag"))
+                       v->s = tag->tag;
+       }
+}
+
+static int num_parents(struct commit *commit)
+{
+       struct commit_list *parents;
+       int i;
+
+       for (i = 0, parents = commit->parents;
+            parents;
+            parents = parents->next)
+               i++;
+       return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct commit *commit = (struct commit *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tree")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+                       v->s = s;
+               }
+               if (!strcmp(name, "numparent")) {
+                       char *s = xmalloc(40);
+                       sprintf(s, "%lu", v->ul);
+                       v->s = s;
+                       v->ul = num_parents(commit);
+               }
+               else if (!strcmp(name, "parent")) {
+                       int num = num_parents(commit);
+                       int i;
+                       struct commit_list *parents;
+                       char *s = xmalloc(42 * num);
+                       v->s = s;
+                       for (i = 0, parents = commit->parents;
+                            parents;
+                            parents = parents->next, i = i + 42) {
+                               struct commit *parent = parents->item;
+                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
+                               if (parents->next)
+                                       s[i+40] = ' ';
+                       }
+               }
+       }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+       const char *eol;
+       while (*buf) {
+               if (!strncmp(buf, who, wholen) &&
+                   buf[wholen] == ' ')
+                       return buf + wholen + 1;
+               eol = strchr(buf, '\n');
+               if (!eol)
+                       return "";
+               eol++;
+               if (eol[1] == '\n')
+                       return ""; /* end of header */
+               buf = eol;
+       }
+       return "";
+}
+
+static char *copy_line(const char *buf)
+{
+       const char *eol = strchr(buf, '\n');
+       char *line;
+       int len;
+       if (!eol)
+               return "";
+       len = eol - buf;
+       line = xmalloc(len + 1);
+       memcpy(line, buf, len);
+       line[len] = 0;
+       return line;
+}
+
+static char *copy_name(const char *buf)
+{
+       const char *eol = strchr(buf, '\n');
+       const char *eoname = strstr(buf, " <");
+       char *line;
+       int len;
+       if (!(eoname && eol && eoname < eol))
+               return "";
+       len = eoname - buf;
+       line = xmalloc(len + 1);
+       memcpy(line, buf, len);
+       line[len] = 0;
+       return line;
+}
+
+static char *copy_email(const char *buf)
+{
+       const char *email = strchr(buf, '<');
+       const char *eoemail = strchr(email, '>');
+       char *line;
+       int len;
+       if (!email || !eoemail)
+               return "";
+       eoemail++;
+       len = eoemail - email;
+       line = xmalloc(len + 1);
+       memcpy(line, email, len);
+       line[len] = 0;
+       return line;
+}
+
+static void grab_date(const char *buf, struct atom_value *v)
+{
+       const char *eoemail = strstr(buf, "> ");
+       char *zone;
+       unsigned long timestamp;
+       long tz;
+
+       if (!eoemail)
+               goto bad;
+       timestamp = strtoul(eoemail + 2, &zone, 10);
+       if (timestamp == ULONG_MAX)
+               goto bad;
+       tz = strtol(zone, NULL, 10);
+       if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+               goto bad;
+       v->s = xstrdup(show_date(timestamp, tz, 0));
+       v->ul = timestamp;
+       return;
+ bad:
+       v->s = "";
+       v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       int wholen = strlen(who);
+       const char *wholine = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strncmp(who, name, wholen))
+                       continue;
+               if (name[wholen] != 0 &&
+                   strcmp(name + wholen, "name") &&
+                   strcmp(name + wholen, "email") &&
+                   strcmp(name + wholen, "date"))
+                       continue;
+               if (!wholine)
+                       wholine = find_wholine(who, wholen, buf, sz);
+               if (!wholine)
+                       return; /* no point looking for it */
+               if (name[wholen] == 0)
+                       v->s = copy_line(wholine);
+               else if (!strcmp(name + wholen, "name"))
+                       v->s = copy_name(wholine);
+               else if (!strcmp(name + wholen, "email"))
+                       v->s = copy_email(wholine);
+               else if (!strcmp(name + wholen, "date"))
+                       grab_date(wholine, v);
+       }
+
+       /* For a tag or a commit object, if "creator" or "creatordate" is
+        * requested, do something special.
+        */
+       if (strcmp(who, "tagger") && strcmp(who, "committer"))
+               return; /* "author" for commit object is not wanted */
+       if (!wholine)
+               wholine = find_wholine(who, wholen, buf, sz);
+       if (!wholine)
+               return;
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+
+               if (!strcmp(name, "creatordate"))
+                       grab_date(wholine, v);
+               else if (!strcmp(name, "creator"))
+                       v->s = copy_line(wholine);
+       }
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+       while (*buf) {
+               const char *eol = strchr(buf, '\n');
+               if (!eol)
+                       return;
+               if (eol[1] == '\n') {
+                       buf = eol + 1;
+                       break; /* found end of header */
+               }
+               buf = eol + 1;
+       }
+       while (*buf == '\n')
+               buf++;
+       if (!*buf)
+               return;
+       *sub = buf; /* first non-empty line */
+       buf = strchr(buf, '\n');
+       if (!buf)
+               return; /* no body */
+       while (*buf == '\n')
+               buf++; /* skip blank between subject and body */
+       *body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       const char *subpos = NULL, *bodypos = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strcmp(name, "subject") &&
+                   strcmp(name, "body") &&
+                   strcmp(name, "contents"))
+                       continue;
+               if (!subpos)
+                       find_subpos(buf, sz, &subpos, &bodypos);
+               if (!subpos)
+                       return;
+
+               if (!strcmp(name, "subject"))
+                       v->s = copy_line(subpos);
+               else if (!strcmp(name, "body"))
+                       v->s = bodypos;
+               else if (!strcmp(name, "contents"))
+                       v->s = subpos;
+       }
+}
+
+/* We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+       int i;
+       for (i = 0; i < used_atom_cnt; i++) {
+               struct atom_value *v = &val[i];
+               if (v->s == NULL)
+                       v->s = "";
+       }
+}
+
+/*
+ * val is a list of atom_value to hold returned values.  Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       grab_common_values(val, deref, obj, buf, sz);
+       switch (obj->type) {
+       case OBJ_TAG:
+               grab_tag_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("tagger", val, deref, obj, buf, sz);
+               break;
+       case OBJ_COMMIT:
+               grab_commit_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("author", val, deref, obj, buf, sz);
+               grab_person("committer", val, deref, obj, buf, sz);
+               break;
+       case OBJ_TREE:
+               // grab_tree_values(val, deref, obj, buf, sz);
+               break;
+       case OBJ_BLOB:
+               // grab_blob_values(val, deref, obj, buf, sz);
+               break;
+       default:
+               die("Eh?  Object of type %d?", obj->type);
+       }
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+       void *buf;
+       struct object *obj;
+       int eaten, i;
+       unsigned long size;
+       const unsigned char *tagged;
+
+       ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+       buf = get_obj(ref->objectname, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+
+       /* Fill in specials first */
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &ref->value[i];
+               if (!strcmp(name, "refname"))
+                       v->s = ref->refname;
+               else if (!strcmp(name, "*refname")) {
+                       int len = strlen(ref->refname);
+                       char *s = xmalloc(len + 4);
+                       sprintf(s, "%s^{}", ref->refname);
+                       v->s = s;
+               }
+       }
+
+       grab_values(ref->value, 0, obj, buf, size);
+       if (!eaten)
+               free(buf);
+
+       /* If there is no atom that wants to know about tagged
+        * object, we are done.
+        */
+       if (!need_tagged || (obj->type != OBJ_TAG))
+               return;
+
+       /* If it is a tag object, see if we use a value that derefs
+        * the object, and if we do grab the object it refers to.
+        */
+       tagged = ((struct tag *)obj)->tagged->sha1;
+
+       /* NEEDSWORK: This derefs tag only once, which
+        * is good to deal with chains of trust, but
+        * is not consistent with what deref_tag() does
+        * which peels the onion to the core.
+        */
+       buf = get_obj(tagged, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       grab_values(ref->value, 1, obj, buf, size);
+       if (!eaten)
+               free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom.  This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+       if (!ref->value) {
+               populate_value(ref);
+               fill_missing_values(ref->value);
+       }
+       *v = &ref->value[atom];
+}
+
+struct grab_ref_cbdata {
+       struct refinfo **grab_array;
+       const char **grab_pattern;
+       int grab_cnt;
+};
+
+/*
+ * A call-back given to for_each_ref().  It is unfortunate that we
+ * need to use global variables to pass extra information to this
+ * function.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct grab_ref_cbdata *cb = cb_data;
+       struct refinfo *ref;
+       int cnt;
+
+       if (*cb->grab_pattern) {
+               const char **pattern;
+               int namelen = strlen(refname);
+               for (pattern = cb->grab_pattern; *pattern; pattern++) {
+                       const char *p = *pattern;
+                       int plen = strlen(p);
+
+                       if ((plen <= namelen) &&
+                           !strncmp(refname, p, plen) &&
+                           (refname[plen] == '\0' ||
+                            refname[plen] == '/'))
+                               break;
+                       if (!fnmatch(p, refname, FNM_PATHNAME))
+                               break;
+               }
+               if (!*pattern)
+                       return 0;
+       }
+
+       /* We do not open the object yet; sort may only need refname
+        * to do its job and the resulting list may yet to be pruned
+        * by maxcount logic.
+        */
+       ref = xcalloc(1, sizeof(*ref));
+       ref->refname = xstrdup(refname);
+       hashcpy(ref->objectname, sha1);
+
+       cnt = cb->grab_cnt;
+       cb->grab_array = xrealloc(cb->grab_array,
+                                 sizeof(*cb->grab_array) * (cnt + 1));
+       cb->grab_array[cnt++] = ref;
+       cb->grab_cnt = cnt;
+       return 0;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+       struct atom_value *va, *vb;
+       int cmp;
+       cmp_type cmp_type = used_atom_type[s->atom];
+
+       get_value(a, s->atom, &va);
+       get_value(b, s->atom, &vb);
+       switch (cmp_type) {
+       case FIELD_STR:
+               cmp = strcmp(va->s, vb->s);
+               break;
+       default:
+               if (va->ul < vb->ul)
+                       cmp = -1;
+               else if (va->ul == vb->ul)
+                       cmp = 0;
+               else
+                       cmp = 1;
+               break;
+       }
+       return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+       struct refinfo *a = *((struct refinfo **)a_);
+       struct refinfo *b = *((struct refinfo **)b_);
+       struct ref_sort *s;
+
+       for (s = ref_sort; s; s = s->next) {
+               int cmp = cmp_ref_sort(s, a, b);
+               if (cmp)
+                       return cmp;
+       }
+       return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+       ref_sort = sort;
+       qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+       struct atom_value *v;
+       get_value(ref, atom, &v);
+       switch (quote_style) {
+       case QUOTE_NONE:
+               fputs(v->s, stdout);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_print(stdout, v->s);
+               break;
+       case QUOTE_PERL:
+               perl_quote_print(stdout, v->s);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_print(stdout, v->s);
+               break;
+       }
+}
+
+static int hex1(char ch)
+{
+       if ('0' <= ch && ch <= '9')
+               return ch - '0';
+       else if ('a' <= ch && ch <= 'f')
+               return ch - 'a' + 10;
+       else if ('A' <= ch && ch <= 'F')
+               return ch - 'A' + 10;
+       return -1;
+}
+static int hex2(const char *cp)
+{
+       if (cp[0] && cp[1])
+               return (hex1(cp[0]) << 4) | hex1(cp[1]);
+       else
+               return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+       while (*cp && (!ep || cp < ep)) {
+               if (*cp == '%') {
+                       if (cp[1] == '%')
+                               cp++;
+                       else {
+                               int ch = hex2(cp + 1);
+                               if (0 <= ch) {
+                                       putchar(ch);
+                                       cp += 3;
+                                       continue;
+                               }
+                       }
+               }
+               putchar(*cp);
+               cp++;
+       }
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+       const char *cp, *sp, *ep;
+
+       for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+               ep = strchr(sp, ')');
+               if (cp < sp)
+                       emit(cp, sp);
+               print_value(info, parse_atom(sp + 2, ep), quote_style);
+       }
+       if (*cp) {
+               sp = cp + strlen(cp);
+               emit(cp, sp);
+       }
+       putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+       static const char cstr_name[] = "refname";
+
+       struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+       sort->next = NULL;
+       sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+       return sort;
+}
+
+int cmd_for_each_ref(int ac, const char **av, char *prefix)
+{
+       int i, num_refs;
+       const char *format = NULL;
+       struct ref_sort *sort = NULL, **sort_tail = &sort;
+       int maxcount = 0;
+       int quote_style = -1; /* unspecified yet */
+       struct refinfo **refs;
+       struct grab_ref_cbdata cbdata;
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strncmp(arg, "--format=", 9)) {
+                       if (format)
+                               die("more than one --format?");
+                       format = arg + 9;
+                       continue;
+               }
+               if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_SHELL;
+                       continue;
+               }
+               if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_PERL;
+                       continue;
+               }
+               if (!strcmp(arg, "--python") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_PYTHON;
+                       continue;
+               }
+               if (!strncmp(arg, "--count=", 8)) {
+                       if (maxcount)
+                               die("more than one --count?");
+                       maxcount = atoi(arg + 8);
+                       if (maxcount <= 0)
+                               die("The number %s did not parse", arg);
+                       continue;
+               }
+               if (!strncmp(arg, "--sort=", 7)) {
+                       struct ref_sort *s = xcalloc(1, sizeof(*s));
+                       int len;
+
+                       s->next = NULL;
+                       *sort_tail = s;
+                       sort_tail = &s->next;
+
+                       arg += 7;
+                       if (*arg == '-') {
+                               s->reverse = 1;
+                               arg++;
+                       }
+                       len = strlen(arg);
+                       sort->atom = parse_atom(arg, arg+len);
+                       continue;
+               }
+               break;
+       }
+       if (quote_style < 0)
+               quote_style = QUOTE_NONE;
+
+       if (!sort)
+               sort = default_sort();
+       sort_atom_limit = used_atom_cnt;
+       if (!format)
+               format = "%(objectname) %(objecttype)\t%(refname)";
+
+       verify_format(format);
+
+       memset(&cbdata, 0, sizeof(cbdata));
+       cbdata.grab_pattern = av + i;
+       for_each_ref(grab_single_ref, &cbdata);
+       refs = cbdata.grab_array;
+       num_refs = cbdata.grab_cnt;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               if (used_atom[i][0] == '*') {
+                       need_tagged = 1;
+                       break;
+               }
+       }
+
+       sort_refs(sort, refs, num_refs);
+
+       if (!maxcount || num_refs < maxcount)
+               maxcount = num_refs;
+       for (i = 0; i < maxcount; i++)
+               show_ref(refs[i], format, quote_style);
+       return 0;
+}
index 4205e5d..ad7dc00 100644 (file)
@@ -596,6 +596,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                                            GREP_CLOSE_PAREN);
                        continue;
                }
+               if (!strcmp("--all-match", arg)) {
+                       opt.all_match = 1;
+                       continue;
+               }
                if (!strcmp("-e", arg)) {
                        if (1 < argc) {
                                append_grep_pattern(&opt, argv[1],
index c3ed1ce..235a0ee 100644 (file)
@@ -218,8 +218,8 @@ static void create_default_files(const char *git_dir, const char *template_path)
         * branch, if it does not exist yet.
         */
        strcpy(path + len, "HEAD");
-       if (read_ref(path, sha1) < 0) {
-               if (create_symref(path, "refs/heads/master") < 0)
+       if (read_ref("HEAD", sha1) < 0) {
+               if (create_symref("HEAD", "refs/heads/master") < 0)
                        exit(1);
        }
 
index 9d1ceae..fedb013 100644 (file)
@@ -171,8 +171,11 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
 static int get_patch_id(struct commit *commit, struct diff_options *options,
                unsigned char *sha1)
 {
-       diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
-                       "", options);
+       if (commit->parents)
+               diff_tree_sha1(commit->parents->item->object.sha1,
+                              commit->object.sha1, "", options);
+       else
+               diff_root_tree_sha1(commit->object.sha1, "", options);
        diffcore_std(options);
        return diff_flush_patch_id(options, sha1);
 }
@@ -437,3 +440,109 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
+static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
+{
+       unsigned char sha1[20];
+       if (get_sha1(arg, sha1) == 0) {
+               struct commit *commit = lookup_commit_reference(sha1);
+               if (commit) {
+                       commit->object.flags |= flags;
+                       add_pending_object(revs, &commit->object, arg);
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+static const char cherry_usage[] =
+"git-cherry [-v] <upstream> [<head>] [<limit>]";
+int cmd_cherry(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       struct diff_options patch_id_opts;
+       struct commit *commit;
+       struct commit_list *list = NULL;
+       const char *upstream;
+       const char *head = "HEAD";
+       const char *limit = NULL;
+       int verbose = 0;
+
+       if (argc > 1 && !strcmp(argv[1], "-v")) {
+               verbose = 1;
+               argc--;
+               argv++;
+       }
+
+       switch (argc) {
+       case 4:
+               limit = argv[3];
+               /* FALLTHROUGH */
+       case 3:
+               head = argv[2];
+               /* FALLTHROUGH */
+       case 2:
+               upstream = argv[1];
+               break;
+       default:
+               usage(cherry_usage);
+       }
+
+       init_revisions(&revs, prefix);
+       revs.diff = 1;
+       revs.combine_merges = 0;
+       revs.ignore_merges = 1;
+       revs.diffopt.recursive = 1;
+
+       if (add_pending_commit(head, &revs, 0))
+               die("Unknown commit %s", head);
+       if (add_pending_commit(upstream, &revs, UNINTERESTING))
+               die("Unknown commit %s", upstream);
+
+       /* Don't say anything if head and upstream are the same. */
+       if (revs.pending.nr == 2) {
+               struct object_array_entry *o = revs.pending.objects;
+               if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+                       return 0;
+       }
+
+       get_patch_ids(&revs, &patch_id_opts, prefix);
+
+       if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
+               die("Unknown commit %s", limit);
+
+       /* reverse the list of commits */
+       prepare_revision_walk(&revs);
+       while ((commit = get_revision(&revs)) != NULL) {
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               commit_list_insert(commit, &list);
+       }
+
+       while (list) {
+               unsigned char sha1[20];
+               char sign = '+';
+
+               commit = list->item;
+               if (!get_patch_id(commit, &patch_id_opts, sha1) &&
+                   lookup_object(sha1))
+                       sign = '-';
+
+               if (verbose) {
+                       static char buf[16384];
+                       pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+                                           buf, sizeof(buf), 0, NULL, NULL, 0);
+                       printf("%c %s %s\n", sign,
+                              sha1_to_hex(commit->object.sha1), buf);
+               }
+               else {
+                       printf("%c %s\n", sign,
+                              sha1_to_hex(commit->object.sha1));
+               }
+
+               list = list->next;
+       }
+
+       return 0;
+}
index 52886b6..618aa31 100644 (file)
@@ -75,11 +75,10 @@ copy_data:
        }
 }
 
-static int tags_only;
-
-static int name_ref(const char *path, const unsigned char *sha1)
+static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
 {
        struct object *o = parse_object(sha1);
+       int tags_only = *(int*)cb_data;
        int deref = 0;
 
        if (tags_only && strncmp(path, "refs/tags/", 10))
@@ -131,6 +130,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
        struct object_array revs = { 0, 0, NULL };
        int as_is = 0, all = 0, transform_stdin = 0;
+       int tags_only = 0;
 
        git_config(git_default_config);
 
@@ -186,7 +186,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                add_object_array((struct object *)commit, *argv, &revs);
        }
 
-       for_each_ref(name_ref);
+       for_each_ref(name_ref, &tags_only);
 
        if (transform_stdin) {
                char buffer[2048];
index 96c069a..69e5dd3 100644 (file)
 #include <sys/time.h>
 #include <signal.h>
 
-static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
+static const char pack_usage[] = "\
+git-pack-objects [{ -q | --progress | --all-progress }] \n\
+       [--local] [--incremental] [--window=N] [--depth=N] \n\
+       [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
+       [--revs [--unpacked | --all]*] [--stdout | base-name] \n\
+       [<ref-list | <object-list]";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -29,6 +34,7 @@ struct object_entry {
        enum object_type type;
        enum object_type in_pack_type;  /* could be delta */
        unsigned long delta_size;       /* delta data size (uncompressed) */
+#define in_pack_header_size delta_size /* only when reusing pack data */
        struct object_entry *delta;     /* delta base object */
        struct packed_git *in_pack;     /* already in pack */
        unsigned int in_pack_offset;
@@ -60,6 +66,8 @@ static int non_empty;
 static int no_reuse_delta;
 static int local;
 static int incremental;
+static int allow_ofs_delta;
+
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects;
 static int nr_objects, nr_alloc, nr_result;
@@ -84,17 +92,25 @@ static int object_ix_hashsz;
  * Pack index for existing packs give us easy access to the offsets into
  * corresponding pack file where each object's data starts, but the entries
  * do not store the size of the compressed representation (uncompressed
- * size is easily available by examining the pack entry header).  We build
- * a hashtable of existing packs (pack_revindex), and keep reverse index
- * here -- pack index file is sorted by object name mapping to offset; this
- * pack_revindex[].revindex array is an ordered list of offsets, so if you
- * know the offset of an object, next offset is where its packed
- * representation ends.
+ * size is easily available by examining the pack entry header).  It is
+ * also rather expensive to find the sha1 for an object given its offset.
+ *
+ * We build a hashtable of existing packs (pack_revindex), and keep reverse
+ * index here -- pack index file is sorted by object name mapping to offset;
+ * this pack_revindex[].revindex array is a list of offset/index_nr pairs
+ * ordered by offset, so if you know the offset of an object, next offset
+ * is where its packed representation ends and the index_nr can be used to
+ * get the object sha1 from the main index.
  */
+struct revindex_entry {
+       unsigned int offset;
+       unsigned int nr;
+};
 struct pack_revindex {
        struct packed_git *p;
-       unsigned long *revindex;
-} *pack_revindex = NULL;
+       struct revindex_entry *revindex;
+};
+static struct  pack_revindex *pack_revindex;
 static int pack_revindex_hashsz;
 
 /*
@@ -141,14 +157,9 @@ static void prepare_pack_ix(void)
 
 static int cmp_offset(const void *a_, const void *b_)
 {
-       unsigned long a = *(unsigned long *) a_;
-       unsigned long b = *(unsigned long *) b_;
-       if (a < b)
-               return -1;
-       else if (a == b)
-               return 0;
-       else
-               return 1;
+       const struct revindex_entry *a = a_;
+       const struct revindex_entry *b = b_;
+       return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
 }
 
 /*
@@ -161,25 +172,27 @@ static void prepare_pack_revindex(struct pack_revindex *rix)
        int i;
        void *index = p->index_base + 256;
 
-       rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
+       rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
        for (i = 0; i < num_ent; i++) {
                unsigned int hl = *((unsigned int *)((char *) index + 24*i));
-               rix->revindex[i] = ntohl(hl);
+               rix->revindex[i].offset = ntohl(hl);
+               rix->revindex[i].nr = i;
        }
        /* This knows the pack format -- the 20-byte trailer
         * follows immediately after the last object data.
         */
-       rix->revindex[num_ent] = p->pack_size - 20;
-       qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset);
+       rix->revindex[num_ent].offset = p->pack_size - 20;
+       rix->revindex[num_ent].nr = -1;
+       qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
 }
 
-static unsigned long find_packed_object_size(struct packed_git *p,
-                                            unsigned long ofs)
+static struct revindex_entry * find_packed_object(struct packed_git *p,
+                                                 unsigned int ofs)
 {
        int num;
        int lo, hi;
        struct pack_revindex *rix;
-       unsigned long *revindex;
+       struct revindex_entry *revindex;
        num = pack_revindex_ix(p);
        if (num < 0)
                die("internal error: pack revindex uninitialized");
@@ -191,10 +204,10 @@ static unsigned long find_packed_object_size(struct packed_git *p,
        hi = num_packed_objects(p) + 1;
        do {
                int mi = (lo + hi) / 2;
-               if (revindex[mi] == ofs) {
-                       return revindex[mi+1] - ofs;
+               if (revindex[mi].offset == ofs) {
+                       return revindex + mi;
                }
-               else if (ofs < revindex[mi])
+               else if (ofs < revindex[mi].offset)
                        hi = mi;
                else
                        lo = mi + 1;
@@ -202,6 +215,20 @@ static unsigned long find_packed_object_size(struct packed_git *p,
        die("internal error: pack revindex corrupt");
 }
 
+static unsigned long find_packed_object_size(struct packed_git *p,
+                                            unsigned long ofs)
+{
+       struct revindex_entry *entry = find_packed_object(p, ofs);
+       return entry[1].offset - ofs;
+}
+
+static unsigned char *find_packed_object_name(struct packed_git *p,
+                                             unsigned long ofs)
+{
+       struct revindex_entry *entry = find_packed_object(p, ofs);
+       return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4;
+}
+
 static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
 {
        unsigned long othersize, delta_size;
@@ -232,7 +259,7 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
        int n = 1;
        unsigned char c;
 
-       if (type < OBJ_COMMIT || type > OBJ_DELTA)
+       if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
                die("bad type %d", type);
 
        c = (type << 4) | (size & 15);
@@ -247,6 +274,10 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
        return n;
 }
 
+/*
+ * we are going to reuse the existing object data as is.  make
+ * sure it is not corrupt.
+ */
 static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect)
 {
        z_stream stream;
@@ -278,32 +309,6 @@ static int check_inflate(unsigned char *data, unsigned long len, unsigned long e
        return st;
 }
 
-/*
- * we are going to reuse the existing pack entry data.  make
- * sure it is not corrupt.
- */
-static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len)
-{
-       enum object_type type;
-       unsigned long size, used;
-
-       if (pack_to_stdout)
-               return 0;
-
-       /* the caller has already called use_packed_git() for us,
-        * so it is safe to access the pack data from mmapped location.
-        * make sure the entry inflates correctly.
-        */
-       used = unpack_object_header_gently(data, len, &type, &size);
-       if (!used)
-               return -1;
-       if (type == OBJ_DELTA)
-               used += 20; /* skip base object name */
-       data += used;
-       len -= used;
-       return check_inflate(data, len, entry->size);
-}
-
 static int revalidate_loose_object(struct object_entry *entry,
                                   unsigned char *map,
                                   unsigned long mapsize)
@@ -334,13 +339,10 @@ static unsigned long write_object(struct sha1file *f,
        enum object_type obj_type;
        int to_reuse = 0;
 
-       if (entry->preferred_base)
-               return 0;
-
        obj_type = entry->type;
        if (! entry->in_pack)
                to_reuse = 0;   /* can't reuse what we don't have */
-       else if (obj_type == OBJ_DELTA)
+       else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
                to_reuse = 1;   /* check_object() decided it for us */
        else if (obj_type != entry->in_pack_type)
                to_reuse = 0;   /* pack has delta which is unusable */
@@ -380,18 +382,35 @@ static unsigned long write_object(struct sha1file *f,
                if (entry->delta) {
                        buf = delta_against(buf, size, entry);
                        size = entry->delta_size;
-                       obj_type = OBJ_DELTA;
+                       obj_type = (allow_ofs_delta && entry->delta->offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
                }
                /*
                 * The object header is a byte of 'type' followed by zero or
-                * more bytes of length.  For deltas, the 20 bytes of delta
-                * sha1 follows that.
+                * more bytes of length.
                 */
                hdrlen = encode_header(obj_type, size, header);
                sha1write(f, header, hdrlen);
 
-               if (entry->delta) {
-                       sha1write(f, entry->delta, 20);
+               if (obj_type == OBJ_OFS_DELTA) {
+                       /*
+                        * Deltas with relative base contain an additional
+                        * encoding of the relative offset for the delta
+                        * base from this object's position in the pack.
+                        */
+                       unsigned long ofs = entry->offset - entry->delta->offset;
+                       unsigned pos = sizeof(header) - 1;
+                       header[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               header[--pos] = 128 | (--ofs & 127);
+                       sha1write(f, header + pos, sizeof(header) - pos);
+                       hdrlen += sizeof(header) - pos;
+               } else if (obj_type == OBJ_REF_DELTA) {
+                       /*
+                        * Deltas with a base reference contain
+                        * an additional 20 bytes for the base sha1.
+                        */
+                       sha1write(f, entry->delta->sha1, 20);
                        hdrlen += 20;
                }
                datalen = sha1write_compressed(f, buf, size);
@@ -399,21 +418,40 @@ static unsigned long write_object(struct sha1file *f,
        }
        else {
                struct packed_git *p = entry->in_pack;
-               use_packed_git(p);
 
-               datalen = find_packed_object_size(p, entry->in_pack_offset);
-               buf = (char *) p->pack_base + entry->in_pack_offset;
+               if (entry->delta) {
+                       obj_type = (allow_ofs_delta && entry->delta->offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
+                       reused_delta++;
+               }
+               hdrlen = encode_header(obj_type, entry->size, header);
+               sha1write(f, header, hdrlen);
+               if (obj_type == OBJ_OFS_DELTA) {
+                       unsigned long ofs = entry->offset - entry->delta->offset;
+                       unsigned pos = sizeof(header) - 1;
+                       header[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               header[--pos] = 128 | (--ofs & 127);
+                       sha1write(f, header + pos, sizeof(header) - pos);
+                       hdrlen += sizeof(header) - pos;
+               } else if (obj_type == OBJ_REF_DELTA) {
+                       sha1write(f, entry->delta->sha1, 20);
+                       hdrlen += 20;
+               }
 
-               if (revalidate_pack_entry(entry, buf, datalen))
+               use_packed_git(p);
+               buf = (char *) p->pack_base
+                       + entry->in_pack_offset
+                       + entry->in_pack_header_size;
+               datalen = find_packed_object_size(p, entry->in_pack_offset)
+                               - entry->in_pack_header_size;
+               if (!pack_to_stdout && check_inflate(buf, datalen, entry->size))
                        die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
                sha1write(f, buf, datalen);
                unuse_packed_git(p);
-               hdrlen = 0; /* not really */
-               if (obj_type == OBJ_DELTA)
-                       reused_delta++;
                reused++;
        }
-       if (obj_type == OBJ_DELTA)
+       if (entry->delta)
                written_delta++;
        written++;
        return hdrlen + datalen;
@@ -423,17 +461,16 @@ static unsigned long write_one(struct sha1file *f,
                               struct object_entry *e,
                               unsigned long offset)
 {
-       if (e->offset)
+       if (e->offset || e->preferred_base)
                /* offset starts from header size and cannot be zero
                 * if it is written already.
                 */
                return offset;
-       e->offset = offset;
-       offset += write_object(f, e);
-       /* if we are deltified, write out its base object. */
+       /* if we are deltified, write out its base object first. */
        if (e->delta)
                offset = write_one(f, e->delta, offset);
-       return offset;
+       e->offset = offset;
+       return offset + write_object(f, e);
 }
 
 static void write_pack_file(void)
@@ -443,15 +480,15 @@ static void write_pack_file(void)
        unsigned long offset;
        struct pack_header hdr;
        unsigned last_percent = 999;
-       int do_progress = 0;
+       int do_progress = progress;
 
-       if (!base_name)
+       if (!base_name) {
                f = sha1fd(1, "<stdout>");
-       else {
+               do_progress >>= 1;
+       }
+       else
                f = sha1create("%s-%s.%s", base_name,
                               sha1_to_hex(object_list_sha1), "pack");
-               do_progress = progress;
-       }
        if (do_progress)
                fprintf(stderr, "Writing %d objects.\n", nr_result);
 
@@ -899,26 +936,64 @@ static void check_object(struct object_entry *entry)
        char type[20];
 
        if (entry->in_pack && !entry->preferred_base) {
-               unsigned char base[20];
-               unsigned long size;
-               struct object_entry *base_entry;
+               struct packed_git *p = entry->in_pack;
+               unsigned long left = p->pack_size - entry->in_pack_offset;
+               unsigned long size, used;
+               unsigned char *buf;
+               struct object_entry *base_entry = NULL;
+
+               use_packed_git(p);
+               buf = p->pack_base;
+               buf += entry->in_pack_offset;
 
                /* We want in_pack_type even if we do not reuse delta.
                 * There is no point not reusing non-delta representations.
                 */
-               check_reuse_pack_delta(entry->in_pack,
-                                      entry->in_pack_offset,
-                                      base, &size,
-                                      &entry->in_pack_type);
+               used = unpack_object_header_gently(buf, left,
+                                                  &entry->in_pack_type, &size);
+               if (!used || left - used <= 20)
+                       die("corrupt pack for %s", sha1_to_hex(entry->sha1));
 
                /* Check if it is delta, and the base is also an object
                 * we are going to pack.  If so we will reuse the existing
                 * delta.
                 */
-               if (!no_reuse_delta &&
-                   entry->in_pack_type == OBJ_DELTA &&
-                   (base_entry = locate_object_entry(base)) &&
-                   (!base_entry->preferred_base)) {
+               if (!no_reuse_delta) {
+                       unsigned char c, *base_name;
+                       unsigned long ofs;
+                       /* there is at least 20 bytes left in the pack */
+                       switch (entry->in_pack_type) {
+                       case OBJ_REF_DELTA:
+                               base_name = buf + used;
+                               used += 20;
+                               break;
+                       case OBJ_OFS_DELTA:
+                               c = buf[used++];
+                               ofs = c & 127;
+                               while (c & 128) {
+                                       ofs += 1;
+                                       if (!ofs || ofs & ~(~0UL >> 7))
+                                               die("delta base offset overflow in pack for %s",
+                                                   sha1_to_hex(entry->sha1));
+                                       c = buf[used++];
+                                       ofs = (ofs << 7) + (c & 127);
+                               }
+                               if (ofs >= entry->in_pack_offset)
+                                       die("delta base offset out of bound for %s",
+                                           sha1_to_hex(entry->sha1));
+                               ofs = entry->in_pack_offset - ofs;
+                               base_name = find_packed_object_name(p, ofs);
+                               break;
+                       default:
+                               base_name = NULL;
+                       }
+                       if (base_name)
+                               base_entry = locate_object_entry(base_name);
+               }
+               unuse_packed_git(p);
+               entry->in_pack_header_size = used;
+
+               if (base_entry) {
 
                        /* Depth value does not matter - find_deltas()
                         * will never consider reused delta as the
@@ -927,9 +1002,9 @@ static void check_object(struct object_entry *entry)
                         */
 
                        /* uncompressed size of the delta data */
-                       entry->size = entry->delta_size = size;
+                       entry->size = size;
                        entry->delta = base_entry;
-                       entry->type = OBJ_DELTA;
+                       entry->type = entry->in_pack_type;
 
                        entry->delta_sibling = base_entry->delta_child;
                        base_entry->delta_child = entry;
@@ -1450,10 +1525,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        local = 1;
                        continue;
                }
-               if (!strcmp("--progress", arg)) {
-                       progress = 1;
-                       continue;
-               }
                if (!strcmp("--incremental", arg)) {
                        incremental = 1;
                        continue;
@@ -1476,6 +1547,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        progress = 1;
                        continue;
                }
+               if (!strcmp("--all-progress", arg)) {
+                       progress = 2;
+                       continue;
+               }
                if (!strcmp("-q", arg)) {
                        progress = 0;
                        continue;
@@ -1484,6 +1559,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        no_reuse_delta = 1;
                        continue;
                }
+               if (!strcmp("--delta-base-offset", arg)) {
+                       allow_ofs_delta = 1;
+                       continue;
+               }
                if (!strcmp("--stdout", arg)) {
                        pack_to_stdout = 1;
                        continue;
@@ -1567,7 +1646,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        else {
                if (nr_result)
                        prepare_pack(window, depth);
-               if (progress && pack_to_stdout) {
+               if (progress == 1 && pack_to_stdout) {
                        /* the other end usually displays progress itself */
                        struct itimerval v = {{0,},};
                        setitimer(ITIMER_REAL, &v, NULL);
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
new file mode 100644 (file)
index 0000000..042d271
--- /dev/null
@@ -0,0 +1,107 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char builtin_pack_refs_usage[] =
+"git-pack-refs [--all] [--prune]";
+
+struct ref_to_prune {
+       struct ref_to_prune *next;
+       unsigned char sha1[20];
+       char name[FLEX_ARRAY];
+};
+
+struct pack_refs_cb_data {
+       int prune;
+       int all;
+       struct ref_to_prune *ref_to_prune;
+       FILE *refs_file;
+};
+
+static int do_not_prune(int flags)
+{
+       /* If it is already packed or if it is a symref,
+        * do not prune it.
+        */
+       return (flags & (REF_ISSYMREF|REF_ISPACKED));
+}
+
+static int handle_one_ref(const char *path, const unsigned char *sha1,
+                         int flags, void *cb_data)
+{
+       struct pack_refs_cb_data *cb = cb_data;
+
+       if (!cb->all && strncmp(path, "refs/tags/", 10))
+               return 0;
+       /* Do not pack the symbolic refs */
+       if (!(flags & REF_ISSYMREF))
+               fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+       if (cb->prune && !do_not_prune(flags)) {
+               int namelen = strlen(path) + 1;
+               struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
+               hashcpy(n->sha1, sha1);
+               strcpy(n->name, path);
+               n->next = cb->ref_to_prune;
+               cb->ref_to_prune = n;
+       }
+       return 0;
+}
+
+/* make sure nobody touched the ref, and unlink */
+static void prune_ref(struct ref_to_prune *r)
+{
+       struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+
+       if (lock) {
+               unlink(git_path("%s", r->name));
+               unlock_ref(lock);
+       }
+}
+
+static void prune_refs(struct ref_to_prune *r)
+{
+       while (r) {
+               prune_ref(r);
+               r = r->next;
+       }
+}
+
+static struct lock_file packed;
+
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+       int fd, i;
+       struct pack_refs_cb_data cbdata;
+
+       memset(&cbdata, 0, sizeof(cbdata));
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--prune")) {
+                       cbdata.prune = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--all")) {
+                       cbdata.all = 1;
+                       continue;
+               }
+               /* perhaps other parameters later... */
+               break;
+       }
+       if (i != argc)
+               usage(builtin_pack_refs_usage);
+
+       fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
+       cbdata.refs_file = fdopen(fd, "w");
+       if (!cbdata.refs_file)
+               die("unable to create ref-pack file structure (%s)",
+                   strerror(errno));
+       for_each_ref(handle_one_ref, &cbdata);
+       fflush(cbdata.refs_file);
+       fsync(fd);
+       fclose(cbdata.refs_file);
+       if (commit_lock_file(&packed) < 0)
+               die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+       if (cbdata.prune)
+               prune_refs(cbdata.ref_to_prune);
+       return 0;
+}
diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c
new file mode 100644 (file)
index 0000000..64999f3
--- /dev/null
@@ -0,0 +1,1889 @@
+/*
+ * Pickaxe
+ *
+ * Copyright (c) 2006, Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+
+#include <time.h>
+#include <sys/time.h>
+#include <regex.h>
+
+static char pickaxe_usage[] =
+"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
+"  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  -t, --time          Show raw timestamp (Default: off)\n"
+"  -f, --show-name     Show original filename (Default: auto)\n"
+"  -n, --show-number   Show original linenumber (Default: off)\n"
+"  -p, --porcelain     Show in a format designed for machine consumption\n"
+"  -L n,m              Process only line range n,m, counting from 1\n"
+"  -M, -C              Find line movements within and across files\n"
+"  -S revs-file        Use revisions from revs-file instead of calling git-rev-list\n";
+
+static int longest_file;
+static int longest_author;
+static int max_orig_digits;
+static int max_digits;
+static int max_score_digits;
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+/* stats */
+static int num_read_blob;
+static int num_get_patch;
+static int num_commits;
+
+#define PICKAXE_BLAME_MOVE             01
+#define PICKAXE_BLAME_COPY             02
+#define PICKAXE_BLAME_COPY_HARDER      04
+
+/*
+ * blame for a blame_entry with score lower than these thresholds
+ * is not passed to the parent using move/copy logic.
+ */
+static unsigned blame_move_score;
+static unsigned blame_copy_score;
+#define BLAME_DEFAULT_MOVE_SCORE       20
+#define BLAME_DEFAULT_COPY_SCORE       40
+
+/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
+#define METAINFO_SHOWN         (1u<<12)
+#define MORE_THAN_ONE_PATH     (1u<<13)
+
+/*
+ * One blob in a commit that is being suspected
+ */
+struct origin {
+       int refcnt;
+       struct commit *commit;
+       mmfile_t file;
+       unsigned char blob_sha1[20];
+       char path[FLEX_ARRAY];
+};
+
+static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+{
+       if (!o->file.ptr) {
+               char type[10];
+               num_read_blob++;
+               file->ptr = read_sha1_file(o->blob_sha1, type,
+                                          (unsigned long *)(&(file->size)));
+               o->file = *file;
+       }
+       else
+               *file = o->file;
+       return file->ptr;
+}
+
+static inline struct origin *origin_incref(struct origin *o)
+{
+       if (o)
+               o->refcnt++;
+       return o;
+}
+
+static void origin_decref(struct origin *o)
+{
+       if (o && --o->refcnt <= 0) {
+               if (o->file.ptr)
+                       free(o->file.ptr);
+               memset(o, 0, sizeof(*o));
+               free(o);
+       }
+}
+
+struct blame_entry {
+       struct blame_entry *prev;
+       struct blame_entry *next;
+
+       /* the first line of this group in the final image;
+        * internally all line numbers are 0 based.
+        */
+       int lno;
+
+       /* how many lines this group has */
+       int num_lines;
+
+       /* the commit that introduced this group into the final image */
+       struct origin *suspect;
+
+       /* true if the suspect is truly guilty; false while we have not
+        * checked if the group came from one of its parents.
+        */
+       char guilty;
+
+       /* the line number of the first line of this group in the
+        * suspect's file; internally all line numbers are 0 based.
+        */
+       int s_lno;
+
+       /* how significant this entry is -- cached to avoid
+        * scanning the lines over and over
+        */
+       unsigned score;
+};
+
+struct scoreboard {
+       /* the final commit (i.e. where we started digging from) */
+       struct commit *final;
+
+       const char *path;
+
+       /* the contents in the final; pointed into by buf pointers of
+        * blame_entries
+        */
+       const char *final_buf;
+       unsigned long final_buf_size;
+
+       /* linked list of blames */
+       struct blame_entry *ent;
+
+       /* look-up a line in the final buffer */
+       int num_lines;
+       int *lineno;
+};
+
+static int cmp_suspect(struct origin *a, struct origin *b)
+{
+       int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+       if (cmp)
+               return cmp;
+       return strcmp(a->path, b->path);
+}
+
+#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) )
+
+static void sanity_check_refcnt(struct scoreboard *);
+
+static void coalesce(struct scoreboard *sb)
+{
+       struct blame_entry *ent, *next;
+
+       for (ent = sb->ent; ent && (next = ent->next); ent = next) {
+               if (!cmp_suspect(ent->suspect, next->suspect) &&
+                   ent->guilty == next->guilty &&
+                   ent->s_lno + ent->num_lines == next->s_lno) {
+                       ent->num_lines += next->num_lines;
+                       ent->next = next->next;
+                       if (ent->next)
+                               ent->next->prev = ent;
+                       origin_decref(next->suspect);
+                       free(next);
+                       ent->score = 0;
+                       next = ent; /* again */
+               }
+       }
+
+       if (DEBUG) /* sanity */
+               sanity_check_refcnt(sb);
+}
+
+static struct origin *make_origin(struct commit *commit, const char *path)
+{
+       struct origin *o;
+       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+       o->commit = commit;
+       o->refcnt = 1;
+       strcpy(o->path, path);
+       return o;
+}
+
+static struct origin *get_origin(struct scoreboard *sb,
+                                struct commit *commit,
+                                const char *path)
+{
+       struct blame_entry *e;
+
+       for (e = sb->ent; e; e = e->next) {
+               if (e->suspect->commit == commit &&
+                   !strcmp(e->suspect->path, path))
+                       return origin_incref(e->suspect);
+       }
+       return make_origin(commit, path);
+}
+
+static int fill_blob_sha1(struct origin *origin)
+{
+       unsigned mode;
+       char type[10];
+
+       if (!is_null_sha1(origin->blob_sha1))
+               return 0;
+       if (get_tree_entry(origin->commit->object.sha1,
+                          origin->path,
+                          origin->blob_sha1, &mode))
+               goto error_out;
+       if (sha1_object_info(origin->blob_sha1, type, NULL) ||
+           strcmp(type, blob_type))
+               goto error_out;
+       return 0;
+ error_out:
+       hashclr(origin->blob_sha1);
+       return -1;
+}
+
+static struct origin *find_origin(struct scoreboard *sb,
+                                 struct commit *parent,
+                                 struct origin *origin)
+{
+       struct origin *porigin = NULL;
+       struct diff_options diff_opts;
+       const char *paths[2];
+
+       if (parent->util) {
+               /* This is a freestanding copy of origin and not
+                * refcounted.
+                */
+               struct origin *cached = parent->util;
+               if (!strcmp(cached->path, origin->path)) {
+                       porigin = get_origin(sb, parent, cached->path);
+                       if (porigin->refcnt == 1)
+                               hashcpy(porigin->blob_sha1, cached->blob_sha1);
+                       return porigin;
+               }
+               /* otherwise it was not very useful; free it */
+               free(parent->util);
+               parent->util = NULL;
+       }
+
+       /* See if the origin->path is different between parent
+        * and origin first.  Most of the time they are the
+        * same and diff-tree is fairly efficient about this.
+        */
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.detect_rename = 0;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       paths[0] = origin->path;
+       paths[1] = NULL;
+
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff-setup");
+       diff_tree_sha1(parent->tree->object.sha1,
+                      origin->commit->tree->object.sha1,
+                      "", &diff_opts);
+       diffcore_std(&diff_opts);
+
+       /* It is either one entry that says "modified", or "created",
+        * or nothing.
+        */
+       if (!diff_queued_diff.nr) {
+               /* The path is the same as parent */
+               porigin = get_origin(sb, parent, origin->path);
+               hashcpy(porigin->blob_sha1, origin->blob_sha1);
+       }
+       else if (diff_queued_diff.nr != 1)
+               die("internal error in pickaxe::find_origin");
+       else {
+               struct diff_filepair *p = diff_queued_diff.queue[0];
+               switch (p->status) {
+               default:
+                       die("internal error in pickaxe::find_origin (%c)",
+                           p->status);
+               case 'M':
+                       porigin = get_origin(sb, parent, origin->path);
+                       hashcpy(porigin->blob_sha1, p->one->sha1);
+                       break;
+               case 'A':
+               case 'T':
+                       /* Did not exist in parent, or type changed */
+                       break;
+               }
+       }
+       diff_flush(&diff_opts);
+       if (porigin) {
+               struct origin *cached;
+               cached = make_origin(porigin->commit, porigin->path);
+               hashcpy(cached->blob_sha1, porigin->blob_sha1);
+               parent->util = cached;
+       }
+       return porigin;
+}
+
+static struct origin *find_rename(struct scoreboard *sb,
+                                 struct commit *parent,
+                                 struct origin *origin)
+{
+       struct origin *porigin = NULL;
+       struct diff_options diff_opts;
+       int i;
+       const char *paths[2];
+
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_opts.single_follow = origin->path;
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff-setup");
+       diff_tree_sha1(parent->tree->object.sha1,
+                      origin->commit->tree->object.sha1,
+                      "", &diff_opts);
+       diffcore_std(&diff_opts);
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+               if ((p->status == 'R' || p->status == 'C') &&
+                   !strcmp(p->two->path, origin->path)) {
+                       porigin = get_origin(sb, parent, p->one->path);
+                       hashcpy(porigin->blob_sha1, p->one->sha1);
+                       break;
+               }
+       }
+       diff_flush(&diff_opts);
+       return porigin;
+}
+
+struct chunk {
+       /* line number in postimage; up to but not including this
+        * line is the same as preimage
+        */
+       int same;
+
+       /* preimage line number after this chunk */
+       int p_next;
+
+       /* postimage line number after this chunk */
+       int t_next;
+};
+
+struct patch {
+       struct chunk *chunks;
+       int num;
+};
+
+struct blame_diff_state {
+       struct xdiff_emit_state xm;
+       struct patch *ret;
+       unsigned hunk_post_context;
+       unsigned hunk_in_pre_context : 1;
+};
+
+static void process_u_diff(void *state_, char *line, unsigned long len)
+{
+       struct blame_diff_state *state = state_;
+       struct chunk *chunk;
+       int off1, off2, len1, len2, num;
+
+       num = state->ret->num;
+       if (len < 4 || line[0] != '@' || line[1] != '@') {
+               if (state->hunk_in_pre_context && line[0] == ' ')
+                       state->ret->chunks[num - 1].same++;
+               else {
+                       state->hunk_in_pre_context = 0;
+                       if (line[0] == ' ')
+                               state->hunk_post_context++;
+                       else
+                               state->hunk_post_context = 0;
+               }
+               return;
+       }
+
+       if (num && state->hunk_post_context) {
+               chunk = &state->ret->chunks[num - 1];
+               chunk->p_next -= state->hunk_post_context;
+               chunk->t_next -= state->hunk_post_context;
+       }
+       state->ret->num = ++num;
+       state->ret->chunks = xrealloc(state->ret->chunks,
+                                     sizeof(struct chunk) * num);
+       chunk = &state->ret->chunks[num - 1];
+       if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) {
+               state->ret->num--;
+               return;
+       }
+
+       /* Line numbers in patch output are one based. */
+       off1--;
+       off2--;
+
+       chunk->same = len2 ? off2 : (off2 + 1);
+
+       chunk->p_next = off1 + (len1 ? len1 : 1);
+       chunk->t_next = chunk->same + len2;
+       state->hunk_in_pre_context = 1;
+       state->hunk_post_context = 0;
+}
+
+static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
+                                   int context)
+{
+       struct blame_diff_state state;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = context;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
+       memset(&state, 0, sizeof(state));
+       state.xm.consume = process_u_diff;
+       state.ret = xmalloc(sizeof(struct patch));
+       state.ret->chunks = NULL;
+       state.ret->num = 0;
+
+       xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb);
+
+       if (state.ret->num) {
+               struct chunk *chunk;
+               chunk = &state.ret->chunks[state.ret->num - 1];
+               chunk->p_next -= state.hunk_post_context;
+               chunk->t_next -= state.hunk_post_context;
+       }
+       return state.ret;
+}
+
+static struct patch *get_patch(struct origin *parent, struct origin *origin)
+{
+       mmfile_t file_p, file_o;
+       struct patch *patch;
+
+       fill_origin_blob(parent, &file_p);
+       fill_origin_blob(origin, &file_o);
+       if (!file_p.ptr || !file_o.ptr)
+               return NULL;
+       patch = compare_buffer(&file_p, &file_o, 0);
+       num_get_patch++;
+       return patch;
+}
+
+static void free_patch(struct patch *p)
+{
+       free(p->chunks);
+       free(p);
+}
+
+static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
+{
+       struct blame_entry *ent, *prev = NULL;
+
+       origin_incref(e->suspect);
+
+       for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
+               prev = ent;
+
+       /* prev, if not NULL, is the last one that is below e */
+       e->prev = prev;
+       if (prev) {
+               e->next = prev->next;
+               prev->next = e;
+       }
+       else {
+               e->next = sb->ent;
+               sb->ent = e;
+       }
+       if (e->next)
+               e->next->prev = e;
+}
+
+static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
+{
+       struct blame_entry *p, *n;
+
+       p = dst->prev;
+       n = dst->next;
+       origin_incref(src->suspect);
+       origin_decref(dst->suspect);
+       memcpy(dst, src, sizeof(*src));
+       dst->prev = p;
+       dst->next = n;
+       dst->score = 0;
+}
+
+static const char *nth_line(struct scoreboard *sb, int lno)
+{
+       return sb->final_buf + sb->lineno[lno];
+}
+
+static void split_overlap(struct blame_entry *split,
+                         struct blame_entry *e,
+                         int tlno, int plno, int same,
+                         struct origin *parent)
+{
+       /* it is known that lines between tlno to same came from
+        * parent, and e has an overlap with that range.  it also is
+        * known that parent's line plno corresponds to e's line tlno.
+        *
+        *                <---- e ----->
+        *                   <------>
+        *                   <------------>
+        *             <------------>
+        *             <------------------>
+        *
+        * Potentially we need to split e into three parts; before
+        * this chunk, the chunk to be blamed for parent, and after
+        * that portion.
+        */
+       int chunk_end_lno;
+       memset(split, 0, sizeof(struct blame_entry [3]));
+
+       if (e->s_lno < tlno) {
+               /* there is a pre-chunk part not blamed on parent */
+               split[0].suspect = origin_incref(e->suspect);
+               split[0].lno = e->lno;
+               split[0].s_lno = e->s_lno;
+               split[0].num_lines = tlno - e->s_lno;
+               split[1].lno = e->lno + tlno - e->s_lno;
+               split[1].s_lno = plno;
+       }
+       else {
+               split[1].lno = e->lno;
+               split[1].s_lno = plno + (e->s_lno - tlno);
+       }
+
+       if (same < e->s_lno + e->num_lines) {
+               /* there is a post-chunk part not blamed on parent */
+               split[2].suspect = origin_incref(e->suspect);
+               split[2].lno = e->lno + (same - e->s_lno);
+               split[2].s_lno = e->s_lno + (same - e->s_lno);
+               split[2].num_lines = e->s_lno + e->num_lines - same;
+               chunk_end_lno = split[2].lno;
+       }
+       else
+               chunk_end_lno = e->lno + e->num_lines;
+       split[1].num_lines = chunk_end_lno - split[1].lno;
+
+       if (split[1].num_lines < 1)
+               return;
+       split[1].suspect = origin_incref(parent);
+}
+
+static void split_blame(struct scoreboard *sb,
+                       struct blame_entry *split,
+                       struct blame_entry *e)
+{
+       struct blame_entry *new_entry;
+
+       if (split[0].suspect && split[2].suspect) {
+               /* we need to split e into two and add another for parent */
+               dup_entry(e, &split[0]);
+
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+       }
+       else if (!split[0].suspect && !split[2].suspect)
+               /* parent covers the entire area */
+               dup_entry(e, &split[1]);
+       else if (split[0].suspect) {
+               dup_entry(e, &split[0]);
+
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+       }
+       else {
+               dup_entry(e, &split[1]);
+
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+       }
+
+       if (DEBUG) { /* sanity */
+               struct blame_entry *ent;
+               int lno = sb->ent->lno, corrupt = 0;
+
+               for (ent = sb->ent; ent; ent = ent->next) {
+                       if (lno != ent->lno)
+                               corrupt = 1;
+                       if (ent->s_lno < 0)
+                               corrupt = 1;
+                       lno += ent->num_lines;
+               }
+               if (corrupt) {
+                       lno = sb->ent->lno;
+                       for (ent = sb->ent; ent; ent = ent->next) {
+                               printf("L %8d l %8d n %8d\n",
+                                      lno, ent->lno, ent->num_lines);
+                               lno = ent->lno + ent->num_lines;
+                       }
+                       die("oops");
+               }
+       }
+}
+
+static void decref_split(struct blame_entry *split)
+{
+       int i;
+
+       for (i = 0; i < 3; i++)
+               origin_decref(split[i].suspect);
+}
+
+static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
+                         int tlno, int plno, int same,
+                         struct origin *parent)
+{
+       struct blame_entry split[3];
+
+       split_overlap(split, e, tlno, plno, same, parent);
+       if (split[1].suspect)
+               split_blame(sb, split, e);
+       decref_split(split);
+}
+
+static int find_last_in_target(struct scoreboard *sb, struct origin *target)
+{
+       struct blame_entry *e;
+       int last_in_target = -1;
+
+       for (e = sb->ent; e; e = e->next) {
+               if (e->guilty || cmp_suspect(e->suspect, target))
+                       continue;
+               if (last_in_target < e->s_lno + e->num_lines)
+                       last_in_target = e->s_lno + e->num_lines;
+       }
+       return last_in_target;
+}
+
+static void blame_chunk(struct scoreboard *sb,
+                       int tlno, int plno, int same,
+                       struct origin *target, struct origin *parent)
+{
+       struct blame_entry *e;
+
+       for (e = sb->ent; e; e = e->next) {
+               if (e->guilty || cmp_suspect(e->suspect, target))
+                       continue;
+               if (same <= e->s_lno)
+                       continue;
+               if (tlno < e->s_lno + e->num_lines)
+                       blame_overlap(sb, e, tlno, plno, same, parent);
+       }
+}
+
+static int pass_blame_to_parent(struct scoreboard *sb,
+                               struct origin *target,
+                               struct origin *parent)
+{
+       int i, last_in_target, plno, tlno;
+       struct patch *patch;
+
+       last_in_target = find_last_in_target(sb, target);
+       if (last_in_target < 0)
+               return 1; /* nothing remains for this target */
+
+       patch = get_patch(parent, target);
+       plno = tlno = 0;
+       for (i = 0; i < patch->num; i++) {
+               struct chunk *chunk = &patch->chunks[i];
+
+               blame_chunk(sb, tlno, plno, chunk->same, target, parent);
+               plno = chunk->p_next;
+               tlno = chunk->t_next;
+       }
+       /* rest (i.e. anything above tlno) are the same as parent */
+       blame_chunk(sb, tlno, plno, last_in_target, target, parent);
+
+       free_patch(patch);
+       return 0;
+}
+
+static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
+{
+       unsigned score;
+       const char *cp, *ep;
+
+       if (e->score)
+               return e->score;
+
+       score = 1;
+       cp = nth_line(sb, e->lno);
+       ep = nth_line(sb, e->lno + e->num_lines);
+       while (cp < ep) {
+               unsigned ch = *((unsigned char *)cp);
+               if (isalnum(ch))
+                       score++;
+               cp++;
+       }
+       e->score = score;
+       return score;
+}
+
+static void copy_split_if_better(struct scoreboard *sb,
+                                struct blame_entry *best_so_far,
+                                struct blame_entry *this)
+{
+       int i;
+
+       if (!this[1].suspect)
+               return;
+       if (best_so_far[1].suspect) {
+               if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
+                       return;
+       }
+
+       for (i = 0; i < 3; i++)
+               origin_incref(this[i].suspect);
+       decref_split(best_so_far);
+       memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
+}
+
+static void find_copy_in_blob(struct scoreboard *sb,
+                             struct blame_entry *ent,
+                             struct origin *parent,
+                             struct blame_entry *split,
+                             mmfile_t *file_p)
+{
+       const char *cp;
+       int cnt;
+       mmfile_t file_o;
+       struct patch *patch;
+       int i, plno, tlno;
+
+       cp = nth_line(sb, ent->lno);
+       file_o.ptr = (char*) cp;
+       cnt = ent->num_lines;
+
+       while (cnt && cp < sb->final_buf + sb->final_buf_size) {
+               if (*cp++ == '\n')
+                       cnt--;
+       }
+       file_o.size = cp - file_o.ptr;
+
+       patch = compare_buffer(file_p, &file_o, 1);
+
+       memset(split, 0, sizeof(struct blame_entry [3]));
+       plno = tlno = 0;
+       for (i = 0; i < patch->num; i++) {
+               struct chunk *chunk = &patch->chunks[i];
+
+               /* tlno to chunk->same are the same as ent */
+               if (ent->num_lines <= tlno)
+                       break;
+               if (tlno < chunk->same) {
+                       struct blame_entry this[3];
+                       split_overlap(this, ent,
+                                     tlno + ent->s_lno, plno,
+                                     chunk->same + ent->s_lno,
+                                     parent);
+                       copy_split_if_better(sb, split, this);
+                       decref_split(this);
+               }
+               plno = chunk->p_next;
+               tlno = chunk->t_next;
+       }
+       free_patch(patch);
+}
+
+static int find_move_in_parent(struct scoreboard *sb,
+                              struct origin *target,
+                              struct origin *parent)
+{
+       int last_in_target, made_progress;
+       struct blame_entry *e, split[3];
+       mmfile_t file_p;
+
+       last_in_target = find_last_in_target(sb, target);
+       if (last_in_target < 0)
+               return 1; /* nothing remains for this target */
+
+       fill_origin_blob(parent, &file_p);
+       if (!file_p.ptr)
+               return 0;
+
+       made_progress = 1;
+       while (made_progress) {
+               made_progress = 0;
+               for (e = sb->ent; e; e = e->next) {
+                       if (e->guilty || cmp_suspect(e->suspect, target))
+                               continue;
+                       find_copy_in_blob(sb, e, parent, split, &file_p);
+                       if (split[1].suspect &&
+                           blame_move_score < ent_score(sb, &split[1])) {
+                               split_blame(sb, split, e);
+                               made_progress = 1;
+                       }
+                       decref_split(split);
+               }
+       }
+       return 0;
+}
+
+
+struct blame_list {
+       struct blame_entry *ent;
+       struct blame_entry split[3];
+};
+
+static struct blame_list *setup_blame_list(struct scoreboard *sb,
+                                          struct origin *target,
+                                          int *num_ents_p)
+{
+       struct blame_entry *e;
+       int num_ents, i;
+       struct blame_list *blame_list = NULL;
+
+       /* Count the number of entries the target is suspected for,
+        * and prepare a list of entry and the best split.
+        */
+       for (e = sb->ent, num_ents = 0; e; e = e->next)
+               if (!e->guilty && !cmp_suspect(e->suspect, target))
+                       num_ents++;
+       if (num_ents) {
+               blame_list = xcalloc(num_ents, sizeof(struct blame_list));
+               for (e = sb->ent, i = 0; e; e = e->next)
+                       if (!e->guilty && !cmp_suspect(e->suspect, target))
+                               blame_list[i++].ent = e;
+       }
+       *num_ents_p = num_ents;
+       return blame_list;
+}
+
+static int find_copy_in_parent(struct scoreboard *sb,
+                              struct origin *target,
+                              struct commit *parent,
+                              struct origin *porigin,
+                              int opt)
+{
+       struct diff_options diff_opts;
+       const char *paths[1];
+       int i, j;
+       int retval;
+       struct blame_list *blame_list;
+       int num_ents;
+
+       blame_list = setup_blame_list(sb, target, &num_ents);
+       if (!blame_list)
+               return 1; /* nothing remains for this target */
+
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff-setup");
+
+       /* Try "find copies harder" on new path if requested;
+        * we do not want to use diffcore_rename() actually to
+        * match things up; find_copies_harder is set only to
+        * force diff_tree_sha1() to feed all filepairs to diff_queue,
+        * and this code needs to be after diff_setup_done(), which
+        * usually makes find-copies-harder imply copy detection.
+        */
+       if ((opt & PICKAXE_BLAME_COPY_HARDER) &&
+           (!porigin || strcmp(target->path, porigin->path)))
+               diff_opts.find_copies_harder = 1;
+
+       diff_tree_sha1(parent->tree->object.sha1,
+                      target->commit->tree->object.sha1,
+                      "", &diff_opts);
+
+       if (!diff_opts.find_copies_harder)
+               diffcore_std(&diff_opts);
+
+       retval = 0;
+       while (1) {
+               int made_progress = 0;
+
+               for (i = 0; i < diff_queued_diff.nr; i++) {
+                       struct diff_filepair *p = diff_queued_diff.queue[i];
+                       struct origin *norigin;
+                       mmfile_t file_p;
+                       struct blame_entry this[3];
+
+                       if (!DIFF_FILE_VALID(p->one))
+                               continue; /* does not exist in parent */
+                       if (porigin && !strcmp(p->one->path, porigin->path))
+                               /* find_move already dealt with this path */
+                               continue;
+
+                       norigin = get_origin(sb, parent, p->one->path);
+                       hashcpy(norigin->blob_sha1, p->one->sha1);
+                       fill_origin_blob(norigin, &file_p);
+                       if (!file_p.ptr)
+                               continue;
+
+                       for (j = 0; j < num_ents; j++) {
+                               find_copy_in_blob(sb, blame_list[j].ent,
+                                                 norigin, this, &file_p);
+                               copy_split_if_better(sb, blame_list[j].split,
+                                                    this);
+                               decref_split(this);
+                       }
+                       origin_decref(norigin);
+               }
+
+               for (j = 0; j < num_ents; j++) {
+                       struct blame_entry *split = blame_list[j].split;
+                       if (split[1].suspect &&
+                           blame_copy_score < ent_score(sb, &split[1])) {
+                               split_blame(sb, split, blame_list[j].ent);
+                               made_progress = 1;
+                       }
+                       decref_split(split);
+               }
+               free(blame_list);
+
+               if (!made_progress)
+                       break;
+               blame_list = setup_blame_list(sb, target, &num_ents);
+               if (!blame_list) {
+                       retval = 1;
+                       break;
+               }
+       }
+       diff_flush(&diff_opts);
+
+       return retval;
+}
+
+/* The blobs of origin and porigin exactly match, so everything
+ * origin is suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(struct scoreboard *sb,
+                            struct origin *origin, struct origin *porigin)
+{
+       struct blame_entry *e;
+
+       if (!porigin->file.ptr && origin->file.ptr) {
+               /* Steal its file */
+               porigin->file = origin->file;
+               origin->file.ptr = NULL;
+       }
+       for (e = sb->ent; e; e = e->next) {
+               if (cmp_suspect(e->suspect, origin))
+                       continue;
+               origin_incref(porigin);
+               origin_decref(e->suspect);
+               e->suspect = porigin;
+       }
+}
+
+#define MAXPARENT 16
+
+static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
+{
+       int i, pass;
+       struct commit *commit = origin->commit;
+       struct commit_list *parent;
+       struct origin *parent_origin[MAXPARENT], *porigin;
+
+       memset(parent_origin, 0, sizeof(parent_origin));
+
+       /* The first pass looks for unrenamed path to optimize for
+        * common cases, then we look for renames in the second pass.
+        */
+       for (pass = 0; pass < 2; pass++) {
+               struct origin *(*find)(struct scoreboard *,
+                                      struct commit *, struct origin *);
+               find = pass ? find_rename : find_origin;
+
+               for (i = 0, parent = commit->parents;
+                    i < MAXPARENT && parent;
+                    parent = parent->next, i++) {
+                       struct commit *p = parent->item;
+                       int j, same;
+
+                       if (parent_origin[i])
+                               continue;
+                       if (parse_commit(p))
+                               continue;
+                       porigin = find(sb, p, origin);
+                       if (!porigin)
+                               continue;
+                       if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
+                               pass_whole_blame(sb, origin, porigin);
+                               origin_decref(porigin);
+                               goto finish;
+                       }
+                       for (j = same = 0; j < i; j++)
+                               if (parent_origin[j] &&
+                                   !hashcmp(parent_origin[j]->blob_sha1,
+                                            porigin->blob_sha1)) {
+                                       same = 1;
+                                       break;
+                               }
+                       if (!same)
+                               parent_origin[i] = porigin;
+                       else
+                               origin_decref(porigin);
+               }
+       }
+
+       num_commits++;
+       for (i = 0, parent = commit->parents;
+            i < MAXPARENT && parent;
+            parent = parent->next, i++) {
+               struct origin *porigin = parent_origin[i];
+               if (!porigin)
+                       continue;
+               if (pass_blame_to_parent(sb, origin, porigin))
+                       goto finish;
+       }
+
+       /*
+        * Optionally run "miff" to find moves in parents' files here.
+        */
+       if (opt & PICKAXE_BLAME_MOVE)
+               for (i = 0, parent = commit->parents;
+                    i < MAXPARENT && parent;
+                    parent = parent->next, i++) {
+                       struct origin *porigin = parent_origin[i];
+                       if (!porigin)
+                               continue;
+                       if (find_move_in_parent(sb, origin, porigin))
+                               goto finish;
+               }
+
+       /*
+        * Optionally run "ciff" to find copies from parents' files here.
+        */
+       if (opt & PICKAXE_BLAME_COPY)
+               for (i = 0, parent = commit->parents;
+                    i < MAXPARENT && parent;
+                    parent = parent->next, i++) {
+                       struct origin *porigin = parent_origin[i];
+                       if (find_copy_in_parent(sb, origin, parent->item,
+                                               porigin, opt))
+                               goto finish;
+               }
+
+ finish:
+       for (i = 0; i < MAXPARENT; i++)
+               origin_decref(parent_origin[i]);
+}
+
+static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+{
+       while (1) {
+               struct blame_entry *ent;
+               struct commit *commit;
+               struct origin *suspect = NULL;
+
+               /* find one suspect to break down */
+               for (ent = sb->ent; !suspect && ent; ent = ent->next)
+                       if (!ent->guilty)
+                               suspect = ent->suspect;
+               if (!suspect)
+                       return; /* all done */
+
+               origin_incref(suspect);
+               commit = suspect->commit;
+               if (!commit->object.parsed)
+                       parse_commit(commit);
+               if (!(commit->object.flags & UNINTERESTING) &&
+                   !(revs->max_age != -1 && commit->date  < revs->max_age))
+                       pass_blame(sb, suspect, opt);
+
+               /* Take responsibility for the remaining entries */
+               for (ent = sb->ent; ent; ent = ent->next)
+                       if (!cmp_suspect(ent->suspect, suspect))
+                               ent->guilty = 1;
+               origin_decref(suspect);
+
+               if (DEBUG) /* sanity */
+                       sanity_check_refcnt(sb);
+       }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+                              int show_raw_time)
+{
+       static char time_buf[128];
+       time_t t = time;
+       int minutes, tz;
+       struct tm *tm;
+
+       if (show_raw_time) {
+               sprintf(time_buf, "%lu %s", time, tz_str);
+               return time_buf;
+       }
+
+       tz = atoi(tz_str);
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+
+       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+       strcat(time_buf, tz_str);
+       return time_buf;
+}
+
+struct commit_info
+{
+       char *author;
+       char *author_mail;
+       unsigned long author_time;
+       char *author_tz;
+
+       /* filled only when asked for details */
+       char *committer;
+       char *committer_mail;
+       unsigned long committer_time;
+       char *committer_tz;
+
+       char *summary;
+};
+
+static void get_ac_line(const char *inbuf, const char *what,
+                       int bufsz, char *person, char **mail,
+                       unsigned long *time, char **tz)
+{
+       int len;
+       char *tmp, *endp;
+
+       tmp = strstr(inbuf, what);
+       if (!tmp)
+               goto error_out;
+       tmp += strlen(what);
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               len = strlen(tmp);
+       else
+               len = endp - tmp;
+       if (bufsz <= len) {
+       error_out:
+               /* Ugh */
+               person = *mail = *tz = "(unknown)";
+               *time = 0;
+               return;
+       }
+       memcpy(person, tmp, len);
+
+       tmp = person;
+       tmp += len;
+       *tmp = 0;
+       while (*tmp != ' ')
+               tmp--;
+       *tz = tmp+1;
+
+       *tmp = 0;
+       while (*tmp != ' ')
+               tmp--;
+       *time = strtoul(tmp, NULL, 10);
+
+       *tmp = 0;
+       while (*tmp != ' ')
+               tmp--;
+       *mail = tmp + 1;
+       *tmp = 0;
+}
+
+static void get_commit_info(struct commit *commit,
+                           struct commit_info *ret,
+                           int detailed)
+{
+       int len;
+       char *tmp, *endp;
+       static char author_buf[1024];
+       static char committer_buf[1024];
+       static char summary_buf[1024];
+
+       /* We've operated without save_commit_buffer, so
+        * we now need to populate them for output.
+        */
+       if (!commit->buffer) {
+               char type[20];
+               unsigned long size;
+               commit->buffer =
+                       read_sha1_file(commit->object.sha1, type, &size);
+       }
+       ret->author = author_buf;
+       get_ac_line(commit->buffer, "\nauthor ",
+                   sizeof(author_buf), author_buf, &ret->author_mail,
+                   &ret->author_time, &ret->author_tz);
+
+       if (!detailed)
+               return;
+
+       ret->committer = committer_buf;
+       get_ac_line(commit->buffer, "\ncommitter ",
+                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+                   &ret->committer_time, &ret->committer_tz);
+
+       ret->summary = summary_buf;
+       tmp = strstr(commit->buffer, "\n\n");
+       if (!tmp) {
+       error_out:
+               sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+               return;
+       }
+       tmp += 2;
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               goto error_out;
+       len = endp - tmp;
+       if (len >= sizeof(summary_buf))
+               goto error_out;
+       memcpy(summary_buf, tmp, len);
+       summary_buf[len] = 0;
+}
+
+#define OUTPUT_ANNOTATE_COMPAT 001
+#define OUTPUT_LONG_OBJECT_NAME        002
+#define OUTPUT_RAW_TIMESTAMP   004
+#define OUTPUT_PORCELAIN       010
+#define OUTPUT_SHOW_NAME       020
+#define OUTPUT_SHOW_NUMBER     040
+#define OUTPUT_SHOW_SCORE      0100
+
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+{
+       int cnt;
+       const char *cp;
+       struct origin *suspect = ent->suspect;
+       char hex[41];
+
+       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       printf("%s%c%d %d %d\n",
+              hex,
+              ent->guilty ? ' ' : '*', // purely for debugging
+              ent->s_lno + 1,
+              ent->lno + 1,
+              ent->num_lines);
+       if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+               struct commit_info ci;
+               suspect->commit->object.flags |= METAINFO_SHOWN;
+               get_commit_info(suspect->commit, &ci, 1);
+               printf("author %s\n", ci.author);
+               printf("author-mail %s\n", ci.author_mail);
+               printf("author-time %lu\n", ci.author_time);
+               printf("author-tz %s\n", ci.author_tz);
+               printf("committer %s\n", ci.committer);
+               printf("committer-mail %s\n", ci.committer_mail);
+               printf("committer-time %lu\n", ci.committer_time);
+               printf("committer-tz %s\n", ci.committer_tz);
+               printf("filename %s\n", suspect->path);
+               printf("summary %s\n", ci.summary);
+       }
+       else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
+               printf("filename %s\n", suspect->path);
+
+       cp = nth_line(sb, ent->lno);
+       for (cnt = 0; cnt < ent->num_lines; cnt++) {
+               char ch;
+               if (cnt)
+                       printf("%s %d %d\n", hex,
+                              ent->s_lno + 1 + cnt,
+                              ent->lno + 1 + cnt);
+               putchar('\t');
+               do {
+                       ch = *cp++;
+                       putchar(ch);
+               } while (ch != '\n' &&
+                        cp < sb->final_buf + sb->final_buf_size);
+       }
+}
+
+static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
+{
+       int cnt;
+       const char *cp;
+       struct origin *suspect = ent->suspect;
+       struct commit_info ci;
+       char hex[41];
+       int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
+
+       get_commit_info(suspect->commit, &ci, 1);
+       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+
+       cp = nth_line(sb, ent->lno);
+       for (cnt = 0; cnt < ent->num_lines; cnt++) {
+               char ch;
+
+               printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);
+               if (opt & OUTPUT_ANNOTATE_COMPAT)
+                       printf("\t(%10s\t%10s\t%d)", ci.author,
+                              format_time(ci.author_time, ci.author_tz,
+                                          show_raw_time),
+                              ent->lno + 1 + cnt);
+               else {
+                       if (opt & OUTPUT_SHOW_SCORE)
+                               printf(" %*d %02d",
+                                      max_score_digits, ent->score,
+                                      ent->suspect->refcnt);
+                       if (opt & OUTPUT_SHOW_NAME)
+                               printf(" %-*.*s", longest_file, longest_file,
+                                      suspect->path);
+                       if (opt & OUTPUT_SHOW_NUMBER)
+                               printf(" %*d", max_orig_digits,
+                                      ent->s_lno + 1 + cnt);
+                       printf(" (%-*.*s %10s %*d) ",
+                              longest_author, longest_author, ci.author,
+                              format_time(ci.author_time, ci.author_tz,
+                                          show_raw_time),
+                              max_digits, ent->lno + 1 + cnt);
+               }
+               do {
+                       ch = *cp++;
+                       putchar(ch);
+               } while (ch != '\n' &&
+                        cp < sb->final_buf + sb->final_buf_size);
+       }
+}
+
+static void output(struct scoreboard *sb, int option)
+{
+       struct blame_entry *ent;
+
+       if (option & OUTPUT_PORCELAIN) {
+               for (ent = sb->ent; ent; ent = ent->next) {
+                       struct blame_entry *oth;
+                       struct origin *suspect = ent->suspect;
+                       struct commit *commit = suspect->commit;
+                       if (commit->object.flags & MORE_THAN_ONE_PATH)
+                               continue;
+                       for (oth = ent->next; oth; oth = oth->next) {
+                               if ((oth->suspect->commit != commit) ||
+                                   !strcmp(oth->suspect->path, suspect->path))
+                                       continue;
+                               commit->object.flags |= MORE_THAN_ONE_PATH;
+                               break;
+                       }
+               }
+       }
+
+       for (ent = sb->ent; ent; ent = ent->next) {
+               if (option & OUTPUT_PORCELAIN)
+                       emit_porcelain(sb, ent);
+               else {
+                       emit_other(sb, ent, option);
+               }
+       }
+}
+
+static int prepare_lines(struct scoreboard *sb)
+{
+       const char *buf = sb->final_buf;
+       unsigned long len = sb->final_buf_size;
+       int num = 0, incomplete = 0, bol = 1;
+
+       if (len && buf[len-1] != '\n')
+               incomplete++; /* incomplete line at the end */
+       while (len--) {
+               if (bol) {
+                       sb->lineno = xrealloc(sb->lineno,
+                                             sizeof(int* ) * (num + 1));
+                       sb->lineno[num] = buf - sb->final_buf;
+                       bol = 0;
+               }
+               if (*buf++ == '\n') {
+                       num++;
+                       bol = 1;
+               }
+       }
+       sb->lineno = xrealloc(sb->lineno,
+                             sizeof(int* ) * (num + incomplete + 1));
+       sb->lineno[num + incomplete] = buf - sb->final_buf;
+       sb->num_lines = num + incomplete;
+       return sb->num_lines;
+}
+
+static int read_ancestry(const char *graft_file)
+{
+       FILE *fp = fopen(graft_file, "r");
+       char buf[1024];
+       if (!fp)
+               return -1;
+       while (fgets(buf, sizeof(buf), fp)) {
+               /* The format is just "Commit Parent1 Parent2 ...\n" */
+               int len = strlen(buf);
+               struct commit_graft *graft = read_graft_line(buf, len);
+               register_commit_graft(graft, 0);
+       }
+       fclose(fp);
+       return 0;
+}
+
+static int lineno_width(int lines)
+{
+        int i, width;
+
+        for (width = 1, i = 10; i <= lines + 1; width++)
+                i *= 10;
+        return width;
+}
+
+static void find_alignment(struct scoreboard *sb, int *option)
+{
+       int longest_src_lines = 0;
+       int longest_dst_lines = 0;
+       unsigned largest_score = 0;
+       struct blame_entry *e;
+
+       for (e = sb->ent; e; e = e->next) {
+               struct origin *suspect = e->suspect;
+               struct commit_info ci;
+               int num;
+
+               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+                       suspect->commit->object.flags |= METAINFO_SHOWN;
+                       get_commit_info(suspect->commit, &ci, 1);
+                       if (strcmp(suspect->path, sb->path))
+                               *option |= OUTPUT_SHOW_NAME;
+                       num = strlen(suspect->path);
+                       if (longest_file < num)
+                               longest_file = num;
+                       num = strlen(ci.author);
+                       if (longest_author < num)
+                               longest_author = num;
+               }
+               num = e->s_lno + e->num_lines;
+               if (longest_src_lines < num)
+                       longest_src_lines = num;
+               num = e->lno + e->num_lines;
+               if (longest_dst_lines < num)
+                       longest_dst_lines = num;
+               if (largest_score < ent_score(sb, e))
+                       largest_score = ent_score(sb, e);
+       }
+       max_orig_digits = lineno_width(longest_src_lines);
+       max_digits = lineno_width(longest_dst_lines);
+       max_score_digits = lineno_width(largest_score);
+}
+
+static void sanity_check_refcnt(struct scoreboard *sb)
+{
+       int baa = 0;
+       struct blame_entry *ent;
+
+       for (ent = sb->ent; ent; ent = ent->next) {
+               /* Nobody should have zero or negative refcnt */
+               if (ent->suspect->refcnt <= 0) {
+                       fprintf(stderr, "%s in %s has negative refcnt %d\n",
+                               ent->suspect->path,
+                               sha1_to_hex(ent->suspect->commit->object.sha1),
+                               ent->suspect->refcnt);
+                       baa = 1;
+               }
+       }
+       for (ent = sb->ent; ent; ent = ent->next) {
+               /* Mark the ones that haven't been checked */
+               if (0 < ent->suspect->refcnt)
+                       ent->suspect->refcnt = -ent->suspect->refcnt;
+       }
+       for (ent = sb->ent; ent; ent = ent->next) {
+               /* then pick each and see if they have the the correct
+                * refcnt.
+                */
+               int found;
+               struct blame_entry *e;
+               struct origin *suspect = ent->suspect;
+
+               if (0 < suspect->refcnt)
+                       continue;
+               suspect->refcnt = -suspect->refcnt; /* Unmark */
+               for (found = 0, e = sb->ent; e; e = e->next) {
+                       if (e->suspect != suspect)
+                               continue;
+                       found++;
+               }
+               if (suspect->refcnt != found) {
+                       fprintf(stderr, "%s in %s has refcnt %d, not %d\n",
+                               ent->suspect->path,
+                               sha1_to_hex(ent->suspect->commit->object.sha1),
+                               ent->suspect->refcnt, found);
+                       baa = 2;
+               }
+       }
+       if (baa) {
+               int opt = 0160;
+               find_alignment(sb, &opt);
+               output(sb, opt);
+               die("Baa %d!", baa);
+       }
+}
+
+static int has_path_in_work_tree(const char *path)
+{
+       struct stat st;
+       return !lstat(path, &st);
+}
+
+static unsigned parse_score(const char *arg)
+{
+       char *end;
+       unsigned long score = strtoul(arg, &end, 10);
+       if (*end)
+               return 0;
+       return score;
+}
+
+static const char *add_prefix(const char *prefix, const char *path)
+{
+       if (!prefix || !prefix[0])
+               return path;
+       return prefix_path(prefix, strlen(prefix), path);
+}
+
+static const char *parse_loc(const char *spec,
+                            struct scoreboard *sb, long lno,
+                            long begin, long *ret)
+{
+       char *term;
+       const char *line;
+       long num;
+       int reg_error;
+       regex_t regexp;
+       regmatch_t match[1];
+
+       /* Allow "-L <something>,+20" to mean starting at <something>
+        * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+        * <something>.
+        */
+       if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+               num = strtol(spec + 1, &term, 10);
+               if (term != spec + 1) {
+                       if (spec[0] == '-')
+                               num = 0 - num;
+                       if (0 < num)
+                               *ret = begin + num - 2;
+                       else if (!num)
+                               *ret = begin;
+                       else
+                               *ret = begin + num;
+                       return term;
+               }
+               return spec;
+       }
+       num = strtol(spec, &term, 10);
+       if (term != spec) {
+               *ret = num;
+               return term;
+       }
+       if (spec[0] != '/')
+               return spec;
+
+       /* it could be a regexp of form /.../ */
+       for (term = (char*) spec + 1; *term && *term != '/'; term++) {
+               if (*term == '\\')
+                       term++;
+       }
+       if (*term != '/')
+               return spec;
+
+       /* try [spec+1 .. term-1] as regexp */
+       *term = 0;
+       begin--; /* input is in human terms */
+       line = nth_line(sb, begin);
+
+       if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
+           !(reg_error = regexec(&regexp, line, 1, match, 0))) {
+               const char *cp = line + match[0].rm_so;
+               const char *nline;
+
+               while (begin++ < lno) {
+                       nline = nth_line(sb, begin);
+                       if (line <= cp && cp < nline)
+                               break;
+                       line = nline;
+               }
+               *ret = begin;
+               regfree(&regexp);
+               *term++ = '/';
+               return term;
+       }
+       else {
+               char errbuf[1024];
+               regerror(reg_error, &regexp, errbuf, 1024);
+               die("-L parameter '%s': %s", spec + 1, errbuf);
+       }
+}
+
+static void prepare_blame_range(struct scoreboard *sb,
+                               const char *bottomtop,
+                               long lno,
+                               long *bottom, long *top)
+{
+       const char *term;
+
+       term = parse_loc(bottomtop, sb, lno, 1, bottom);
+       if (*term == ',') {
+               term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
+               if (*term)
+                       usage(pickaxe_usage);
+       }
+       if (*term)
+               usage(pickaxe_usage);
+}
+
+int cmd_pickaxe(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       const char *path;
+       struct scoreboard sb;
+       struct origin *o;
+       struct blame_entry *ent;
+       int i, seen_dashdash, unk, opt;
+       long bottom, top, lno;
+       int output_option = 0;
+       const char *revs_file = NULL;
+       const char *final_commit_name = NULL;
+       char type[10];
+       const char *bottomtop = NULL;
+
+       save_commit_buffer = 0;
+
+       opt = 0;
+       seen_dashdash = 0;
+       for (unk = i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (*arg != '-')
+                       break;
+               else if (!strcmp("-c", arg))
+                       output_option |= OUTPUT_ANNOTATE_COMPAT;
+               else if (!strcmp("-t", arg))
+                       output_option |= OUTPUT_RAW_TIMESTAMP;
+               else if (!strcmp("-l", arg))
+                       output_option |= OUTPUT_LONG_OBJECT_NAME;
+               else if (!strcmp("-S", arg) && ++i < argc)
+                       revs_file = argv[i];
+               else if (!strncmp("-M", arg, 2)) {
+                       opt |= PICKAXE_BLAME_MOVE;
+                       blame_move_score = parse_score(arg+2);
+               }
+               else if (!strncmp("-C", arg, 2)) {
+                       if (opt & PICKAXE_BLAME_COPY)
+                               opt |= PICKAXE_BLAME_COPY_HARDER;
+                       opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
+                       blame_copy_score = parse_score(arg+2);
+               }
+               else if (!strncmp("-L", arg, 2)) {
+                       if (!arg[2]) {
+                               if (++i >= argc)
+                                       usage(pickaxe_usage);
+                               arg = argv[i];
+                       }
+                       else
+                               arg += 2;
+                       if (bottomtop)
+                               die("More than one '-L n,m' option given");
+                       bottomtop = arg;
+               }
+               else if (!strcmp("--score-debug", arg))
+                       output_option |= OUTPUT_SHOW_SCORE;
+               else if (!strcmp("-f", arg) ||
+                        !strcmp("--show-name", arg))
+                       output_option |= OUTPUT_SHOW_NAME;
+               else if (!strcmp("-n", arg) ||
+                        !strcmp("--show-number", arg))
+                       output_option |= OUTPUT_SHOW_NUMBER;
+               else if (!strcmp("-p", arg) ||
+                        !strcmp("--porcelain", arg))
+                       output_option |= OUTPUT_PORCELAIN;
+               else if (!strcmp("--", arg)) {
+                       seen_dashdash = 1;
+                       i++;
+                       break;
+               }
+               else
+                       argv[unk++] = arg;
+       }
+
+       if (!blame_move_score)
+               blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
+       if (!blame_copy_score)
+               blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
+
+       /* We have collected options unknown to us in argv[1..unk]
+        * which are to be passed to revision machinery if we are
+        * going to do the "bottom" procesing.
+        *
+        * The remaining are:
+        *
+        * (1) if seen_dashdash, its either
+        *     "-options -- <path>" or
+        *     "-options -- <path> <rev>".
+        *     but the latter is allowed only if there is no
+        *     options that we passed to revision machinery.
+        *
+        * (2) otherwise, we may have "--" somewhere later and
+        *     might be looking at the first one of multiple 'rev'
+        *     parameters (e.g. " master ^next ^maint -- path").
+        *     See if there is a dashdash first, and give the
+        *     arguments before that to revision machinery.
+        *     After that there must be one 'path'.
+        *
+        * (3) otherwise, its one of the three:
+        *     "-options <path> <rev>"
+        *     "-options <rev> <path>"
+        *     "-options <path>"
+        *     but again the first one is allowed only if
+        *     there is no options that we passed to revision
+        *     machinery.
+        */
+
+       if (seen_dashdash) {
+               /* (1) */
+               if (argc <= i)
+                       usage(pickaxe_usage);
+               path = add_prefix(prefix, argv[i]);
+               if (i + 1 == argc - 1) {
+                       if (unk != 1)
+                               usage(pickaxe_usage);
+                       argv[unk++] = argv[i + 1];
+               }
+               else if (i + 1 != argc)
+                       /* garbage at end */
+                       usage(pickaxe_usage);
+       }
+       else {
+               int j;
+               for (j = i; !seen_dashdash && j < argc; j++)
+                       if (!strcmp(argv[j], "--"))
+                               seen_dashdash = j;
+               if (seen_dashdash) {
+                       if (seen_dashdash + 1 != argc - 1)
+                               usage(pickaxe_usage);
+                       path = add_prefix(prefix, argv[seen_dashdash + 1]);
+                       for (j = i; j < seen_dashdash; j++)
+                               argv[unk++] = argv[j];
+               }
+               else {
+                       /* (3) */
+                       path = add_prefix(prefix, argv[i]);
+                       if (i + 1 == argc - 1) {
+                               final_commit_name = argv[i + 1];
+
+                               /* if (unk == 1) we could be getting
+                                * old-style
+                                */
+                               if (unk == 1 && !has_path_in_work_tree(path)) {
+                                       path = add_prefix(prefix, argv[i + 1]);
+                                       final_commit_name = argv[i];
+                               }
+                       }
+                       else if (i != argc - 1)
+                               usage(pickaxe_usage); /* garbage at end */
+
+                       if (!has_path_in_work_tree(path))
+                               die("cannot stat path %s: %s",
+                                   path, strerror(errno));
+               }
+       }
+
+       if (final_commit_name)
+               argv[unk++] = final_commit_name;
+
+       /* Now we got rev and path.  We do not want the path pruning
+        * but we may want "bottom" processing.
+        */
+       argv[unk] = NULL;
+
+       init_revisions(&revs, NULL);
+       setup_revisions(unk, argv, &revs, "HEAD");
+       memset(&sb, 0, sizeof(sb));
+
+       /* There must be one and only one positive commit in the
+        * revs->pending array.
+        */
+       for (i = 0; i < revs.pending.nr; i++) {
+               struct object *obj = revs.pending.objects[i].item;
+               if (obj->flags & UNINTERESTING)
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?",
+                           revs.pending.objects[i].name);
+               if (sb.final)
+                       die("More than one commit to dig from %s and %s?",
+                           revs.pending.objects[i].name,
+                           final_commit_name);
+               sb.final = (struct commit *) obj;
+               final_commit_name = revs.pending.objects[i].name;
+       }
+
+       if (!sb.final) {
+               /* "--not A B -- path" without anything positive */
+               unsigned char head_sha1[20];
+
+               final_commit_name = "HEAD";
+               if (get_sha1(final_commit_name, head_sha1))
+                       die("No such ref: HEAD");
+               sb.final = lookup_commit_reference(head_sha1);
+               add_pending_object(&revs, &(sb.final->object), "HEAD");
+       }
+
+       /* If we have bottom, this will mark the ancestors of the
+        * bottom commits we would reach while traversing as
+        * uninteresting.
+        */
+       prepare_revision_walk(&revs);
+
+       o = get_origin(&sb, sb.final, path);
+       if (fill_blob_sha1(o))
+               die("no such path %s in %s", path, final_commit_name);
+
+       sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size);
+       num_read_blob++;
+       lno = prepare_lines(&sb);
+
+       bottom = top = 0;
+       if (bottomtop)
+               prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
+       if (bottom && top && top < bottom) {
+               long tmp;
+               tmp = top; top = bottom; bottom = tmp;
+       }
+       if (bottom < 1)
+               bottom = 1;
+       if (top < 1)
+               top = lno;
+       bottom--;
+       if (lno < top)
+               die("file %s has only %lu lines", path, lno);
+
+       ent = xcalloc(1, sizeof(*ent));
+       ent->lno = bottom;
+       ent->num_lines = top - bottom;
+       ent->suspect = o;
+       ent->s_lno = bottom;
+
+       sb.ent = ent;
+       sb.path = path;
+
+       if (revs_file && read_ancestry(revs_file))
+               die("reading graft file %s failed: %s",
+                   revs_file, strerror(errno));
+
+       assign_blame(&sb, &revs, opt);
+
+       coalesce(&sb);
+
+       if (!(output_option & OUTPUT_PORCELAIN))
+               find_alignment(&sb, &output_option);
+
+       output(&sb, output_option);
+       free((void *)sb.final_buf);
+       for (ent = sb.ent; ent; ) {
+               struct blame_entry *e = ent->next;
+               free(ent);
+               ent = e;
+       }
+
+       if (DEBUG) {
+               printf("num read blob: %d\n", num_read_blob);
+               printf("num get patch: %d\n", num_get_patch);
+               printf("num commits: %d\n", num_commits);
+       }
+       return 0;
+}
index 7290e6d..d853902 100644 (file)
@@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs)
        }
 }
 
-static int add_one_ref(const char *path, const unsigned char *sha1)
+static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *object = parse_object(sha1);
        if (!object)
@@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
        revs.tree_objects = 1;
 
        /* Add all external refs */
-       for_each_ref(add_one_ref);
+       for_each_ref(add_one_ref, NULL);
 
        /* Add all refs from the index file */
        add_cache_refs();
index f5150ed..d23974e 100644 (file)
@@ -10,7 +10,7 @@
 
 static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]";
 
-static int all, tags, force, thin = 1;
+static int all, tags, force, thin = 1, verbose;
 static const char *execute;
 
 #define BUF_SIZE (2084)
@@ -27,7 +27,7 @@ static void add_refspec(const char *ref)
        refspec_nr = nr;
 }
 
-static int expand_one_ref(const char *ref, const unsigned char *sha1)
+static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
 {
        /* Ignore the "refs/" at the beginning of the refname */
        ref += 5;
@@ -51,7 +51,7 @@ static void expand_refspecs(void)
        }
        if (!tags)
                return;
-       for_each_ref(expand_one_ref);
+       for_each_ref(expand_one_ref, NULL);
 }
 
 static void set_refspecs(const char **refs, int nr)
@@ -248,6 +248,8 @@ static int do_push(const char *repo)
                while (dest_refspec_nr--)
                        argv[dest_argc++] = *dest_refspec++;
                argv[dest_argc] = NULL;
+               if (verbose)
+                       fprintf(stderr, "Pushing to %s\n", dest);
                err = run_command_v(argc, argv);
                if (!err)
                        continue;
@@ -281,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        i++;
                        break;
                }
+               if (!strcmp(arg, "-v")) {
+                       verbose=1;
+                       continue;
+               }
+               if (!strncmp(arg, "--repo=", 7)) {
+                       repo = arg+7;
+                       continue;
+               }
                if (!strcmp(arg, "--all")) {
                        all = 1;
                        continue;
index f60cee1..7b6e572 100644 (file)
@@ -3,7 +3,7 @@
 #include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char *key;
 static regex_t *key_regexp;
@@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
                        type = T_BOOL;
                else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
                        return git_config(show_all_config);
-               else
+               else if (!strcmp(argv[1], "--global")) {
+                       char *home = getenv("HOME");
+                       if (home) {
+                               char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+                               setenv("GIT_CONFIG", user_config, 1);
+                               free(user_config);
+                       } else {
+                               die("$HOME not set");
+                       }
+               } else
                        break;
                argc--;
                argv++;
index fd3ccc8..3b716fb 100644 (file)
@@ -137,7 +137,7 @@ static void show_default(void)
        }
 }
 
-static int show_reference(const char *refname, const unsigned char *sha1)
+static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        show_rev(NORMAL, sha1, refname);
        return 0;
@@ -299,19 +299,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--all")) {
-                               for_each_ref(show_reference);
+                               for_each_ref(show_reference, NULL);
                                continue;
                        }
                        if (!strcmp(arg, "--branches")) {
-                               for_each_branch_ref(show_reference);
+                               for_each_branch_ref(show_reference, NULL);
                                continue;
                        }
                        if (!strcmp(arg, "--tags")) {
-                               for_each_tag_ref(show_reference);
+                               for_each_tag_ref(show_reference, NULL);
                                continue;
                        }
                        if (!strcmp(arg, "--remotes")) {
-                               for_each_remote_ref(show_reference);
+                               for_each_remote_ref(show_reference, NULL);
                                continue;
                        }
                        if (!strcmp(arg, "--show-prefix")) {
index 578c9fa..fb1a400 100644 (file)
@@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top)
              compare_ref_name);
 }
 
-static int append_ref(const char *refname, const unsigned char *sha1)
+static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct commit *commit = lookup_commit_reference_gently(sha1, 1);
        int i;
@@ -369,7 +369,7 @@ static int append_ref(const char *refname, const unsigned char *sha1)
        return 0;
 }
 
-static int append_head_ref(const char *refname, const unsigned char *sha1)
+static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        unsigned char tmp[20];
        int ofs = 11;
@@ -380,14 +380,14 @@ static int append_head_ref(const char *refname, const unsigned char *sha1)
         */
        if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
                ofs = 5;
-       return append_ref(refname + ofs, sha1);
+       return append_ref(refname + ofs, sha1, flag, cb_data);
 }
 
-static int append_tag_ref(const char *refname, const unsigned char *sha1)
+static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        if (strncmp(refname, "refs/tags/", 10))
                return 0;
-       return append_ref(refname + 5, sha1);
+       return append_ref(refname + 5, sha1, flag, cb_data);
 }
 
 static const char *match_ref_pattern = NULL;
@@ -401,7 +401,7 @@ static int count_slash(const char *s)
        return cnt;
 }
 
-static int append_matching_ref(const char *refname, const unsigned char *sha1)
+static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        /* we want to allow pattern hold/<asterisk> to show all
         * branches under refs/heads/hold/, and v0.99.9? to show
@@ -417,41 +417,39 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1)
        if (fnmatch(match_ref_pattern, tail, 0))
                return 0;
        if (!strncmp("refs/heads/", refname, 11))
-               return append_head_ref(refname, sha1);
+               return append_head_ref(refname, sha1, flag, cb_data);
        if (!strncmp("refs/tags/", refname, 10))
-               return append_tag_ref(refname, sha1);
-       return append_ref(refname, sha1);
+               return append_tag_ref(refname, sha1, flag, cb_data);
+       return append_ref(refname, sha1, flag, cb_data);
 }
 
 static void snarf_refs(int head, int tag)
 {
        if (head) {
                int orig_cnt = ref_name_cnt;
-               for_each_ref(append_head_ref);
+               for_each_ref(append_head_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
        if (tag) {
                int orig_cnt = ref_name_cnt;
-               for_each_ref(append_tag_ref);
+               for_each_ref(append_tag_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
 }
 
-static int rev_is_head(char *head_path, int headlen, char *name,
+static int rev_is_head(char *head, int headlen, char *name,
                       unsigned char *head_sha1, unsigned char *sha1)
 {
-       int namelen;
-       if ((!head_path[0]) ||
+       if ((!head[0]) ||
            (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
                return 0;
-       namelen = strlen(name);
-       if ((headlen < namelen) ||
-           memcmp(head_path + headlen - namelen, name, namelen))
-               return 0;
-       if (headlen == namelen ||
-           head_path[headlen - namelen - 1] == '/')
-               return 1;
-       return 0;
+       if (!strncmp(head, "refs/heads/", 11))
+               head += 11;
+       if (!strncmp(name, "refs/heads/", 11))
+               name += 11;
+       else if (!strncmp(name, "heads/", 6))
+               name += 6;
+       return !strcmp(head, name);
 }
 
 static int show_merge_base(struct commit_list *seen, int num_rev)
@@ -495,7 +493,7 @@ static void append_one_rev(const char *av)
 {
        unsigned char revkey[20];
        if (!get_sha1(av, revkey)) {
-               append_ref(av, revkey);
+               append_ref(av, revkey, 0, NULL);
                return;
        }
        if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
@@ -503,7 +501,7 @@ static void append_one_rev(const char *av)
                int saved_matches = ref_name_cnt;
                match_ref_pattern = av;
                match_ref_slash = count_slash(av);
-               for_each_ref(append_matching_ref);
+               for_each_ref(append_matching_ref, NULL);
                if (saved_matches == ref_name_cnt &&
                    ref_name_cnt < MAX_REVS)
                        error("no matching refs with %s", av);
@@ -559,9 +557,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        int all_heads = 0, all_tags = 0;
        int all_mask, all_revs;
        int lifo = 1;
-       char head_path[128];
-       const char *head_path_p;
-       int head_path_len;
+       char head[128];
+       const char *head_p;
+       int head_len;
        unsigned char head_sha1[20];
        int merge_base = 0;
        int independent = 0;
@@ -638,31 +636,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                ac--; av++;
        }
 
-       head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
-       if (head_path_p) {
-               head_path_len = strlen(head_path_p);
-               memcpy(head_path, head_path_p, head_path_len + 1);
+       head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+       if (head_p) {
+               head_len = strlen(head_p);
+               memcpy(head, head_p, head_len + 1);
        }
        else {
-               head_path_len = 0;
-               head_path[0] = 0;
+               head_len = 0;
+               head[0] = 0;
        }
 
-       if (with_current_branch && head_path_p) {
+       if (with_current_branch && head_p) {
                int has_head = 0;
                for (i = 0; !has_head && i < ref_name_cnt; i++) {
                        /* We are only interested in adding the branch
                         * HEAD points at.
                         */
-                       if (rev_is_head(head_path,
-                                       head_path_len,
+                       if (rev_is_head(head,
+                                       head_len,
                                        ref_name[i],
                                        head_sha1, NULL))
                                has_head++;
                }
                if (!has_head) {
-                       int pfxlen = strlen(git_path("refs/heads/"));
-                       append_one_rev(head_path + pfxlen);
+                       int pfxlen = strlen("refs/heads/");
+                       append_one_rev(head + pfxlen);
                }
        }
 
@@ -713,8 +711,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        if (1 < num_rev || extra < 0) {
                for (i = 0; i < num_rev; i++) {
                        int j;
-                       int is_head = rev_is_head(head_path,
-                                                 head_path_len,
+                       int is_head = rev_is_head(head,
+                                                 head_len,
                                                  ref_name[i],
                                                  head_sha1,
                                                  rev[i]->object.sha1);
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
new file mode 100644 (file)
index 0000000..06ec400
--- /dev/null
@@ -0,0 +1,147 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]";
+
+static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
+       found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
+static const char **pattern;
+
+static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+       struct object *obj;
+       const char *hex;
+
+       if (tags_only || heads_only) {
+               int match;
+
+               match = heads_only && !strncmp(refname, "refs/heads/", 11);
+               match |= tags_only && !strncmp(refname, "refs/tags/", 10);
+               if (!match)
+                       return 0;
+       }
+       if (pattern) {
+               int reflen = strlen(refname);
+               const char **p = pattern, *m;
+               while ((m = *p++) != NULL) {
+                       int len = strlen(m);
+                       if (len > reflen)
+                               continue;
+                       if (memcmp(m, refname + reflen - len, len))
+                               continue;
+                       if (len == reflen)
+                               goto match;
+                       /* "--verify" requires an exact match */
+                       if (verify)
+                               continue;
+                       if (refname[reflen - len - 1] == '/')
+                               goto match;
+               }
+               return 0;
+       }
+
+match:
+       found_match++;
+       obj = parse_object(sha1);
+       if (!obj) {
+               if (quiet)
+                       return 0;
+               die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
+       }
+       if (quiet)
+               return 0;
+
+       hex = find_unique_abbrev(sha1, abbrev);
+       if (hash_only)
+               printf("%s\n", hex);
+       else
+               printf("%s %s\n", hex, refname);
+       if (deref_tags && obj->type == OBJ_TAG) {
+               obj = deref_tag(obj, refname, 0);
+               hex = find_unique_abbrev(obj->sha1, abbrev);
+               printf("%s %s^{}\n", hex, refname);
+       }
+       return 0;
+}
+
+int cmd_show_ref(int argc, const char **argv, const char *prefix)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (*arg != '-') {
+                       pattern = argv + i;
+                       break;
+               }
+               if (!strcmp(arg, "--")) {
+                       pattern = argv + i + 1;
+                       if (!*pattern)
+                               pattern = NULL;
+                       break;
+               }
+               if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+                       quiet = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) {
+                       show_head = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) {
+                       deref_tags = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) {
+                       hash_only = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--hash=", 7) ||
+                   (!strncmp(arg, "--abbrev", 8) &&
+                    (arg[8] == '=' || arg[8] == '\0'))) {
+                       if (arg[3] != 'h' && !arg[8])
+                               /* --abbrev only */
+                               abbrev = DEFAULT_ABBREV;
+                       else {
+                               /* --hash= or --abbrev= */
+                               char *end;
+                               if (arg[3] == 'h') {
+                                       hash_only = 1;
+                                       arg += 7;
+                               }
+                               else
+                                       arg += 9;
+                               abbrev = strtoul(arg, &end, 10);
+                               if (*end || abbrev > 40)
+                                       usage(show_ref_usage);
+                               if (abbrev < MINIMUM_ABBREV)
+                                       abbrev = MINIMUM_ABBREV;
+                       }
+                       continue;
+               }
+               if (!strcmp(arg, "--verify")) {
+                       verify = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--tags")) {
+                       tags_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--heads")) {
+                       heads_only = 1;
+                       continue;
+               }
+               usage(show_ref_usage);
+       }
+       if (show_head)
+               head_ref(show_ref, NULL);
+       for_each_ref(show_ref, NULL);
+       if (!found_match) {
+               if (verify && !quiet)
+                       die("No match");
+               return 1;
+       }
+       return 0;
+}
index 1d3a5e2..d8be052 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "refs.h"
 
 static const char git_symbolic_ref_usage[] =
 "git-symbolic-ref name [ref]";
@@ -7,15 +8,14 @@ static const char git_symbolic_ref_usage[] =
 static void check_symref(const char *HEAD)
 {
        unsigned char sha1[20];
-       const char *git_HEAD = xstrdup(git_path("%s", HEAD));
-       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
-       if (git_refs_heads_master) {
-               /* we want to strip the .git/ part */
-               int pfxlen = strlen(git_HEAD) - strlen(HEAD);
-               puts(git_refs_heads_master + pfxlen);
-       }
-       else
+       int flag;
+       const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+
+       if (!refs_heads_master)
                die("No such ref: %s", HEAD);
+       else if (!(flag & REF_ISSYMREF))
+               die("ref %s is not a symbolic ref", HEAD);
+       puts(refs_heads_master);
 }
 
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
@@ -26,7 +26,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                check_symref(argv[1]);
                break;
        case 3:
-               create_symref(xstrdup(git_path("%s", argv[1])), argv[2]);
+               create_symref(argv[1], argv[2]);
                break;
        default:
                usage(git_symbolic_ref_usage);
index 4f96bca..e6d7574 100644 (file)
@@ -15,14 +15,14 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-fil
 
 /* We always read in 4kB chunks. */
 static unsigned char buffer[4096];
-static unsigned long offset, len;
+static unsigned long offset, len, consumed_bytes;
 static SHA_CTX ctx;
 
 /*
  * Make sure at least "min" bytes are available in the buffer, and
  * return the pointer to the buffer.
  */
-static void * fill(int min)
+static void *fill(int min)
 {
        if (min <= len)
                return buffer + offset;
@@ -30,7 +30,7 @@ static void * fill(int min)
                die("cannot fill %d bytes", min);
        if (offset) {
                SHA1_Update(&ctx, buffer, offset);
-               memcpy(buffer, buffer + offset, len);
+               memmove(buffer, buffer + offset, len);
                offset = 0;
        }
        do {
@@ -51,6 +51,7 @@ static void use(int bytes)
                die("used more bytes than were available");
        len -= bytes;
        offset += bytes;
+       consumed_bytes += bytes;
 }
 
 static void *get_data(unsigned long size)
@@ -89,35 +90,49 @@ static void *get_data(unsigned long size)
 
 struct delta_info {
        unsigned char base_sha1[20];
+       unsigned long base_offset;
        unsigned long size;
        void *delta;
+       unsigned nr;
        struct delta_info *next;
 };
 
 static struct delta_info *delta_list;
 
-static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+                             unsigned long base_offset,
+                             void *delta, unsigned long size)
 {
        struct delta_info *info = xmalloc(sizeof(*info));
 
        hashcpy(info->base_sha1, base_sha1);
+       info->base_offset = base_offset;
        info->size = size;
        info->delta = delta;
+       info->nr = nr;
        info->next = delta_list;
        delta_list = info;
 }
 
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+struct obj_info {
+       unsigned long offset;
+       unsigned char sha1[20];
+};
+
+static struct obj_info *obj_list;
+
+static void added_object(unsigned nr, const char *type, void *data,
+                        unsigned long size);
 
-static void write_object(void *buf, unsigned long size, const char *type)
+static void write_object(unsigned nr, void *buf, unsigned long size,
+                        const char *type)
 {
-       unsigned char sha1[20];
-       if (write_sha1_file(buf, size, type, sha1) < 0)
+       if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0)
                die("failed to write object");
-       added_object(sha1, type, buf, size);
+       added_object(nr, type, buf, size);
 }
 
-static void resolve_delta(const char *type,
+static void resolve_delta(unsigned nr, const char *type,
                          void *base, unsigned long base_size,
                          void *delta, unsigned long delta_size)
 {
@@ -130,20 +145,23 @@ static void resolve_delta(const char *type,
        if (!result)
                die("failed to apply delta");
        free(delta);
-       write_object(result, result_size, type);
+       write_object(nr, result, result_size, type);
        free(result);
 }
 
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+static void added_object(unsigned nr, const char *type, void *data,
+                        unsigned long size)
 {
        struct delta_info **p = &delta_list;
        struct delta_info *info;
 
        while ((info = *p) != NULL) {
-               if (!hashcmp(info->base_sha1, sha1)) {
+               if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+                   info->base_offset == obj_list[nr].offset) {
                        *p = info->next;
                        p = &delta_list;
-                       resolve_delta(type, data, size, info->delta, info->size);
+                       resolve_delta(info->nr, type, data, size,
+                                     info->delta, info->size);
                        free(info);
                        continue;
                }
@@ -151,7 +169,8 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
        }
 }
 
-static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
+static void unpack_non_delta_entry(enum object_type kind, unsigned long size,
+                                  unsigned nr)
 {
        void *buf = get_data(size);
        const char *type;
@@ -164,30 +183,80 @@ static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
        default: die("bad type %d", kind);
        }
        if (!dry_run && buf)
-               write_object(buf, size, type);
+               write_object(nr, buf, size, type);
        free(buf);
 }
 
-static void unpack_delta_entry(unsigned long delta_size)
+static void unpack_delta_entry(enum object_type kind, unsigned long delta_size,
+                              unsigned nr)
 {
        void *delta_data, *base;
        unsigned long base_size;
        char type[20];
        unsigned char base_sha1[20];
 
-       hashcpy(base_sha1, fill(20));
-       use(20);
+       if (kind == OBJ_REF_DELTA) {
+               hashcpy(base_sha1, fill(20));
+               use(20);
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               if (!has_sha1_file(base_sha1)) {
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+                       return;
+               }
+       } else {
+               unsigned base_found = 0;
+               unsigned char *pack, c;
+               unsigned long base_offset;
+               unsigned lo, mid, hi;
 
-       delta_data = get_data(delta_size);
-       if (dry_run || !delta_data) {
-               free(delta_data);
-               return;
-       }
+               pack = fill(1);
+               c = *pack;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                               die("offset value overflow for delta base object");
+                       pack = fill(1);
+                       c = *pack;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               base_offset = obj_list[nr].offset - base_offset;
 
-       if (!has_sha1_file(base_sha1)) {
-               add_delta_to_list(base_sha1, delta_data, delta_size);
-               return;
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               lo = 0;
+               hi = nr;
+               while (lo < hi) {
+                       mid = (lo + hi)/2;
+                       if (base_offset < obj_list[mid].offset) {
+                               hi = mid;
+                       } else if (base_offset > obj_list[mid].offset) {
+                               lo = mid + 1;
+                       } else {
+                               hashcpy(base_sha1, obj_list[mid].sha1);
+                               base_found = !is_null_sha1(base_sha1);
+                               break;
+                       }
+               }
+               if (!base_found) {
+                       /* The delta base object is itself a delta that
+                          has not been resolved yet. */
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+                       return;
+               }
        }
+
        base = read_sha1_file(base_sha1, type, &base_size);
        if (!base) {
                error("failed to read delta-pack base object %s",
@@ -197,7 +266,7 @@ static void unpack_delta_entry(unsigned long delta_size)
                has_errors = 1;
                return;
        }
-       resolve_delta(type, base, base_size, delta_data, delta_size);
+       resolve_delta(nr, type, base, base_size, delta_data, delta_size);
        free(base);
 }
 
@@ -208,6 +277,8 @@ static void unpack_one(unsigned nr, unsigned total)
        unsigned long size;
        enum object_type type;
 
+       obj_list[nr].offset = consumed_bytes;
+
        pack = fill(1);
        c = *pack;
        use(1);
@@ -216,7 +287,7 @@ static void unpack_one(unsigned nr, unsigned total)
        shift = 4;
        while (c & 0x80) {
                pack = fill(1);
-               c = *pack++;
+               c = *pack;
                use(1);
                size += (c & 0x7f) << shift;
                shift += 7;
@@ -225,13 +296,14 @@ static void unpack_one(unsigned nr, unsigned total)
                static unsigned long last_sec;
                static unsigned last_percent;
                struct timeval now;
-               unsigned percentage = (nr * 100) / total;
+               unsigned percentage = ((nr+1) * 100) / total;
 
                gettimeofday(&now, NULL);
                if (percentage != last_percent || now.tv_sec != last_sec) {
                        last_sec = now.tv_sec;
                        last_percent = percentage;
-                       fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+                       fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                       percentage, (nr+1), total);
                }
        }
        switch (type) {
@@ -239,10 +311,11 @@ static void unpack_one(unsigned nr, unsigned total)
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
-               unpack_non_delta_entry(type, size);
+               unpack_non_delta_entry(type, size, nr);
                return;
-       case OBJ_DELTA:
-               unpack_delta_entry(size);
+       case OBJ_REF_DELTA:
+       case OBJ_OFS_DELTA:
+               unpack_delta_entry(type, size, nr);
                return;
        default:
                error("bad object type %d", type);
@@ -265,9 +338,10 @@ static void unpack_all(void)
                die("unknown pack file version %d", ntohl(hdr->hdr_version));
        fprintf(stderr, "Unpacking %d objects\n", nr_objects);
 
+       obj_list = xmalloc(nr_objects * sizeof(*obj_list));
        use(sizeof(struct pack_header));
        for (i = 0; i < nr_objects; i++)
-               unpack_one(i+1, nr_objects);
+               unpack_one(i, nr_objects);
        if (delta_list)
                die("unresolved deltas left after unpacking");
 }
@@ -297,6 +371,21 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
                                recover = 1;
                                continue;
                        }
+                       if (!strncmp(arg, "--pack_header=", 14)) {
+                               struct pack_header *hdr;
+