Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Wed, 1 Nov 2006 23:09:55 +0000 (15:09 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 1 Nov 2006 23:09:55 +0000 (15:09 -0800)
* maint:
pack-objects doesn't create random pack names
link_temp_to_file: don't leave the path truncated on adjust_shared_perm failure

102 files changed:
.gitignore
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-blame.txt
Documentation/git-for-each-ref.txt [new file with mode: 0644]
Documentation/git-grep.txt
Documentation/git-pack-objects.txt
Documentation/git-pull.txt
Documentation/git-rebase.txt
Documentation/git-repack.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-prune.c
builtin-push.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
combine-diff.c
contrib/completion/git-completion.bash
contrib/emacs/vc-git.el
describe.c
diff.c
diff.h
environment.c
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-clone.sh
git-commit.sh
git-cvsimport.perl
git-cvsserver.perl
git-fetch.sh
git-merge.sh
git-rebase.sh
git-repack.sh
git-resolve.sh
git-revert.sh
git-send-email.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
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/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
tree-diff.c
upload-pack.c
wt-status.c
index 25eb4637a6f971a4a290ab823a937997c700bd7f..4c8c8e411500fe2ed0108c46838d50576ed15185 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 ee51fe3b9fcb551d8dbd6374103898229d4b77ea..d9e73da2a7949a876360674f4ddd41cba8dec0ae 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].
index 7b7b9e8ce92db7fbb2072b75a613b073b0c5a4e6..e112172ca57da75ce6c2dd447cc97c6aa2b6499e 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 e1f89444a95618a1e520940a79389c854cf9d576..9891c1d3779185c200345c0e6d92763aa155a169 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]
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 d8af4d961b83e362b83f25b21d40f9d54ed4bc11..bfbece9864a3c6d8b38dd526e2483fea0fb0b58f 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 944c9e1e8fd99c3a9cab28397b1a2e12011280b1..9bd1e39feb0b34ef01c0bc0df7a9be0ded0e34cb 100644 (file)
@@ -9,7 +9,7 @@ git-pack-objects - Create a packed archive of objects
 SYNOPSIS
 --------
 [verse]
-'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
+'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
        [--local] [--incremental] [--window=N] [--depth=N]
        [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
 
@@ -110,6 +110,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
 ------
index 51577fcbe638981baf1870006eef633be304e26b..2a5aea73bafedd62f20cecb1fbf5069b515022bd 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 9d7bcaa38cc5c13e29c00ddd18c64202e98cb5f8..10f2924f4df1eb29c1baf4484d2837377b6cfcb3 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
 
@@ -121,6 +121,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 d2eaa0995ded915f89d436e4501c5ae667822642..0fa47e3b018815ffa9de23725737fe2b599eaead 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 481b3f50e3d690f5cd47ffa2c2d8bb645114e315..4c8d907bd595a4f31b3f53cfff25b84cea059ff7 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 e062030e91ae2b2a25c2ef7bafc4c026015e1f5e..71bcb7954f0c53a995ba56de6c99d9aebc6aa52a 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 b00607ee1b836e2cdcb033e55ca18214d91cb3b9..0679e3c209278d07173007e514c09e78fe9a4e49 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)
 -------------------------------
 
@@ -321,7 +142,7 @@ gitlink:git-mv[1]::
        Move or rename a file, a directory, or a symlink.
 
 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.
@@ -488,6 +309,188 @@ 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-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 66c8b4b127cc5c0380ab7d8cb5f4d1dcb89b18a7..1cc9f586d6d239273b72c4500b35e6c437debe6a 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
 
@@ -210,7 +212,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))
 
@@ -265,8 +267,10 @@ LIB_OBJS = \
 
 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 \
@@ -278,6 +282,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 \
@@ -306,7 +311,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
@@ -675,6 +682,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 $@+ $@
index 3ffdad68d130136312028b389da73af1a789232e..28e7352e98eaa3bcbc3c86ce772bae35a605ceb2 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 3e227d27124ff3eb09409a781c532de5bfc52369..3ec1c8f1b287654cccb0f2427810d0afead5c44b 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 {
@@ -158,11 +164,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 +175,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);
@@ -244,7 +244,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 +253,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 +273,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 +300,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 +335,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 +373,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 +382,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 +399,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 +445,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 +506,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 +522,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 +537,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 +565,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 +585,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 +611,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 +642,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 +766,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 +797,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 +904,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 +948,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 +1010,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,62 +1027,49 @@ 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;
                }
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 11a5277a69f4cb5c02fb8c9a9312ebafdc472028..11397f5504f98ccff47a90b228abc71b30327fb9 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..e028a53
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * 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;
+
+       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++) {
+               if (!strcmp(ref_list[i], head))
+                       printf("* %s\n", ref_list[i]);
+               else
+                       printf("  %s\n", 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 c407c033e7d4f43818d62691688911de71a5cf11..3d3097d299d4c964e1acfff7a365501a8bd53587 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..93d3d7e
--- /dev/null
@@ -0,0 +1,871 @@
+#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 },
+       { "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);
+       }
+}
+
+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 4205e5d38dea6dee2d815a7434f87eacd089e4a9..ad7dc00cde4e8e08ef35b313525781c135357df3 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 c3ed1ce4929472b4e3fb7577ac61bd914baf0954..235a0ee48f2c5ce09c63a949358eb16cde05332d 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 9d1ceae44c6a449d2329e897b9705a5b457187f0..fedb0137bc5e3ba50ecb90ce560ce9ce9290d8df 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 52886b69b068eca899bea574c5d9790395eeb006..618aa314d22dd16bb62b0a7deb3be14fda411ebe 100644 (file)
@@ -75,11 +75,10 @@ static void name_rev(struct commit *commit,
        }
 }
 
-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 96c069a81da643b7ee3515ca8c89734881fa3b77..41e1e74533d64e999ec1e564e15b8dea29e4c9c6 100644 (file)
@@ -15,7 +15,7 @@
 #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] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -29,6 +29,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 +61,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 +87,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 +152,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 +167,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 +199,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 +210,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 +254,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 +269,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 +304,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 +334,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 +377,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 +413,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 +456,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)
@@ -899,26 +931,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 +997,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;
@@ -1484,6 +1554,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;
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
new file mode 100644 (file)
index 0000000..1087657
--- /dev/null
@@ -0,0 +1,105 @@
+#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;
+       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;
+
+       /* 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;
+       int (*iterate_ref)(each_ref_fn, void *) = for_each_tag_ref;
+
+       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")) {
+                       iterate_ref = for_each_ref;
+                       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));
+       iterate_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;
+}
index 7290e6d9aa9e26cc8256a34ed22028e80936c010..d853902c51e78800b76cf4714dffaa79600d3e53 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 f5150ed82d269dd825a45cc9c369c71e777bac05..5f7eccf14b272fc108bcd49aece911ebde460b4f 100644 (file)
@@ -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)
index fd3ccc8546455f60f56ceb59dbe98fb367a86d2a..3b716fba133ae2f0375ef73d6a50a4d93112b5e4 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 578c9fafd022ce8a8676d348120c2fc7b2b1c5dd..fb1a4000d98e27389904578309b935cbda2a85fe 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 1d3a5e229ae1a16211671e7591a7544af98721f8..d8be0527f4752131b0a1276ecedcc82714636c28 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 4f96bcae32afd22093b9a76d5fe5b39ba8268583..74a90c1129d0ff2f772db937d6f9a0ac4e2589a4 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 write_object(void *buf, unsigned long size, const char *type)
+static void added_object(unsigned nr, const char *type, void *data,
+                        unsigned long size);
+
+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");
 }
index a3c0a455ae5d1b8d7b7b6a8798ed7f038ab7bd72..7f9c638466f79fee7d2914b8ec76d713c3670e7e 100644 (file)
@@ -406,9 +406,9 @@ static int unresolve_one(const char *path)
 
 static void read_head_pointers(void)
 {
-       if (read_ref(git_path("HEAD"), head_sha1))
+       if (read_ref("HEAD", head_sha1))
                die("No HEAD -- no initial commit yet?\n");
-       if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+       if (read_ref("MERGE_HEAD", merge_head_sha1)) {
                fprintf(stderr, "Not in the middle of a merge.\n");
                exit(0);
        }
@@ -445,7 +445,7 @@ static int do_reupdate(int ac, const char **av,
        int has_head = 1;
        const char **pathspec = get_pathspec(prefix, av + 1);
 
-       if (read_ref(git_path("HEAD"), head_sha1))
+       if (read_ref("HEAD", head_sha1))
                /* If there is no HEAD, that means it is an initial
                 * commit.  Update everything in the index.
                 */
index 90a3da53ad003a82781e6d4acfc7815c8f15f24f..b34e5987dd256e0b7d9fae46fe89f66dd18ad91f 100644 (file)
@@ -3,15 +3,16 @@
 #include "builtin.h"
 
 static const char git_update_ref_usage[] =
-"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
+"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
        const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
        struct ref_lock *lock;
        unsigned char sha1[20], oldsha1[20];
-       int i;
+       int i, delete;
 
+       delete = 0;
        setup_ident();
        git_config(git_default_config);
 
@@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                                die("Refusing to perform update with \\n in message.");
                        continue;
                }
+               if (!strcmp("-d", argv[i])) {
+                       delete = 1;
+                       continue;
+               }
                if (!refname) {
                        refname = argv[i];
                        continue;
@@ -44,11 +49,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 
        if (get_sha1(value, sha1))
                die("%s: not a valid SHA1", value);
+
+       if (delete) {
+               if (oldval)
+                       usage(git_update_ref_usage);
+               return delete_ref(refname, sha1);
+       }
+
        hashclr(oldsha1);
-       if (oldval && get_sha1(oldval, oldsha1))
+       if (oldval && *oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
-       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
        if (!lock)
                return 1;
        if (write_ref_sha1(lock, sha1, msg) < 0)
index f71b9629b9c8671dd17296fabac7e1952b3fb9e9..a9b28ed8996ffc255f82ebc35f32386865b7f227 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -14,11 +14,14 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 extern void prune_packed_objects(int);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_annotate(int argc, const char **argv, const char *prefix);
 extern int cmd_apply(int argc, const char **argv, const char *prefix);
 extern int cmd_archive(int argc, const char **argv, const char *prefix);
+extern int cmd_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
+extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
@@ -27,6 +30,7 @@ extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_stages(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
+extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
@@ -49,8 +53,8 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
 extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
-extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
+extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
@@ -63,5 +67,7 @@ extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
 extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
 
 #endif
diff --git a/cache.h b/cache.h
index c35470107dde85b2179333b354aff0be1eed6df2..e997a8500526024d33e60b87e782557f7c427d14 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -179,6 +179,7 @@ struct lock_file {
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
 extern int commit_lock_file(struct lock_file *);
 extern void rollback_lock_file(struct lock_file *);
+extern int delete_ref(const char *, unsigned char *sha1);
 
 /* Environment bits from configuration mechanism */
 extern int use_legacy_headers;
@@ -188,7 +189,6 @@ extern int prefer_symlink_refs;
 extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int shared_repository;
-extern int deny_non_fast_forwards;
 extern const char *apply_default_whitespace;
 extern int zlib_compression_level;
 
@@ -269,8 +269,9 @@ enum object_type {
        OBJ_TREE = 2,
        OBJ_BLOB = 3,
        OBJ_TAG = 4,
-       /* 5/6 for future expansion */
-       OBJ_DELTA = 7,
+       /* 5 for future expansion */
+       OBJ_OFS_DELTA = 6,
+       OBJ_REF_DELTA = 7,
        OBJ_BAD,
 };
 
@@ -288,9 +289,9 @@ extern int get_sha1(const char *str, unsigned char *sha1);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
-extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
-extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
-extern int validate_symref(const char *git_HEAD);
+extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+extern int create_symref(const char *ref, const char *refs_heads_master);
+extern int validate_symref(const char *ref);
 
 extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
index 8bf99f215c36c701bb3ff2ffc952587b8562c920..29d0c9cf953fdc13bd44bef451d242c762e1c611 100644 (file)
@@ -898,8 +898,10 @@ void diff_tree_combined(const unsigned char *sha1,
                /* show stat against the first parent even
                 * when doing combined diff.
                 */
-               if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT)
-                       diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+               int stat_opt = (opt->output_format &
+                               (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+               if (i == 0 && stat_opt)
+                       diffopts.output_format = stat_opt;
                else
                        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
                diff_tree_sha1(parent[i], sha1, "", &diffopts);
@@ -929,7 +931,8 @@ void diff_tree_combined(const unsigned char *sha1,
                        }
                        needsep = 1;
                }
-               else if (opt->output_format & DIFF_FORMAT_DIFFSTAT)
+               else if (opt->output_format &
+                        (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
                        needsep = 1;
                if (opt->output_format & DIFF_FORMAT_PATCH) {
                        if (needsep)
index d9cb17d0b2f21b6cabd78c245f4deaf23cc6b6a0..b074f4fe5779da8907e112c8d5af1ea60f120be5 100755 (executable)
@@ -101,6 +101,23 @@ __git_complete_file ()
        esac
 }
 
+__git_aliases ()
+{
+       git repo-config --list | grep '^alias\.' \
+               | sed -e 's/^alias\.//' -e 's/=.*$//'
+}
+
+__git_aliased_command ()
+{
+       local cmdline=$(git repo-config alias.$1)
+       for word in $cmdline; do
+               if [ "${word##-*}" ]; then
+                       echo $word
+                       return
+               fi
+       done
+}
+
 _git_branch ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -264,10 +281,18 @@ _git ()
 {
        if [ $COMP_CWORD = 1 ]; then
                COMPREPLY=($(compgen \
-                       -W "--version $(git help -a|egrep '^ ')" \
+                       -W "--version $(git help -a|egrep '^ ') \
+                           $(__git_aliases)" \
                        -- "${COMP_WORDS[COMP_CWORD]}"))
        else
-               case "${COMP_WORDS[1]}" in
+               local command="${COMP_WORDS[1]}"
+               local expansion=$(__git_aliased_command "$command")
+
+               if [ "$expansion" ]; then
+                       command="$expansion"
+               fi
+
+               case "$command" in
                branch)      _git_branch ;;
                cat-file)    _git_cat_file ;;
                checkout)    _git_checkout ;;
index 80e767533a2f3c0c288f64533f3c73dd80c0d738..8b6361922fd6e6a2fcd9acb20fd54f5b645b36f0 100644 (file)
@@ -33,6 +33,8 @@
 ;;  - working with revisions other than HEAD
 ;;
 
+(eval-when-compile (require 'cl))
+
 (defvar git-commits-coding-system 'utf-8
   "Default coding system for git commits.")
 
index ab192f83ae27c5e0bf3f2a5f61684d0dc66deb32..f4029ee74e80305e9ec9e5793d2e12c07096f45a 100644 (file)
@@ -53,7 +53,7 @@ static void add_to_known_names(const char *path,
        names = ++idx;
 }
 
-static int get_name(const char *path, const unsigned char *sha1)
+static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct commit *commit = lookup_commit_reference_gently(sha1, 1);
        struct object *object;
@@ -113,7 +113,7 @@ static void describe(const char *arg, int last_one)
 
        if (!initialized) {
                initialized = 1;
-               for_each_ref(get_name);
+               for_each_ref(get_name, NULL);
                qsort(name_array, names, sizeof(*name_array), compare_names);
        }
 
diff --git a/diff.c b/diff.c
index fb8243261cb3cc9165dbe990586d3865fad4ee61..33153787b8117396cf906e69e656849ac04f3257 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -795,6 +795,23 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
               set, total_files, adds, dels, reset);
 }
 
+static void show_numstat(struct diffstat_t* data, struct diff_options *options)
+{
+       int i;
+
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+
+               printf("%d\t%d\t", file->added, file->deleted);
+               if (options->line_termination &&
+                   quote_c_style(file->name, NULL, NULL, 0))
+                       quote_c_style(file->name, NULL, stdout, 0);
+               else
+                       fputs(file->name, stdout);
+               putchar(options->line_termination);
+       }
+}
+
 struct checkdiff_t {
        struct xdiff_emit_state xm;
        const char *filename;
@@ -1731,6 +1748,7 @@ int diff_setup_done(struct diff_options *options)
                                      DIFF_FORMAT_CHECKDIFF |
                                      DIFF_FORMAT_NO_OUTPUT))
                options->output_format &= ~(DIFF_FORMAT_RAW |
+                                           DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
@@ -1740,7 +1758,9 @@ int diff_setup_done(struct diff_options *options)
         * recursive bits for other formats here.
         */
        if (options->output_format & (DIFF_FORMAT_PATCH |
+                                     DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
        /*
@@ -1828,6 +1848,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--patch-with-raw")) {
                options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
+       else if (!strcmp(arg, "--numstat")) {
+               options->output_format |= DIFF_FORMAT_NUMSTAT;
+       }
        else if (!strncmp(arg, "--stat", 6)) {
                char *end;
                int width = options->stat_width;
@@ -2602,7 +2625,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & DIFF_FORMAT_DIFFSTAT) {
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2612,7 +2635,10 @@ void diff_flush(struct diff_options *options)
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(&diffstat, options);
+               if (output_format & DIFF_FORMAT_NUMSTAT)
+                       show_numstat(&diffstat, options);
+               if (output_format & DIFF_FORMAT_DIFFSTAT)
+                       show_stats(&diffstat, options);
                separator++;
        }
 
diff --git a/diff.h b/diff.h
index b48c9914e7e3802d17870bbc0fd68c454fded61c..ac7b21c46bdf958df5d7c8162c7aa248af54dbdb 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -26,20 +26,21 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 
 #define DIFF_FORMAT_RAW                0x0001
 #define DIFF_FORMAT_DIFFSTAT   0x0002
-#define DIFF_FORMAT_SUMMARY    0x0004
-#define DIFF_FORMAT_PATCH      0x0008
+#define DIFF_FORMAT_NUMSTAT    0x0004
+#define DIFF_FORMAT_SUMMARY    0x0008
+#define DIFF_FORMAT_PATCH      0x0010
 
 /* These override all above */
-#define DIFF_FORMAT_NAME       0x0010
-#define DIFF_FORMAT_NAME_STATUS        0x0020
-#define DIFF_FORMAT_CHECKDIFF  0x0040
+#define DIFF_FORMAT_NAME       0x0100
+#define DIFF_FORMAT_NAME_STATUS        0x0200
+#define DIFF_FORMAT_CHECKDIFF  0x0400
 
 /* Same as output_format = 0 but we know that -s flag was given
  * and we should not give default value to output_format.
  */
-#define DIFF_FORMAT_NO_OUTPUT  0x0080
+#define DIFF_FORMAT_NO_OUTPUT  0x0800
 
-#define DIFF_FORMAT_CALLBACK   0x0100
+#define DIFF_FORMAT_CALLBACK   0x1000
 
 struct diff_options {
        const char *filter;
@@ -101,6 +102,8 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
                     const char *base, struct diff_options *opt);
 extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
                          const char *base, struct diff_options *opt);
+extern int diff_root_tree_sha1(const unsigned char *new, const char *base,
+                               struct diff_options *opt);
 
 struct combine_diff_path {
        struct combine_diff_path *next;
@@ -170,6 +173,7 @@ extern void diffcore_std_no_resolve(struct diff_options *);
 "  --patch-with-raw\n" \
 "                output both a patch and the diff-raw format.\n" \
 "  --stat        show diffstat instead of patch.\n" \
+"  --numstat     show numeric diffstat instead of patch.\n" \
 "  --patch-with-stat\n" \
 "                output a patch and prepend its diffstat.\n" \
 "  --name-only   show only names of changed files.\n" \
index 63b1d155be1aca2f9fe7aad5f747c06b9102fd60..84d870ca4eca6e202bcbec388e9a299b887f9a12 100644 (file)
@@ -20,7 +20,6 @@ int warn_ambiguous_refs = 1;
 int repository_format_version;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
 int shared_repository = PERM_UMASK;
-int deny_non_fast_forwards = 0;
 const char *apply_default_whitespace;
 int zlib_compression_level = Z_DEFAULT_COMPRESSION;
 int pager_in_use;
index e8708aa802b8e09d8044bb99dbccb0fecdb14481..90b79407c65840deaf1ab80697a90d2e9df12a4a 100644 (file)
@@ -42,7 +42,7 @@ static void rev_list_push(struct commit *commit, int mark)
        }
 }
 
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = deref_tag(parse_object(sha1), path, 0);
 
@@ -143,7 +143,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        unsigned in_vain = 0;
        int got_continue = 0;
 
-       for_each_ref(rev_list_insert_ref);
+       for_each_ref(rev_list_insert_ref, NULL);
 
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
@@ -166,12 +166,13 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                }
 
                if (!fetching)
-                       packet_write(fd[1], "want %s%s%s%s%s\n",
+                       packet_write(fd[1], "want %s%s%s%s%s%s\n",
                                     sha1_to_hex(remote),
                                     (multi_ack ? " multi_ack" : ""),
                                     (use_sideband == 2 ? " side-band-64k" : ""),
                                     (use_sideband == 1 ? " side-band" : ""),
-                                    (use_thin_pack ? " thin-pack" : ""));
+                                    (use_thin_pack ? " thin-pack" : ""),
+                                    " ofs-delta");
                else
                        packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
                fetching++;
@@ -253,7 +254,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
 
 static struct commit_list *complete;
 
-static int mark_complete(const char *path, const unsigned char *sha1)
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = parse_object(sha1);
 
@@ -365,7 +366,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
                }
        }
 
-       for_each_ref(mark_complete);
+       for_each_ref(mark_complete, NULL);
        if (cutoff)
                mark_recent_complete_commits(cutoff);
 
diff --git a/fetch.c b/fetch.c
index 34df8d37d7dc92f8b652c160c49f4ace6e44e19c..c426c049974fa1bf1aceea9d43d2e2c5ce194e06 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1)
        return -1;
 }
 
-static int mark_complete(const char *path, const unsigned char *sha1)
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct commit *commit = lookup_commit_reference_gently(sha1, 1);
        if (commit) {
@@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref,
                if (!write_ref || !write_ref[i])
                        continue;
 
-               lock[i] = lock_ref_sha1(write_ref[i], NULL, 0);
+               lock[i] = lock_ref_sha1(write_ref[i], NULL);
                if (!lock[i]) {
                        error("Can't lock ref %s", write_ref[i]);
                        goto unlock_and_fail;
@@ -274,7 +274,7 @@ int pull(int targets, char **target, const char **write_ref,
        }
 
        if (!get_recover)
-               for_each_ref(mark_complete);
+               for_each_ref(mark_complete, NULL);
 
        for (i = 0; i < targets; i++) {
                if (interpret_target(target[i], &sha1[20 * i])) {
index 4d994f3fc83d71501bbfde5159a869b4f2a38e99..46b628cb94375e3f645f868efb04547ffc20e6e7 100644 (file)
@@ -402,7 +402,7 @@ static void fsck_dir(int i, char *path)
 
 static int default_refs;
 
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *obj;
 
@@ -424,7 +424,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
 
 static void get_default_heads(void)
 {
-       for_each_ref(fsck_handle_ref);
+       for_each_ref(fsck_handle_ref, NULL);
 
        /*
         * Not having any default heads isn't really fatal, but
@@ -458,15 +458,14 @@ static void fsck_object_dir(const char *path)
 static int fsck_head_link(void)
 {
        unsigned char sha1[20];
-       const char *git_HEAD = xstrdup(git_path("HEAD"));
-       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
-       int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+       int flag;
+       const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag);
 
-       if (!git_refs_heads_master)
+       if (!head_points_at || !(flag & REF_ISSYMREF))
                return error("HEAD is not a symbolic ref");
-       if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+       if (strncmp(head_points_at, "refs/heads/", 11))
                return error("HEAD points to something strange (%s)",
-                            git_refs_heads_master + pfxlen);
+                            head_points_at);
        if (is_null_sha1(sha1))
                return error("HEAD: not a valid git pointer");
        return 0;
diff --git a/git-annotate.perl b/git-annotate.perl
deleted file mode 100755 (executable)
index 215ed26..0000000
+++ /dev/null
@@ -1,708 +0,0 @@
-#!/usr/bin/perl
-# Copyright 2006, Ryan Anderson <ryan@michonline.com>
-#
-# GPL v2 (See COPYING)
-#
-# This file is licensed under the GPL v2, or a later version
-# at the discretion of Linus Torvalds.
-
-use warnings;
-use strict;
-use Getopt::Long;
-use POSIX qw(strftime gmtime);
-use File::Basename qw(basename dirname);
-
-sub usage() {
-       print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
-       -l, --long
-                       Show long rev (Defaults off)
-       -t, --time
-                       Show raw timestamp (Defaults off)
-       -r, --rename
-                       Follow renames (Defaults on).
-       -S, --rev-file revs-file
-                       Use revs from revs-file instead of calling git-rev-list
-       -h, --help
-                       This message.
-";
-
-       exit(1);
-}
-
-our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
-
-my $rc = GetOptions(   "long|l" => \$longrev,
-                       "time|t" => \$rawtime,
-                       "help|h" => \$help,
-                       "rename|r" => \$rename,
-                       "rev-file|S=s" => \$rev_file);
-if (!$rc or $help or !@ARGV) {
-       usage();
-}
-
-my $filename = shift @ARGV;
-if (@ARGV) {
-       $starting_rev = shift @ARGV;
-}
-
-my @stack = (
-       {
-               'rev' => defined $starting_rev ? $starting_rev : "HEAD",
-               'filename' => $filename,
-       },
-);
-
-our @filelines = ();
-
-if (defined $starting_rev) {
-       @filelines = git_cat_file($starting_rev, $filename);
-} else {
-       open(F,"<",$filename)
-               or die "Failed to open filename: $!";
-
-       while(<F>) {
-               chomp;
-               push @filelines, $_;
-       }
-       close(F);
-
-}
-
-our %revs;
-our @revqueue;
-our $head;
-
-my $revsprocessed = 0;
-while (my $bound = pop @stack) {
-       my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});
-       foreach my $revinst (@revisions) {
-               my ($rev, @parents) = @$revinst;
-               $head ||= $rev;
-
-               if (!defined($rev)) {
-                       $rev = "";
-               }
-               $revs{$rev}{'filename'} = $bound->{'filename'};
-               if (scalar @parents > 0) {
-                       $revs{$rev}{'parents'} = \@parents;
-                       next;
-               }
-
-               if (!$rename) {
-                       next;
-               }
-
-               my $newbound = find_parent_renames($rev, $bound->{'filename'});
-               if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {
-                       push @stack, $newbound;
-                       $revs{$rev}{'parents'} = [$newbound->{'rev'}];
-               }
-       }
-}
-push @revqueue, $head;
-init_claim( defined $starting_rev ? $head : 'dirty');
-unless (defined $starting_rev) {
-       my $diff = open_pipe("git","diff","HEAD", "--",$filename)
-               or die "Failed to call git diff to check for dirty state: $!";
-
-       _git_diff_parse($diff, [$head], "dirty", (
-                               'author' => gitvar_name("GIT_AUTHOR_IDENT"),
-                               'author_date' => sprintf("%s +0000",time()),
-                               )
-                       );
-       close($diff);
-}
-handle_rev();
-
-
-my $i = 0;
-foreach my $l (@filelines) {
-       my ($output, $rev, $committer, $date);
-       if (ref $l eq 'ARRAY') {
-               ($output, $rev, $committer, $date) = @$l;
-               if (!$longrev && length($rev) > 8) {
-                       $rev = substr($rev,0,8);
-               }
-       } else {
-               $output = $l;
-               ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown');
-       }
-
-       printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
-               format_date($date), ++$i, $output);
-}
-
-sub init_claim {
-       my ($rev) = @_;
-       for (my $i = 0; $i < @filelines; $i++) {
-               $filelines[$i] = [ $filelines[$i], '', '', '', 1];
-                       # line,
-                       # rev,
-                       # author,
-                       # date,
-                       # 1 <-- belongs to the original file.
-       }
-       $revs{$rev}{'lines'} = \@filelines;
-}
-
-
-sub handle_rev {
-       my $revseen = 0;
-       my %seen;
-       while (my $rev = shift @revqueue) {
-               next if $seen{$rev}++;
-
-               my %revinfo = git_commit_info($rev);
-
-               if (exists $revs{$rev}{parents} &&
-                   scalar @{$revs{$rev}{parents}} != 0) {
-
-                       git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo);
-                       push @revqueue, @{$revs{$rev}{'parents'}};
-
-               } else {
-                       # We must be at the initial rev here, so claim everything that is left.
-                       for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
-                               if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
-                                       claim_line($i, $rev, $revs{$rev}{lines}, %revinfo);
-                               }
-                       }
-               }
-       }
-}
-
-
-sub git_rev_list {
-       my ($rev, $file) = @_;
-
-       my $revlist;
-       if ($rev_file) {
-               open($revlist, '<' . $rev_file)
-                   or die "Failed to open $rev_file : $!";
-       } else {
-               $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
-                       or die "Failed to exec git-rev-list: $!";
-       }
-
-       my @revs;
-       while(my $line = <$revlist>) {
-               chomp $line;
-               my ($rev, @parents) = split /\s+/, $line;
-               push @revs, [ $rev, @parents ];
-       }
-       close($revlist);
-
-       printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0);
-       return @revs;
-}
-
-sub find_parent_renames {
-       my ($rev, $file) = @_;
-
-       my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev")
-               or die "Failed to exec git-diff: $!";
-
-       local $/ = "\0";
-       my %bound;
-       my $junk = <$patch>;
-       while (my $change = <$patch>) {
-               chomp $change;
-               my $filename = <$patch>;
-               if (!defined $filename) {
-                       next;
-               }
-               chomp $filename;
-
-               if ($change =~ m/^[AMD]$/ ) {
-                       next;
-               } elsif ($change =~ m/^R/ ) {
-                       my $oldfilename = $filename;
-                       $filename = <$patch>;
-                       chomp $filename;
-                       if ( $file eq $filename ) {
-                               my $parent = git_find_parent($rev, $oldfilename);
-                               @bound{'rev','filename'} = ($parent, $oldfilename);
-                               last;
-                       }
-               }
-       }
-       close($patch);
-
-       return \%bound;
-}
-
-
-sub git_find_parent {
-       my ($rev, $filename) = @_;
-
-       my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename)
-               or die "Failed to open git-rev-list to find a single parent: $!";
-
-       my $parentline = <$revparent>;
-       chomp $parentline;
-       my ($revfound,$parent) = split m/\s+/, $parentline;
-
-       close($revparent);
-
-       return $parent;
-}
-
-sub git_find_all_parents {
-       my ($rev) = @_;
-
-       my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev")
-               or die "Failed to open git-rev-list to find a single parent: $!";
-
-       my $parentline = <$revparent>;
-       chomp $parentline;
-       my ($origrev, @parents) = split m/\s+/, $parentline;
-
-       close($revparent);
-
-       return @parents;
-}
-
-sub git_merge_base {
-       my ($rev1, $rev2) = @_;
-
-       my $mb = open_pipe("git-merge-base", $rev1, $rev2)
-               or die "Failed to open git-merge-base: $!";
-
-       my $base = <$mb>;
-       chomp $base;
-
-       close($mb);
-
-       return $base;
-}
-
-# Construct a set of pseudo parents that are in the same order,
-# and the same quantity as the real parents,
-# but whose SHA1s are as similar to the logical parents
-# as possible.
-sub get_pseudo_parents {
-       my ($all, $fake) = @_;
-
-       my @all = @$all;
-       my @fake = @$fake;
-
-       my @pseudo;
-
-       my %fake = map {$_ => 1} @fake;
-       my %seenfake;
-
-       my $fakeidx = 0;
-       foreach my $p (@all) {
-               if (exists $fake{$p}) {
-                       if ($fake[$fakeidx] ne $p) {
-                               die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n",
-                                           $fake[$fakeidx], $p,
-                                           join(", ", @all),
-                                           join(", ", @fake),
-                                          );
-                       }
-
-                       push @pseudo, $p;
-                       $fakeidx++;
-                       $seenfake{$p}++;
-
-               } else {
-                       my $base = git_merge_base($fake[$fakeidx], $p);
-                       if ($base ne $fake[$fakeidx]) {
-                               die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n",
-                                      $fake[$fakeidx], $p, $base);
-                       }
-
-                       # The details of how we parse the diffs
-                       # mean that we cannot have a duplicate
-                       # revision in the list, so if we've already
-                       # seen the revision we would normally add, just use
-                       # the actual revision.
-                       if ($seenfake{$base}) {
-                               push @pseudo, $p;
-                       } else {
-                               push @pseudo, $base;
-                               $seenfake{$base}++;
-                       }
-               }
-       }
-
-       return @pseudo;
-}
-
-
-# Get a diff between the current revision and a parent.
-# Record the commit information that results.
-sub git_diff_parse {
-       my ($parents, $rev, %revinfo) = @_;
-
-       my @pseudo_parents;
-       my @command = ("git-diff-tree");
-       my $revision_spec;
-
-       if (scalar @$parents == 1) {
-
-               $revision_spec = join("..", $parents->[0], $rev);
-               @pseudo_parents = @$parents;
-       } else {
-               my @all_parents = git_find_all_parents($rev);
-
-               if (@all_parents !=  @$parents) {
-                       @pseudo_parents = get_pseudo_parents(\@all_parents, $parents);
-               } else {
-                       @pseudo_parents = @$parents;
-               }
-
-               $revision_spec = $rev;
-               push @command, "-c";
-       }
-
-       my @filenames = ( $revs{$rev}{'filename'} );
-
-       foreach my $parent (@$parents) {
-               push @filenames, $revs{$parent}{'filename'};
-       }
-
-       push @command, "-p", "-M", $revision_spec, "--", @filenames;
-
-
-       my $diff = open_pipe( @command )
-               or die "Failed to call git-diff for annotation: $!";
-
-       _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo);
-
-       close($diff);
-}
-
-sub _git_diff_parse {
-       my ($diff, $parents, $rev, %revinfo) = @_;
-
-       my $ri = 0;
-
-       my $slines = $revs{$rev}{'lines'};
-       my (%plines, %pi);
-
-       my $gotheader = 0;
-       my ($remstart);
-       my $parent_count = @$parents;
-
-       my $diff_header_regexp = "^@";
-       $diff_header_regexp .= "@" x @$parents;
-       $diff_header_regexp .= ' -\d+,\d+' x @$parents;
-       $diff_header_regexp .= ' \+(\d+),\d+';
-       $diff_header_regexp .= " " . ("@" x @$parents);
-
-       my %claim_regexps;
-       my $allparentplus = '^' . '\\+' x @$parents . '(.*)$';
-
-       {
-               my $i = 0;
-               foreach my $parent (@$parents) {
-
-                       $pi{$parent} = 0;
-                       my $r = '^' . '.' x @$parents . '(.*)$';
-                       my $p = $r;
-                       substr($p,$i+1, 1) = '\\+';
-
-                       my $m = $r;
-                       substr($m,$i+1, 1) = '-';
-
-                       $claim_regexps{$parent}{plus} = $p;
-                       $claim_regexps{$parent}{minus} = $m;
-
-                       $plines{$parent} = [];
-
-                       $i++;
-               }
-       }
-
-       DIFF:
-       while(<$diff>) {
-               chomp;
-               #printf("%d:%s:\n", $gotheader, $_);
-               if (m/$diff_header_regexp/) {
-                       $remstart = $1 - 1;
-                       # (0-based arrays)
-
-                       $gotheader = 1;
-
-                       foreach my $parent (@$parents) {
-                               for (my $i = $ri; $i < $remstart; $i++) {
-                                       $plines{$parent}[$pi{$parent}++] = $slines->[$i];
-                               }
-                       }
-                       $ri = $remstart;
-
-                       next DIFF;
-
-               } elsif (!$gotheader) {
-                       # Skip over the leadin.
-                       next DIFF;
-               }
-
-               if (m/^\\/) {
-                       ;
-                       # Skip \No newline at end of file.
-                       # But this can be internationalized, so only look
-                       # for an initial \
-
-               } else {
-                       my %claims = ();
-                       my $negclaim = 0;
-                       my $allclaimed = 0;
-                       my $line;
-
-                       if (m/$allparentplus/) {
-                               claim_line($ri, $rev, $slines, %revinfo);
-                               $allclaimed = 1;
-
-                       }
-
-                       PARENT:
-                       foreach my $parent (keys %claim_regexps) {
-                               my $m = $claim_regexps{$parent}{minus};
-                               my $p = $claim_regexps{$parent}{plus};
-
-                               if (m/$m/) {
-                                       $line = $1;
-                                       $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ];
-                                       $negclaim++;
-
-                               } elsif (m/$p/) {
-                                       $line = $1;
-                                       if (get_line($slines, $ri) eq $line) {
-                                               # Found a match, claim
-                                               $claims{$parent}++;
-
-                                       } else {
-                                               die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n",
-                                                               $ri, $line,
-                                                               get_line($slines, $ri),
-                                                               $rev, $parent);
-                                       }
-                               }
-                       }
-
-                       if (%claims) {
-                               foreach my $parent (@$parents) {
-                                       next if $claims{$parent} || $allclaimed;
-                                       $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
-                                           #[ $line, '', '', '', 0 ];
-                               }
-                               $ri++;
-
-                       } elsif ($negclaim) {
-                               next DIFF;
-
-                       } else {
-                               if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) {
-                                       foreach my $parent (@$parents) {
-                                               printf("parent %s is on line %d\n", $parent, $pi{$parent});
-                                       }
-
-                                       my @context;
-                                       for (my $i = -2; $i < 2; $i++) {
-                                               push @context, get_line($slines, $ri + $i);
-                                       }
-                                       my $context = join("\n", @context);
-
-                                       my $justline = substr($_, scalar @$parents);
-                                       die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n",
-                                                   $ri,
-                                                   $justline,
-                                                   $context);
-                               }
-                               foreach my $parent (@$parents) {
-                                       $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
-                               }
-                               $ri++;
-                       }
-               }
-       }
-
-       for (my $i = $ri; $i < @{$slines} ; $i++) {
-               foreach my $parent (@$parents) {
-                       push @{$plines{$parent}}, $slines->[$ri];
-               }
-               $ri++;
-       }
-
-       foreach my $parent (@$parents) {
-               $revs{$parent}{lines} = $plines{$parent};
-       }
-
-       return;
-}
-
-sub get_line {
-       my ($lines, $index) = @_;
-
-       return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index];
-}
-
-sub git_cat_file {
-       my ($rev, $filename) = @_;
-       return () unless defined $rev && defined $filename;
-
-       my $blob = git_ls_tree($rev, $filename);
-       die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob;
-
-       my $catfile = open_pipe("git","cat-file", "blob", $blob)
-               or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
-
-       my @lines;
-       while(<$catfile>) {
-               chomp;
-               push @lines, $_;
-       }
-       close($catfile);
-
-       return @lines;
-}
-
-sub git_ls_tree {
-       my ($rev, $filename) = @_;
-
-       my $lstree = open_pipe("git","ls-tree",$rev,$filename)
-               or die "Failed to call git ls-tree: $!";
-
-       my ($mode, $type, $blob, $tfilename);
-       while(<$lstree>) {
-               chomp;
-               ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
-               last if ($tfilename eq $filename);
-       }
-       close($lstree);
-
-       return $blob if ($tfilename eq $filename);
-       die "git-ls-tree failed to find blob for $filename";
-
-}
-
-
-
-sub claim_line {
-       my ($floffset, $rev, $lines, %revinfo) = @_;
-       my $oline = get_line($lines, $floffset);
-       @{$lines->[$floffset]} = ( $oline, $rev,
-               $revinfo{'author'}, $revinfo{'author_date'} );
-       #printf("Claiming line %d with rev %s: '%s'\n",
-       #               $floffset, $rev, $oline) if 1;
-}
-
-sub git_commit_info {
-       my ($rev) = @_;
-       my $commit = open_pipe("git-cat-file", "commit", $rev)
-               or die "Failed to call git-cat-file: $!";
-
-       my %info;
-       while(<$commit>) {
-               chomp;
-               last if (length $_ == 0);
-
-               if (m/^author (.*) <(.*)> (.*)$/) {
-                       $info{'author'} = $1;
-                       $info{'author_email'} = $2;
-                       $info{'author_date'} = $3;
-               } elsif (m/^committer (.*) <(.*)> (.*)$/) {
-                       $info{'committer'} = $1;
-                       $info{'committer_email'} = $2;
-                       $info{'committer_date'} = $3;
-               }
-       }
-       close($commit);
-
-       return %info;
-}
-
-sub format_date {
-       if ($rawtime) {
-               return $_[0];
-       }
-       my ($timestamp, $timezone) = split(' ', $_[0]);
-       my $minutes = abs($timezone);
-       $minutes = int($minutes / 100) * 60 + ($minutes % 100);
-       if ($timezone < 0) {
-           $minutes = -$minutes;
-       }
-       my $t = $timestamp + $minutes * 60;
-       return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t));
-}
-
-# Copied from git-send-email.perl - We need a Git.pm module..
-sub gitvar {
-    my ($var) = @_;
-    my $fh;
-    my $pid = open($fh, '-|');
-    die "$!" unless defined $pid;
-    if (!$pid) {
-       exec('git-var', $var) or die "$!";
-    }
-    my ($val) = <$fh>;
-    close $fh or die "$!";
-    chomp($val);
-    return $val;
-}
-
-sub gitvar_name {
-    my ($name) = @_;
-    my $val = gitvar($name);
-    my @field = split(/\s+/, $val);
-    return join(' ', @field[0...(@field-4)]);
-}
-
-sub open_pipe {
-       if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
-               return open_pipe_activestate(@_);
-       } else {
-               return open_pipe_normal(@_);
-       }
-}
-
-sub open_pipe_activestate {
-       tie *fh, "Git::ActiveStatePipe", @_;
-       return *fh;
-}
-
-sub open_pipe_normal {
-       my (@execlist) = @_;
-
-       my $pid = open my $kid, "-|";
-       defined $pid or die "Cannot fork: $!";
-
-       unless ($pid) {
-               exec @execlist;
-               die "Cannot exec @execlist: $!";
-       }
-
-       return $kid;
-}
-
-package Git::ActiveStatePipe;
-use strict;
-
-sub TIEHANDLE {
-       my ($class, @params) = @_;
-       my $cmdline = join " ", @params;
-       my  @data = qx{$cmdline};
-       bless { i => 0, data => \@data }, $class;
-}
-
-sub READLINE {
-       my $self = shift;
-       if ($self->{i} >= scalar @{$self->{data}}) {
-               return undef;
-       }
-       return $self->{'data'}->[ $self->{i}++ ];
-}
-
-sub CLOSE {
-       my $self = shift;
-       delete $self->{data};
-       delete $self->{i};
-}
-
-sub EOF {
-       my $self = shift;
-       return ($self->{i} >= scalar @{$self->{data}});
-}
index 06a8d26945a679b06438308ceb96c69cd76c43db..6da31e87a01af9b883958b3b0bae4304bfa94520 100755 (executable)
@@ -179,11 +179,12 @@ bisect_reset() {
         *)
            usage ;;
        esac
-       git checkout "$branch" &&
-       rm -fr "$GIT_DIR/refs/bisect"
-       rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
-       rm -f "$GIT_DIR/BISECT_LOG"
-       rm -f "$GIT_DIR/BISECT_NAMES"
+       if git checkout "$branch"; then
+               rm -fr "$GIT_DIR/refs/bisect"
+               rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
+               rm -f "$GIT_DIR/BISECT_LOG"
+               rm -f "$GIT_DIR/BISECT_NAMES"
+       fi
 }
 
 bisect_replay () {
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755 (executable)
index f823c78..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/bin/sh
-
-USAGE='[-l] [-f] <branchname> [<start-point>] | (-d | -D) <branchname> | [-r]'
-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
-If one argument, create a new branch <branchname> based off of current HEAD.
-If two arguments, create a new branch <branchname> based off of <start-point>.'
-
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-
-delete_branch () {
-    option="$1"
-    shift
-    for branch_name
-    do
-       case ",$headref," in
-       ",$branch_name,")
-           die "Cannot delete the branch you are on." ;;
-       ,,)
-           die "What branch are you on anyway?" ;;
-       esac
-       branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
-           branch=$(git-rev-parse --verify "$branch^0") ||
-               die "Seriously, what branch are you talking about?"
-       case "$option" in
-       -D)
-           ;;
-       *)
-           mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
-           case " $mbs " in
-           *' '$branch' '*)
-               # the merge base of branch and HEAD contains branch --
-               # which means that the HEAD contains everything in both.
-               ;;
-           *)
-               echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
-               exit 1
-               ;;
-           esac
-           ;;
-       esac
-       rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
-       rm -f "$GIT_DIR/refs/heads/$branch_name"
-       echo "Deleted branch $branch_name."
-    done
-    exit 0
-}
-
-ls_remote_branches () {
-    git-rev-parse --symbolic --all |
-    sed -ne 's|^refs/\(remotes/\)|\1|p' |
-    sort
-}
-
-force=
-create_log=
-while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
-do
-       case "$1" in
-       -d | -D)
-               delete_branch "$@"
-               exit
-               ;;
-       -r)
-               ls_remote_branches
-               exit
-               ;;
-       -f)
-               force="$1"
-               ;;
-       -l)
-               create_log="yes"
-               ;;
-       --)
-               shift
-               break
-               ;;
-       -*)
-               usage
-               ;;
-       esac
-       shift
-done
-
-case "$#" in
-0)
-       git-rev-parse --symbolic --branches |
-       sort |
-       while read ref
-       do
-               if test "$headref" = "$ref"
-               then
-                       pfx='*'
-               else
-                       pfx=' '
-               fi
-               echo "$pfx $ref"
-       done
-       exit 0 ;;
-1)
-       head=HEAD ;;
-2)
-       head="$2^0" ;;
-esac
-branchname="$1"
-
-rev=$(git-rev-parse --verify "$head") || exit
-
-git-check-ref-format "heads/$branchname" ||
-       die "we do not like '$branchname' as a branch name."
-
-if [ -d "$GIT_DIR/refs/heads/$branchname" ]
-then
-       for refdir in `cd "$GIT_DIR" && \
-               find "refs/heads/$branchname" -type d | sort -r`
-       do
-               rmdir "$GIT_DIR/$refdir" || \
-                   die "Could not delete '$refdir', there may still be a ref there."
-       done
-fi
-
-if [ -e "$GIT_DIR/refs/heads/$branchname" ]
-then
-       if test '' = "$force"
-       then
-               die "$branchname already exists."
-       elif test "$branchname" = "$headref"
-       then
-               die "cannot force-update the current branch."
-       fi
-fi
-if test "$create_log" = 'yes'
-then
-       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
-       touch "$GIT_DIR/logs/refs/heads/$branchname"
-fi
-git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
index dd477245fb3703402b950cbc1378b35ce379d631..119bca1ffbfb8fb0dae404a8e7389f577aebc42c 100755 (executable)
@@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do
                shift
                [ -z "$newbranch" ] &&
                        die "git checkout: -b needs a branch name"
-               [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+               git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
                        die "git checkout: branch $newbranch already exists"
                git-check-ref-format "heads/$newbranch" ||
                        die "git checkout: we do not like '$newbranch' as a branch name."
@@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do
                        fi
                        new="$rev"
                        new_name="$arg^0"
-                       if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+                       if git-show-ref --verify --quiet -- "refs/heads/$arg"
+                       then
                                branch="$arg"
                        fi
                elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
index 24b119537bf5ed926235aabfceb8e4526af6f77a..3f006d1a778636e5906ad9e8671adb6561fa0d98 100755 (executable)
@@ -8,11 +8,15 @@
 # See git-sh-setup why.
 unset CDPATH
 
-usage() {
-       echo >&2 "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+die() {
+       echo >&2 "$@"
        exit 1
 }
 
+usage() {
+       die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+}
+
 get_repo_base() {
        (cd "$1" && (cd .git ; pwd)) 2> /dev/null
 }
@@ -35,11 +39,9 @@ clone_dumb_http () {
                "`git-repo-config --bool http.noEPSV`" = true ]; then
                curl_extra_args="${curl_extra_args} --disable-epsv"
        fi
-       http_fetch "$1/info/refs" "$clone_tmp/refs" || {
-               echo >&2 "Cannot get remote repository information.
+       http_fetch "$1/info/refs" "$clone_tmp/refs" ||
+               die "Cannot get remote repository information.
 Perhaps git-update-server-info needs to be run there?"
-               exit 1;
-       }
        while read sha1 refname
        do
                name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
@@ -143,17 +145,12 @@ while
                '')
                    usage ;;
                */*)
-                   echo >&2 "'$2' is not suitable for an origin name"
-                   exit 1
+                   die "'$2' is not suitable for an origin name"
                esac
-               git-check-ref-format "heads/$2" || {
-                   echo >&2 "'$2' is not suitable for a branch name"
-                   exit 1
-               }
-               test -z "$origin_override" || {
-                   echo >&2 "Do not give more than one --origin options."
-                   exit 1
-               }
+               git-check-ref-format "heads/$2" ||
+                   die "'$2' is not suitable for a branch name"
+               test -z "$origin_override" ||
+                   die "Do not give more than one --origin options."
                origin_override=yes
                origin="$2"; shift
                ;;
@@ -169,24 +166,19 @@ do
 done
 
 repo="$1"
-if test -z "$repo"
-then
-    echo >&2 'you must specify a repository to clone.'
-    exit 1
-fi
+test -n "$repo" ||
+    die 'you must specify a repository to clone.'
 
 # --bare implies --no-checkout
 if test yes = "$bare"
 then
        if test yes = "$origin_override"
        then
-               echo >&2 '--bare and --origin $origin options are incompatible.'
-               exit 1
+               die '--bare and --origin $origin options are incompatible.'
        fi
        if test t = "$use_separate_remote"
        then
-               echo >&2 '--bare and --use-separate-remote options are incompatible.'
-               exit 1
+               die '--bare and --use-separate-remote options are incompatible.'
        fi
        no_checkout=yes
 fi
@@ -206,7 +198,7 @@ fi
 dir="$2"
 # Try using "humanish" part of source repo if user didn't specify one
 [ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
-[ -e "$dir" ] && echo "$dir already exists." && usage
+[ -e "$dir" ] && die "destination directory '$dir' already exists."
 mkdir -p "$dir" &&
 D=$(cd "$dir" && pwd) &&
 trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0
@@ -233,7 +225,7 @@ then
                 cd reference-tmp &&
                 tar xf -)
        else
-               echo >&2 "$reference: not a local directory." && usage
+               die "reference repository '$reference' is not a local directory."
        fi
 fi
 
@@ -242,10 +234,8 @@ rm -f "$GIT_DIR/CLONE_HEAD"
 # We do local magic only when the user tells us to.
 case "$local,$use_local" in
 yes,yes)
-       ( cd "$repo/objects" ) || {
-               echo >&2 "-l flag seen but $repo is not local."
-               exit 1
-       }
+       ( cd "$repo/objects" ) ||
+               die "-l flag seen but repository '$repo' is not local."
 
        case "$local_shared" in
        no)
@@ -307,18 +297,15 @@ yes,yes)
                then
                        clone_dumb_http "$repo" "$D"
                else
-                       echo >&2 "http transport not supported, rebuild Git with curl support"
-                       exit 1
+                       die "http transport not supported, rebuild Git with curl support"
                fi
                ;;
        *)
                case "$upload_pack" in
                '') git-fetch-pack --all -k $quiet "$repo" ;;
                *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;;
-               esac >"$GIT_DIR/CLONE_HEAD" || {
-                       echo >&2 "fetch-pack from '$repo' failed."
-                       exit 1
-               }
+               esac >"$GIT_DIR/CLONE_HEAD" ||
+                       die "fetch-pack from '$repo' failed."
                ;;
        esac
        ;;
index 5b1cf85825b0c69f2515445842812a0caf48ef10..81c3a0cb6152b02ccdcaed607efcd500813ce607 100755 (executable)
@@ -441,7 +441,7 @@ then
 elif test "$use_commit" != ""
 then
        git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
-elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
+elif test -f "$GIT_DIR/MERGE_MSG"
 then
        cat "$GIT_DIR/MERGE_MSG"
 elif test -f "$GIT_DIR/SQUASH_MSG"
@@ -522,15 +522,15 @@ then
                PARENTS=$(git-cat-file commit HEAD |
                        sed -n -e '/^$/q' -e 's/^parent /-p /p')
        fi
-       current=$(git-rev-parse --verify HEAD)
+       current="$(git-rev-parse --verify HEAD)"
 else
        if [ -z "$(git-ls-files)" ]; then
                echo >&2 Nothing to commit
                exit 1
        fi
        PARENTS=""
-       current=
        rloga='commit (initial)'
+       current=''
 fi
 
 if test -z "$no_edit"
@@ -606,8 +606,8 @@ then
        fi &&
        commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
        rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
-       git-update-ref -m "$rloga: $rlogm" HEAD $commit $current &&
-       rm -f -- "$GIT_DIR/MERGE_HEAD" &&
+       git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" &&
+       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
        if test -f "$NEXT_INDEX"
        then
                mv "$NEXT_INDEX" "$THIS_INDEX"
index e5a00a12857036d01e2e0dd18510df511378854f..14e2c6131b1db10690a7ebe0a894651ea6195b7e 100755 (executable)
@@ -495,22 +495,17 @@ ($$)
        $tip_at_start = `git-rev-parse --verify HEAD`;
 
        # Get the last import timestamps
-       opendir(D,"$git_dir/refs/heads");
-       while(defined(my $head = readdir(D))) {
-               next if $head =~ /^\./;
-               open(F,"$git_dir/refs/heads/$head")
-                       or die "Bad head branch: $head: $!\n";
-               chomp(my $ftag = <F>);
-               close(F);
-               open(F,"git-cat-file commit $ftag |");
-               while(<F>) {
-                       next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
-                       $branch_date{$head} = $1;
-                       last;
-               }
-               close(F);
+       my $fmt = '($ref, $author) = (%(refname), %(author));';
+       open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
+               die "Cannot run git-for-each-ref: $!\n";
+       while(defined(my $entry = <H>)) {
+               my ($ref, $author);
+               eval($entry) || die "cannot eval refs list: $@";
+               my ($head) = ($ref =~ m|^refs/heads/(.*)|);
+               $author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+               $branch_date{$head} = $1;
        }
-       closedir(D);
+       close(H);
 }
 
 -d $git_dir
index 08ad831a3900d4ac0bc04b4e9bd371ffcb54e1ae..8817f8bb4f7c6b69d186c70d83cfb49b1fcea80e 100755 (executable)
@@ -2118,9 +2118,17 @@ sub new
                 mode       TEXT NOT NULL
             )
         ");
+        $self->{dbh}->do("
+            CREATE INDEX revision_ix1
+            ON revision (name,revision)
+        ");
+        $self->{dbh}->do("
+            CREATE INDEX revision_ix2
+            ON revision (name,commithash)
+        ");
     }
 
-    # Construct the revision table if required
+    # Construct the head table if required
     unless ( $self->{tables}{head} )
     {
         $self->{dbh}->do("
@@ -2134,6 +2142,10 @@ sub new
                 mode       TEXT NOT NULL
             )
         ");
+        $self->{dbh}->do("
+            CREATE INDEX head_ix1
+            ON head (name)
+        ");
     }
 
     # Construct the properties table if required
index a674c8c5744c5f8b3826336fc9db4e53fd0942b3..539dff6ee05f7cfde20ea34a80a1e6bbdc1fbca2 100755 (executable)
@@ -129,38 +129,44 @@ append_fetch_head () {
     then
        headc_=$(git-rev-parse --verify "$head_^0") || exit
        echo "$headc_   $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
-       [ "$verbose" ] && echo >&2 "* committish: $head_"
-       [ "$verbose" ] && echo >&2 "  $note_"
     else
        echo "$head_    not-for-merge   $note_" >>"$GIT_DIR/FETCH_HEAD"
-       [ "$verbose" ] && echo >&2 "* non-commit: $head_"
-       [ "$verbose" ] && echo >&2 "  $note_"
-    fi
-    if test "$local_name_" != ""
-    then
-       # We are storing the head locally.  Make sure that it is
-       # a fast forward (aka "reverse push").
-       fast_forward_local "$local_name_" "$head_" "$note_"
     fi
+
+    update_local_ref "$local_name_" "$head_" "$note_"
 }
 
-fast_forward_local () {
-    mkdir -p "$(dirname "$GIT_DIR/$1")"
+update_local_ref () {
+    # If we are storing the head locally make sure that it is
+    # a fast forward (aka "reverse push").
+
+    label_=$(git-cat-file -t $2)
+    newshort_=$(git-rev-parse --short $2)
+    if test -z "$1" ; then
+       [ "$verbose" ] && echo >&2 "* fetched $3"
+       [ "$verbose" ] && echo >&2 "  $label_: $newshort_"
+       return 0
+    fi
+    oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null)
+
     case "$1" in
     refs/tags/*)
        # Tags need not be pointing at commits so there
        # is no way to guarantee "fast-forward" anyway.
-       if test -f "$GIT_DIR/$1"
+       if test -n "$oldshort_"
        then
-               if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+               if now_=$(git show-ref --hash "$1") && test "$now_" = "$2"
                then
-                       [ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
+                       [ "$verbose" ] && echo >&2 "* $1: same as $3"
+                       [ "$verbose" ] && echo >&2 "  $label_: $newshort_" ||:
                else
                        echo >&2 "* $1: updating with $3"
+                       echo >&2 "  $label_: $newshort_"
                        git-update-ref -m "$rloga: updating tag" "$1" "$2"
                fi
        else
                echo >&2 "* $1: storing $3"
+               echo >&2 "  $label_: $newshort_"
                git-update-ref -m "$rloga: storing tag" "$1" "$2"
        fi
        ;;
@@ -178,31 +184,34 @@ fast_forward_local () {
                if test -n "$verbose"
                then
                        echo >&2 "* $1: same as $3"
+                       echo >&2 "  $label_: $newshort_"
                fi
                ;;
            *,$local)
                echo >&2 "* $1: fast forward to $3"
-               echo >&2 "  from $local to $2"
+               echo >&2 "  old..new: $oldshort_..$newshort_"
                git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
                ;;
            *)
                false
                ;;
            esac || {
-               echo >&2 "* $1: does not fast forward to $3;"
                case ",$force,$single_force," in
                *,t,*)
-                       echo >&2 "  forcing update."
+                       echo >&2 "* $1: forcing update to non-fast forward $3"
+                       echo >&2 "  old...new: $oldshort_...$newshort_"
                        git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
                        ;;
                *)
-                       echo >&2 "  not updating."
+                       echo >&2 "* $1: not updating to non-fast forward $3"
+                       echo >&2 "  old...new: $oldshort_...$newshort_"
                        exit 1
                        ;;
                esac
            }
        else
            echo >&2 "* $1: storing $3"
+           echo >&2 "  $label_: $newshort_"
            git-update-ref -m "$rloga: storing head" "$1" "$2"
        fi
        ;;
@@ -418,7 +427,7 @@ case "$no_tags$tags" in
                sed -ne 's|^\([0-9a-f]*\)[      ]\(refs/tags/.*\)^{}$|\1 \2|p' |
                while read sha1 name
                do
-                       test -f "$GIT_DIR/$name" && continue
+                       git-show-ref --verify --quiet -- $name && continue
                        git-check-ref-format "$name" || {
                                echo >&2 "warning: tag ${name} ignored"
                                continue
index 789f4de595d2657c9c348b4d52b47e2c42543ad2..cb094388bb5c830d37a790f5e7bbf13e8c61352a 100755 (executable)
@@ -199,7 +199,7 @@ f,*)
        ;;
 ?,1,"$head",*)
        # Again the most common case of merging one remote.
-       echo "Updating from $head to $1"
+       echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
        git-update-index --refresh 2>/dev/null
        new_head=$(git-rev-parse --verify "$1^0") &&
        git-read-tree -u -v -m $head "$new_head" &&
index a7373c0532fad447263e7199d0b8ec2908c683c9..546fa446fc3c6c63488b9ef38cb5bdc960953cb1 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--onto <newbase>] <upstream> [<branch>]'
+USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -39,6 +39,7 @@ strategy=recursive
 do_merge=
 dotest=$GIT_DIR/.dotest-merge
 prec=4
+verbose=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -190,6 +191,9 @@ do
                esac
                do_merge=t
                ;;
+       -v|--verbose)
+               verbose=t
+               ;;
        -*)
                usage
                ;;
@@ -273,6 +277,12 @@ then
        exit 0
 fi
 
+if test -n "$verbose"
+then
+       echo "Changes from $mb to $onto:"
+       git-diff-tree --stat --summary "$mb" "$onto"
+fi
+
 # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
 git-reset --hard "$onto"
 
@@ -286,7 +296,7 @@ fi
 
 if test -z "$do_merge"
 then
-       git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
+       git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
        git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
                --reflog-action=rebase
        exit $?
index f2c9071d1109e014832f0efd8a1fd67dca44c8af..17e24526c279467891389295a55f8f257ca0d01b 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 #
 
-USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]'
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
@@ -25,6 +25,15 @@ do
        shift
 done
 
+# Later we will default repack.UseDeltaBaseOffset to true
+default_dbo=false
+
+case "`git repo-config --bool repack.usedeltabaseoffset ||
+       echo $default_dbo`" in
+true)
+       extra="$extra --delta-base-offset" ;;
+esac
+
 PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
 PACKTMP="$GIT_DIR/.tmp-$$-pack"
 rm -f "$PACKTMP"-*
index 729ec65dc9e0ddebfe81fb4d837a5f9c83b537ee..36b90e38494eb79b4859cf59301b5a4e6ccccea1 100755 (executable)
@@ -46,7 +46,7 @@ case "$common" in
        exit 0
        ;;
 "$head")
-       echo "Updating from $head to $merge"
+       echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)"
        git-read-tree -u -m $head $merge || exit 1
        git-update-ref -m "resolve $merge_name: Fast forward" \
                HEAD "$merge" "$head"
index 4fd81b6ed60e5877df85c1052057b0ad5410f4fe..6eab3c72df0db0994a352836018a7fc35ba1eae3 100755 (executable)
@@ -145,9 +145,18 @@ git-read-tree -m -u --aggressive $base $head $next &&
 result=$(git-write-tree 2>/dev/null) || {
     echo >&2 "Simple $me fails; trying Automatic $me."
     git-merge-index -o git-merge-one-file -a || {
+           mv -f .msg "$GIT_DIR/MERGE_MSG"
+           {
+               echo '
+Conflicts:
+'
+               git ls-files --unmerged |
+               sed -e 's/^[^   ]*      /       /' |
+               uniq
+           } >>"$GIT_DIR/MERGE_MSG"
            echo >&2 "Automatic $me failed.  After resolving the conflicts,"
            echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
-           echo >&2 "and commit with 'git commit -F .msg'"
+           echo >&2 "and commit the result."
            case "$me" in
            cherry-pick)
                echo >&2 "You may choose to use the following when making"
index 3f50abaeb6901772b22e6ca2c1e87b4bd92f3b92..4c87c20c158fe3edfd0b770f1855e67e20038e3b 100755 (executable)
@@ -83,11 +83,12 @@ sub format_2822_time {
 my $compose_filename = ".msg.$$";
 
 # Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,@bcclist,
+my (@to,@cc,@initial_cc,@bcclist,@xh,
        $initial_reply_to,$initial_subject,@files,$from,$compose,$time);
 
 # Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
+       $dry_run) = (1, 0, 0, 0, 0);
 my $smtp_server;
 
 # Example reply to:
@@ -116,6 +117,7 @@ sub format_2822_time {
                    "quiet" => \$quiet,
                    "suppress-from" => \$suppress_from,
                    "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+                   "dry-run" => \$dry_run,
         );
 
 # Verify the user input
@@ -228,6 +230,9 @@ sub expand_aliases {
        $initial_reply_to =~ s/(^\s+|\s+$)//g;
 }
 
+if (!$smtp_server) {
+       $smtp_server = $repo->config('sendemail.smtpserver');
+}
 if (!$smtp_server) {
        foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
                if (-x $_) {
@@ -409,6 +414,11 @@ sub send_message
            $gitversion = Git::version();
        }
 
+       my ($author_name) = ($from =~ /^(.*?)\s+</);
+       if ($author_name && $author_name =~ /\./ && $author_name !~ /^".*"$/) {
+               my ($name, $addr) = ($from =~ /^(.*?)(\s+<.*)/);
+               $from = "\"$name\"$addr";
+       }
        my $header = "From: $from
 To: $to
 Cc: $cc
@@ -422,8 +432,13 @@ sub send_message
                $header .= "In-Reply-To: $reply_to\n";
                $header .= "References: $references\n";
        }
+       if (@xh) {
+               $header .= join("\n", @xh) . "\n";
+       }
 
-       if ($smtp_server =~ m#^/#) {
+       if ($dry_run) {
+               # We don't want to send the email.
+       } elsif ($smtp_server =~ m#^/#) {
                my $pid = open my $sm, '|-';
                defined $pid or die $!;
                if (!$pid) {
@@ -472,15 +487,22 @@ sub send_message
 
        my $author_not_sender = undef;
        @cc = @initial_cc;
-       my $found_mbox = 0;
+       @xh = ();
+       my $input_format = undef;
        my $header_done = 0;
        $message = "";
        while(<F>) {
                if (!$header_done) {
-                       $found_mbox = 1, next if (/^From /);
+                       if (/^From /) {
+                               $input_format = 'mbox';
+                               next;
+                       }
                        chomp;
+                       if (!defined $input_format && /^[-A-Za-z]+:\s/) {
+                               $input_format = 'mbox';
+                       }
 
-                       if ($found_mbox) {
+                       if (defined $input_format && $input_format eq 'mbox') {
                                if (/^Subject:\s+(.*)$/) {
                                        $subject = $1;
 
@@ -495,6 +517,9 @@ sub send_message
                                                $2, $_) unless $quiet;
                                        push @cc, $2;
                                }
+                               elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
+                                       push @xh, $_;
+                               }
 
                        } else {
                                # In the traditional
@@ -502,6 +527,7 @@ sub send_message
                                # line 1 = cc
                                # line 2 = subject
                                # So let's support that, too.
+                               $input_format = 'lots';
                                if (@cc == 0) {
                                        printf("(non-mbox) Adding cc: %s from line '%s'\n",
                                                $_, $_) unless $quiet;
index aca0e4f64ebb467cf5ba69dfbfaa55a4da6e6d3d..cbaa8ab37c78769002e10947d7ca08fc440e552f 100755 (executable)
@@ -31,7 +31,7 @@
 $ENV{'TZ'}="UTC";
 
 our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
-    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F);
+    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,$opt_P);
 
 sub usage() {
        print STDERR <<END;
@@ -39,17 +39,19 @@ ()
        [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
        [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
        [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
-       [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL]
+       [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
 END
        exit(1);
 }
 
-getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage();
+getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:uv") or usage();
 usage if $opt_h;
 
 my $tag_name = $opt_t || "tags";
 my $trunk_name = $opt_T || "trunk";
 my $branch_name = $opt_b || "branches";
+my $project_name = $opt_P || "";
+$project_name = "/" . $project_name if ($project_name);
 
 @ARGV == 1 or @ARGV == 2 or usage();
 
@@ -193,6 +195,13 @@ sub ignore {
        }
 }
 
+sub dir_list {
+       my($self,$path,$rev) = @_;
+       my ($dirents,undef,$properties)
+           = $self->{'svn'}->get_dir($path,$rev,undef);
+       return $dirents;
+}
+
 package main;
 use URI;
 
@@ -342,35 +351,16 @@ ($$)
 
 open BRANCHES,">>", "$git_dir/svn2git";
 
-sub node_kind($$$) {
-       my ($branch, $path, $revision) = @_;
+sub node_kind($$) {
+       my ($svnpath, $revision) = @_;
        my $pool=SVN::Pool->new;
-       my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
+       my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool);
        $pool->clear;
        return $kind;
 }
 
-sub revert_split_path($$) {
-       my($branch,$path) = @_;
-
-       my $svnpath;
-       $path = "" if $path eq "/"; # this should not happen, but ...
-       if($branch eq "/") {
-               $svnpath = "$trunk_name/$path";
-       } elsif($branch =~ m#^/#) {
-               $svnpath = "$tag_name$branch/$path";
-       } else {
-               $svnpath = "$branch_name/$branch/$path";
-       }
-
-       $svnpath =~ s#/+$##;
-       return $svnpath;
-}
-
 sub get_file($$$) {
-       my($rev,$branch,$path) = @_;
-
-       my $svnpath = revert_split_path($branch,$path);
+       my($svnpath,$rev,$path) = @_;
 
        # now get it
        my ($name,$mode);
@@ -413,10 +403,9 @@ ($$$)
 }
 
 sub get_ignore($$$$$) {
-       my($new,$old,$rev,$branch,$path) = @_;
+       my($new,$old,$rev,$path,$svnpath) = @_;
 
        return unless $opt_I;
-       my $svnpath = revert_split_path($branch,$path);
        my $name = $svn->ignore("$svnpath",$rev);
        if ($path eq '/') {
                $path = $opt_I;
@@ -435,11 +424,25 @@ ($$$$$)
                close $F;
                unlink $name;
                push(@$new,['0644',$sha,$path]);
-       } else {
+       } elsif (defined $old) {
                push(@$old,$path);
        }
 }
 
+sub project_path($$)
+{
+       my ($path, $project) = @_;
+
+       $path = "/".$path unless ($path =~ m#^\/#) ;
+       return $1 if ($path =~ m#^$project\/(.*)$#);
+
+       $path =~ s#\.#\\\.#g;
+       $path =~ s#\+#\\\+#g;
+       return "/" if ($project =~ m#^$path.*$#);
+
+       return undef;
+}
+
 sub split_path($$) {
        my($rev,$path) = @_;
        my $branch;
@@ -459,7 +462,11 @@ ($$)
                print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
                return ()
        }
-       $path = "/" if $path eq "";
+       if ($path eq "") {
+               $path = "/";
+       } elsif ($project_name) {
+               $path = project_path($path, $project_name);
+       }
        return ($branch,$path);
 }
 
@@ -480,6 +487,27 @@ ($$)
        return $therev;
 }
 
+sub expand_svndir($$$);
+
+sub expand_svndir($$$)
+{
+       my ($svnpath, $rev, $path) = @_;
+       my @list;
+       get_ignore(\@list, undef, $rev, $path, $svnpath);
+       my $dirents = $svn->dir_list($svnpath, $rev);
+       foreach my $p(keys %$dirents) {
+               my $kind = node_kind($svnpath.'/'.$p, $rev);
+               if ($kind eq $SVN::Node::file) {
+                       my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
+                       push(@list, $f) if $f;
+               } elsif ($kind eq $SVN::Node::dir) {
+                       push(@list,
+                            expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
+               }
+       }
+       return @list;
+}
+
 sub copy_path($$$$$$$$) {
        # Somebody copied a whole subdirectory.
        # We need to find the index entries from the old version which the
@@ -488,8 +516,11 @@ ($$$$$$$$)
        my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
 
        my($srcbranch,$srcpath) = split_path($rev,$oldpath);
-       unless(defined $srcbranch) {
-               print "Path not found when copying from $oldpath @ $rev\n";
+       unless(defined $srcbranch && defined $srcpath) {
+               print "Path not found when copying from $oldpath @ $rev.\n".
+                       "Will try to copy from original SVN location...\n"
+                       if $opt_v;
+               push (@$new, expand_svndir($oldpath, $rev, $path));
                return;
        }
        my $therev = branch_rev($srcbranch, $rev);
@@ -503,7 +534,7 @@ ($$$$$$$$)
        }
        print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
        if ($node_kind eq $SVN::Node::dir) {
-                       $srcpath =~ s#/*$#/#;
+               $srcpath =~ s#/*$#/#;
        }
        
        my $pid = open my $f,'-|';
@@ -582,10 +613,12 @@ sub commit {
                if(defined $oldpath) {
                        my $p;
                        ($parent,$p) = split_path($revision,$oldpath);
-                       if($parent eq "/") {
-                               $parent = $opt_o;
-                       } else {
-                               $parent =~ s#^/##; # if it's a tag
+                       if(defined $parent) {
+                               if($parent eq "/") {
+                                       $parent = $opt_o;
+                               } else {
+                                       $parent =~ s#^/##; # if it's a tag
+                               }
                        }
                } else {
                        $parent = undef;
@@ -651,9 +684,10 @@ sub commit {
                                push(@old,$path); # remove any old stuff
                        }
                        if(($action->[0] eq "A") || ($action->[0] eq "R")) {
-                               my $node_kind = node_kind($branch,$path,$revision);
+                               my $node_kind = node_kind($action->[3], $revision);
                                if ($node_kind eq $SVN::Node::file) {
-                                       my $f = get_file($revision,$branch,$path);
+                                       my $f = get_file($action->[3],
+                                                        $revision, $path);
                                        if ($f) {
                                                push(@new,$f) if $f;
                                        } else {
@@ -668,19 +702,20 @@ sub commit {
                                                          \@new, \@parents);
                                        } else {
                                                get_ignore(\@new, \@old, $revision,
-                                                          $branch, $path);
+                                                          $path, $action->[3]);
                                        }
                                }
                        } elsif ($action->[0] eq "D") {
                                push(@old,$path);
                        } elsif ($action->[0] eq "M") {
-                               my $node_kind = node_kind($branch,$path,$revision);
+                               my $node_kind = node_kind($action->[3], $revision);
                                if ($node_kind eq $SVN::Node::file) {
-                                       my $f = get_file($revision,$branch,$path);
+                                       my $f = get_file($action->[3],
+                                                        $revision, $path);
                                        push(@new,$f) if $f;
                                } elsif ($node_kind eq $SVN::Node::dir) {
                                        get_ignore(\@new, \@old, $revision,
-                                                  $branch,$path);
+                                                  $path, $action->[3]);
                                }
                        } else {
                                die "$revision: unknown action '".$action->[0]."' for $path\n";
@@ -883,6 +918,7 @@ sub commit_all {
        while(my($path,$action) = each %$changed_paths) {
                ($branch,$path) = split_path($revision,$path);
                next if not defined $branch;
+               next if not defined $path;
                $done{$branch}{$path} = $action;
        }
        while(($branch,$changed_paths) = each %done) {
index a0afa25821b5438f41abfed3a2f27bd22767c3d6..ac269e3277d9b8c844e8c43b95695375dd829746 100755 (executable)
@@ -47,8 +47,10 @@ do
     -d)
        shift
        tag_name="$1"
-       rm "$GIT_DIR/refs/tags/$tag_name" && \
-               echo "Deleted tag $tag_name."
+       tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") ||
+               die "Seriously, what tag are you talking about?"
+       git-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" &&
+               echo "Deleted tag $tag_name."
        exit $?
        ;;
     -*)
@@ -63,8 +65,11 @@ done
 
 name="$1"
 [ "$name" ] || usage
-if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
-    die "tag '$name' already exists"
+prev=0000000000000000000000000000000000000000
+if git-show-ref --verify --quiet -- "refs/tags/$name"
+then
+    test -n "$force" || die "tag '$name' already exists"
+    prev=`git rev-parse "refs/tags/$name"`
 fi
 shift
 git-check-ref-format "tags/$name" ||
@@ -107,6 +112,5 @@ if [ "$annotate" ]; then
     object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
 fi
 
-leading=`expr "refs/tags/$name" : '\(.*\)/'` &&
-mkdir -p "$GIT_DIR/$leading" &&
-echo $object > "$GIT_DIR/refs/tags/$name"
+git update-ref "refs/tags/$name" "$object" "$prev"
+
diff --git a/git.c b/git.c
index e089b53571cd8776ab208fe14b87af53b7057739..d2460c861885a50df9ff8b3acb11c1b45bcc366e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -219,11 +219,14 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                int option;
        } commands[] = {
                { "add", cmd_add, RUN_SETUP },
+               { "annotate", cmd_annotate, },
                { "apply", cmd_apply },
                { "archive", cmd_archive },
+               { "branch", cmd_branch, RUN_SETUP },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout-index", cmd_checkout_index, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
+               { "cherry", cmd_cherry, RUN_SETUP },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "diff", cmd_diff, RUN_SETUP | USE_PAGER },
@@ -232,6 +235,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "diff-stages", cmd_diff_stages, RUN_SETUP },
                { "diff-tree", cmd_diff_tree, RUN_SETUP },
                { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
+               { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
                { "format-patch", cmd_format_patch, RUN_SETUP },
                { "get-tar-commit-id", cmd_get_tar_commit_id },
                { "grep", cmd_grep, RUN_SETUP },
@@ -267,6 +271,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
                { "write-tree", cmd_write_tree, RUN_SETUP },
                { "verify-pack", cmd_verify_pack },
+               { "show-ref", cmd_show_ref, RUN_SETUP },
+               { "pack-refs", cmd_pack_refs, RUN_SETUP },
        };
        int i;
 
index 78e6fc05f3dbbc274f7f1523e861799a4161f379..e02e90f0429be0d2a69b76571101f20b8f75530f 100644 (file)
@@ -26,12 +26,26 @@ You can specify the following configuration variables when building GIT:
  * GITWEB_LOGO
    Points to the location where you put git-logo.png on your web server.
  * GITWEB_CONFIG
-   This file will be loaded using 'require'.  If the environment
+   This file will be loaded using 'require' and can be used to override any
+   of the options above as well as some other options - see the top of
+   'gitweb.cgi' for their full list and description.  If the environment
    $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the
    environment variable will be loaded instead of the file
    specified when gitweb.cgi was created.
 
 
+Runtime gitweb configuration
+----------------------------
+
+You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG`
+(defaults to 'gitweb_config.perl' in the same directory as the CGI).
+See the top of 'gitweb.cgi' for the list of variables and some description.
+The most notable thing that is not configurable at compile time are the
+optional features, stored in the '%features' variable. You can find further
+description on how to reconfigure the default features setting in your
+`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'.
+
+
 Webserver configuration
 -----------------------
 
index 3f62b6d752a82d33de2b3ad2a67a384819dbca2a..0eda98237e1ea56936b8cd48a6c095090eced7fd 100644 (file)
@@ -333,6 +333,8 @@ div.index_include {
 }
 
 div.search {
+       font-size: 12px;
+       font-weight: normal;
        margin: 4px 8px;
        position: absolute;
        top: 56px;
index 05e7b1253bcd6d92bdd8482e1943ab80b95aacac..bf5f8299442ffe3ad8c4de6629af79667a67e5e2 100755 (executable)
 
 # name of your site or organization to appear in page titles
 # replace this with something more descriptive for clearer bookmarks
-our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
+our $site_name = "++GITWEB_SITENAME++"
+                 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
 
+# filename of html text to include at top of each page
+our $site_header = "++GITWEB_SITE_HEADER++";
 # html text to include at home page
 our $home_text = "++GITWEB_HOMETEXT++";
+# filename of html text to include at bottom of each page
+our $site_footer = "++GITWEB_SITE_FOOTER++";
 
-# URI of default stylesheet
-our $stylesheet = "++GITWEB_CSS++";
+# URI of stylesheets
+our @stylesheets = ("++GITWEB_CSS++");
+# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
+our $stylesheet = undef;
 # URI of GIT logo (72x27 size)
 our $logo = "++GITWEB_LOGO++";
 # URI of GIT favicon, assumed to be image/png type
        #
        # use gitweb_check_feature(<feature>) to check if <feature> is enabled
 
+       # Enable the 'blame' blob view, showing the last commit that modified
+       # each line in the file. This can be very CPU-intensive.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'blame'}{'default'} = [1];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'blame'}{'override'} = 1;
+       # and in project config gitweb.blame = 0|1;
        'blame' => {
                'sub' => \&feature_blame,
                'override' => 0,
                'default' => [0]},
 
+       # Enable the 'snapshot' link, providing a compressed tarball of any
+       # tree. This can potentially generate high traffic if you have large
+       # project.
+
+       # To disable system wide have in $GITWEB_CONFIG
+       # $feature{'snapshot'}{'default'} = [undef];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'blame'}{'override'} = 1;
+       # and in project config gitweb.snapshot = none|gzip|bzip2;
        'snapshot' => {
                'sub' => \&feature_snapshot,
                'override' => 0,
                #         => [content-encoding, suffix, program]
                'default' => ['x-gzip', 'gz', 'gzip']},
 
+       # Enable the pickaxe search, which will list the commits that modified
+       # a given string in a file. This can be practical and quite faster
+       # alternative to 'blame', but still potentially CPU-intensive.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'pickaxe'}{'default'} = [1];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'pickaxe'}{'override'} = 1;
+       # and in project config gitweb.pickaxe = 0|1;
        'pickaxe' => {
                'sub' => \&feature_pickaxe,
                'override' => 0,
                'default' => [1]},
+
+       # Make gitweb use an alternative format of the URLs which can be
+       # more readable and natural-looking: project name is embedded
+       # directly in the path and the query string contains other
+       # auxiliary information. All gitweb installations recognize
+       # URL in either format; this configures in which formats gitweb
+       # generates links.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'pathinfo'}{'default'} = [1];
+       # Project specific override is not supported.
+
+       # Note that you will need to change the default location of CSS,
+       # favicon, logo and possibly other files to an absolute URL. Also,
+       # if gitweb.cgi serves as your indexfile, you will need to force
+       # $my_uri to contain the script name in your $GITWEB_CONFIG.
+       'pathinfo' => {
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_check_feature {
@@ -118,15 +170,13 @@ sub gitweb_check_feature {
                $feature{$name}{'override'},
                @{$feature{$name}{'default'}});
        if (!$override) { return @defaults; }
+       if (!defined $sub) {
+               warn "feature $name is not overrideable";
+               return @defaults;
+       }
        return $sub->(@defaults);
 }
 
-# To enable system wide have in $GITWEB_CONFIG
-# $feature{'blame'}{'default'} = [1];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'blame'}{'override'} = 1;
-# and in project config gitweb.blame = 0|1;
-
 sub feature_blame {
        my ($val) = git_get_project_config('blame', '--bool');
 
@@ -139,12 +189,6 @@ sub feature_blame {
        return $_[0];
 }
 
-# To disable system wide have in $GITWEB_CONFIG
-# $feature{'snapshot'}{'default'} = [undef];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'blame'}{'override'} = 1;
-# and in project config  gitweb.snapshot = none|gzip|bzip2
-
 sub feature_snapshot {
        my ($ctype, $suffix, $command) = @_;
 
@@ -168,12 +212,6 @@ sub gitweb_have_snapshot {
        return $have_snapshot;
 }
 
-# To enable system wide have in $GITWEB_CONFIG
-# $feature{'pickaxe'}{'default'} = [1];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'pickaxe'}{'override'} = 1;
-# and in project config gitweb.pickaxe = 0|1;
-
 sub feature_pickaxe {
        my ($val) = git_get_project_config('pickaxe', '--bool');
 
@@ -186,6 +224,22 @@ sub feature_pickaxe {
        return ($_[0]);
 }
 
+# checking HEAD file with -e is fragile if the repository was
+# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
+# and then pruned.
+sub check_head_link {
+       my ($dir) = @_;
+       my $headfile = "$dir/HEAD";
+       return ((-e $headfile) ||
+               (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
+}
+
+sub check_export_ok {
+       my ($dir) = @_;
+       return (check_head_link($dir) &&
+               (!$export_ok || -e "$dir/$export_ok"));
+}
+
 # rename detection options for git-diff and git-diff-tree
 # - default is '-M', with the cost proportional to
 #   (number of removed files) * (number of new files).
@@ -218,7 +272,7 @@ sub feature_pickaxe {
 if (defined $project) {
        if (!validate_pathname($project) ||
            !(-d "$projectroot/$project") ||
-           !(-e "$projectroot/$project/HEAD") ||
+           !check_head_link("$projectroot/$project") ||
            ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
            ($strict_export && !project_in_list($project))) {
                undef $project;
@@ -285,6 +339,13 @@ sub feature_pickaxe {
        $searchtext = quotemeta $searchtext;
 }
 
+our $searchtype = $cgi->param('st');
+if (defined $searchtype) {
+       if ($searchtype =~ m/[^a-z]/) {
+               die_error(undef, "Invalid searchtype parameter");
+       }
+}
+
 # now read PATH_INFO and use it as alternative to parameters
 sub evaluate_path_info {
        return if defined $project;
@@ -295,7 +356,7 @@ sub evaluate_path_info {
        # find which part of PATH_INFO is project
        $project = $path_info;
        $project =~ s,/+$,,;
-       while ($project && !-e "$projectroot/$project/HEAD") {
+       while ($project && !check_head_link("$projectroot/$project")) {
                $project =~ s,/*[^/]*$,,;
        }
        # validate project
@@ -349,6 +410,7 @@ sub evaluate_path_info {
        "log" => \&git_log,
        "rss" => \&git_rss,
        "search" => \&git_search,
+       "search_help" => \&git_search_help,
        "shortlog" => \&git_shortlog,
        "summary" => \&git_summary,
        "tag" => \&git_tag,
@@ -381,6 +443,10 @@ sub evaluate_path_info {
 
 sub href(%) {
        my %params = @_;
+       my $href = $my_uri;
+
+       # XXX: Warning: If you touch this, check the search form for updating,
+       # too.
 
        my @mapping = (
                project => "p",
@@ -394,11 +460,25 @@ (%)
                page => "pg",
                order => "o",
                searchtext => "s",
+               searchtype => "st",
        );
        my %mapping = @mapping;
 
        $params{'project'} = $project unless exists $params{'project'};
 
+       my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+       if ($use_pathinfo) {
+               # use PATH_INFO for project name
+               $href .= "/$params{'project'}" if defined $params{'project'};
+               delete $params{'project'};
+
+               # Summary just uses the project path URL
+               if (defined $params{'action'} && $params{'action'} eq 'summary') {
+                       delete $params{'action'};
+               }
+       }
+
+       # now encode the parameters explicitly
        my @result = ();
        for (my $i = 0; $i < @mapping; $i += 2) {
                my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
@@ -406,7 +486,9 @@ (%)
                        push @result, $symbol . "=" . esc_param($params{$name});
                }
        }
-       return "$my_uri?" . join(';', @result);
+       $href .= "?" . join(';', @result) if scalar @result;
+
+       return $href;
 }
 
 
@@ -778,7 +860,7 @@ sub git_get_hash_by_path {
        close $fd or return undef;
 
        #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
        if (defined $type && $type ne $2) {
                # type doesn't match
                return undef;
@@ -828,8 +910,7 @@ sub git_get_projects_list {
 
                                my $subdir = substr($File::Find::name, $pfxlen + 1);
                                # we check related file in $projectroot
-                               if (-e "$projectroot/$subdir/HEAD" && (!$export_ok ||
-                                   -e "$projectroot/$subdir/$export_ok")) {
+                               if (check_export_ok("$projectroot/$subdir")) {
                                        push @list, { path => $subdir };
                                        $File::Find::prune = 1;
                                }
@@ -850,8 +931,7 @@ sub git_get_projects_list {
                        if (!defined $path) {
                                next;
                        }
-                       if (-e "$projectroot/$path/HEAD" && (!$export_ok ||
-                           -e "$projectroot/$path/$export_ok")) {
+                       if (check_export_ok("$projectroot/$path")) {
                                my $pr = {
                                        path => $path,
                                        owner => to_utf8($owner),
@@ -896,6 +976,24 @@ sub git_get_project_owner {
        return $owner;
 }
 
+sub git_get_last_activity {
+       my ($path) = @_;
+       my $fd;
+
+       $git_dir = "$projectroot/$path";
+       open($fd, "-|", git_cmd(), 'for-each-ref',
+            '--format=%(refname) %(committer)',
+            '--sort=-committerdate',
+            'refs/heads') or return;
+       my $most_recent = <$fd>;
+       close $fd or return;
+       if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+               my $timestamp = $1;
+               my $age = time - $timestamp;
+               return ($age, age_string($age));
+       }
+}
+
 sub git_get_references {
        my $type = shift || "";
        my %refs;
@@ -961,6 +1059,9 @@ sub parse_date {
        $date{'hour_local'} = $hour;
        $date{'minute_local'} = $min;
        $date{'tz_local'} = $tz;
+       $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
+                                  1900+$year, $mon+1, $mday,
+                                  $hour, $min, $sec, $tz);
        return %date;
 }
 
@@ -1010,7 +1111,9 @@ sub parse_commit {
                @commit_lines = @$commit_text;
        } else {
                local $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id
+               open my $fd, "-|", git_cmd(), "rev-list",
+                       "--header", "--parents", "--max-count=1",
+                       $commit_id, "--"
                        or return;
                @commit_lines = split '\n', <$fd>;
                close $fd or return;
@@ -1174,7 +1277,7 @@ ($;%)
        my %res;
 
        #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
 
        $res{'mode'} = $1;
        $res{'type'} = $2;
@@ -1328,7 +1431,7 @@ sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
 
-       my $title = "$site_name git";
+       my $title = "$site_name";
        if (defined $project) {
                $title .= " - $project";
                if (defined $action) {
@@ -1366,8 +1469,17 @@ sub git_header_html {
 <meta name="generator" content="gitweb/$version git/$git_version"/>
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
 EOF
+# print out each stylesheet that exist
+       if (defined $stylesheet) {
+#provides backwards capability for those people who define style sheet in a config file
+               print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+       } else {
+               foreach my $stylesheet (@stylesheets) {
+                       next unless $stylesheet;
+                       print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+               }
+       }
        if (defined $project) {
                printf('<link rel="alternate" title="%s log" '.
                       'href="%s" type="application/rss+xml"/>'."\n",
@@ -1385,8 +1497,15 @@ sub git_header_html {
        }
 
        print "</head>\n" .
-             "<body>\n" .
-             "<div class=\"page_header\">\n" .
+             "<body>\n";
+
+       if (-f $site_header) {
+               open (my $fd, $site_header);
+               print <$fd>;
+               close $fd;
+       }
+
+       print "<div class=\"page_header\">\n" .
              $cgi->a({-href => esc_url($logo_url),
                       -title => $logo_label},
                      qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
@@ -1410,11 +1529,16 @@ sub git_header_html {
                }
                $cgi->param("a", "search");
                $cgi->param("h", $search_hash);
+               $cgi->param("p", $project);
                print $cgi->startform(-method => "get", -action => $my_uri) .
                      "<div class=\"search\">\n" .
                      $cgi->hidden(-name => "p") . "\n" .
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
+                     $cgi->popup_menu(-name => 'st', -default => 'commit',
+                                      -values => ['commit', 'author', 'committer', 'pickaxe']) .
+                     $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+                     " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
                      "</div>" .
                      $cgi->end_form() . "\n";
@@ -1437,8 +1561,15 @@ sub git_footer_html {
                print $cgi->a({-href => href(project=>undef, action=>"project_index"),
                              -class => "rss_logo"}, "TXT") . "\n";
        }
-       print "</div>\n" .
-             "</body>\n" .
+       print "</div>\n" ;
+
+       if (-f $site_footer) {
+               open (my $fd, $site_footer);
+               print <$fd>;
+               close $fd;
+       }
+
+       print "</body>\n" .
              "</html>";
 }
 
@@ -1563,17 +1694,16 @@ sub git_print_page_path {
        my $type = shift;
        my $hb = shift;
 
-       if (!defined $name) {
-               print "<div class=\"page_path\">/</div>\n";
-       } else {
+
+       print "<div class=\"page_path\">";
+       print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+                     -title => 'tree root'}, "[$project]");
+       print " / ";
+       if (defined $name) {
                my @dirname = split '/', $name;
                my $basename = pop @dirname;
                my $fullname = '';
 
-               print "<div class=\"page_path\">";
-               print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-                             -title => 'tree root'}, "[$project]");
-               print " / ";
                foreach my $dir (@dirname) {
                        $fullname .= ($fullname ? '/' : '') . $dir;
                        print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
@@ -1589,11 +1719,12 @@ sub git_print_page_path {
                        print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
                                                     hash_base=>$hb),
                                      -title => $name}, esc_html($basename));
+                       print " / ";
                } else {
                        print esc_html($basename);
                }
-               print "<br/></div>\n";
        }
+       print "<br/></div>\n";
 }
 
 # sub git_print_log (\@;%) {
@@ -1646,15 +1777,6 @@ ($;%)
        }
 }
 
-sub git_print_simplified_log {
-       my $log = shift;
-       my $remove_title = shift;
-
-       git_print_log($log,
-               -final_empty_line=> 1,
-               -remove_title => $remove_title);
-}
-
 # print tree entry (row of git_tree), but without encompassing <tr> element
 sub git_print_tree_entry {
        my ($t, $basedir, $hash_base, $have_blame) = @_;
@@ -1673,16 +1795,18 @@ sub git_print_tree_entry {
                                               file_name=>"$basedir$t->{'name'}", %base_key),
                                -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
                print "<td class=\"link\">";
+               print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "blob");
                if ($have_blame) {
-                       print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+                       print " | " .
+                             $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
                                                           file_name=>"$basedir$t->{'name'}", %base_key)},
                                            "blame");
                }
                if (defined $hash_base) {
-                       if ($have_blame) {
-                               print " | ";
-                       }
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print " | " .
+                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
@@ -1699,8 +1823,12 @@ sub git_print_tree_entry {
                              esc_html($t->{'name'}));
                print "</td>\n";
                print "<td class=\"link\">";
+               print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "tree");
                if (defined $hash_base) {
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print " | " .
+                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
@@ -1783,6 +1911,9 @@ sub git_difftree_body {
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
                                                     file_name=>$diff{'file'})},
                                      "blame") . " | ";
@@ -1828,6 +1959,9 @@ sub git_difftree_body {
                                }
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
                                                     file_name=>$diff{'file'})},
                                      "blame") . " | ";
@@ -1868,6 +2002,9 @@ sub git_difftree_body {
                                }
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'from_file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
                                                     file_name=>$diff{'from_file'})},
                                      "blame") . " | ";
@@ -2035,6 +2172,7 @@ sub git_shortlog_body {
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
                      "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
                if (gitweb_have_snapshot()) {
@@ -2229,16 +2367,11 @@ sub git_project_list {
                die_error(undef, "No projects found");
        }
        foreach my $pr (@list) {
-               my $head = git_get_head_hash($pr->{'path'});
-               if (!defined $head) {
-                       next;
-               }
-               $git_dir = "$projectroot/$pr->{'path'}";
-               my %co = parse_commit($head);
-               if (!%co) {
+               my (@aa) = git_get_last_activity($pr->{'path'});
+               unless (@aa) {
                        next;
                }
-               $pr->{'commit'} = \%co;
+               ($pr->{'age'}, $pr->{'age_string'}) = @aa;
                if (!defined $pr->{'descr'}) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
                        $pr->{'descr'} = chop_str($descr, 25, 5);
@@ -2288,7 +2421,7 @@ sub git_project_list {
                      "</th>\n";
        }
        if ($order eq "age") {
-               @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
+               @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
                print "<th>Last Change</th>\n";
        } else {
                print "<th>" .
@@ -2310,8 +2443,8 @@ sub git_project_list {
                                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
                      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
                      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-               print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
-                     $pr->{'commit'}{'age_string'} . "</td>\n" .
+               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+                     $pr->{'age_string'} . "</td>\n" .
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
@@ -2389,8 +2522,16 @@ sub git_summary {
        }
        print "</table>\n";
 
+       if (-s "$projectroot/$project/README.html") {
+               if (open my $fd, "$projectroot/$project/README.html") {
+                       print "<div class=\"title\">readme</div>\n";
+                       print $_ while (<$fd>);
+                       close $fd;
+               }
+       }
+
        open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
-               git_get_head_hash($project)
+               git_get_head_hash($project), "--"
                or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
@@ -2467,7 +2608,8 @@ sub git_blame2 {
        if ($ftype !~ "blob") {
                die_error("400 Bad Request", "Object is not a blob");
        }
-       open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base)
+       open ($fd, "-|", git_cmd(), "blame", '-p', '--',
+             $file_name, $hash_base)
                or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
@@ -2491,25 +2633,52 @@ sub git_blame2 {
 <table class="blame">
 <tr><th>Commit</th><th>Line</th><th>Data</th></tr>
 HTML
-       while (<$fd>) {
-               /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
-               my $full_rev = $1;
+       my %metainfo = ();
+       while (1) {
+               $_ = <$fd>;
+               last unless defined $_;
+               my ($full_rev, $orig_lineno, $lineno, $group_size) =
+                   /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+               if (!exists $metainfo{$full_rev}) {
+                       $metainfo{$full_rev} = {};
+               }
+               my $meta = $metainfo{$full_rev};
+               while (<$fd>) {
+                       last if (s/^\t//);
+                       if (/^(\S+) (.*)$/) {
+                               $meta->{$1} = $2;
+                       }
+               }
+               my $data = $_;
                my $rev = substr($full_rev, 0, 8);
-               my $lineno = $2;
-               my $data = $3;
-
-               if (!defined $last_rev) {
-                       $last_rev = $full_rev;
-               } elsif ($last_rev ne $full_rev) {
-                       $last_rev = $full_rev;
+               my $author = $meta->{'author'};
+               my %date = parse_date($meta->{'author-time'},
+                                     $meta->{'author-tz'});
+               my $date = $date{'iso-tz'};
+               if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
                }
                print "<tr class=\"$rev_color[$current_color]\">\n";
-               print "<td class=\"sha1\">" .
-                       $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)},
-                               esc_html($rev)) . "</td>\n";
-               print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" .
-                     esc_html($lineno) . "</a></td>\n";
+               if ($group_size) {
+                       print "<td class=\"sha1\"";
+                       print " title=\"". esc_html($author) . ", $date\"";
+                       print " rowspan=\"$group_size\"" if ($group_size > 1);
+                       print ">";
+                       print $cgi->a({-href => href(action=>"commit",
+                                                    hash=>$full_rev,
+                                                    file_name=>$file_name)},
+                                     esc_html($rev));
+                       print "</td>\n";
+               }
+               my $blamed = href(action => 'blame',
+                                 file_name => $meta->{'filename'},
+                                 hash_base => $full_rev);
+               print "<td class=\"linenr\">";
+               print $cgi->a({ -href => "$blamed#l$orig_lineno",
+                               -id => "l$lineno",
+                               -class => "linenr" },
+                             esc_html($lineno));
+               print "</td>";
                print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
                print "</tr>\n";
        }
@@ -2819,6 +2988,30 @@ sub git_tree {
        print "<div class=\"page_body\">\n";
        print "<table cellspacing=\"0\">\n";
        my $alternate = 1;
+       # '..' (top directory) link if possible
+       if (defined $hash_base &&
+           defined $file_name && $file_name =~ m![^/]+$!) {
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+
+               my $up = $file_name;
+               $up =~ s!/?[^/]+$!!;
+               undef $up unless $up;
+               # based on git_print_tree_entry
+               print '<td class="mode">' . mode_str('040000') . "</td>\n";
+               print '<td class="list">';
+               print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
+                                            file_name=>$up)},
+                             "..");
+               print "</td>\n";
+               print "<td class=\"link\"></td>\n";
+
+               print "</tr>\n";
+       }
        foreach my $line (@entries) {
                my %t = parse_ls_tree_line($line, -z => 1);
 
@@ -2881,7 +3074,7 @@ sub git_log {
        my $refs = git_get_references();
 
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash
+       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
                or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
@@ -2920,7 +3113,7 @@ sub git_log {
                      "</div>\n";
 
                print "<div class=\"log_body\">\n";
-               git_print_simplified_log($co{'comment'});
+               git_print_log($co{'comment'}, -final_empty_line=> 1);
                print "</div>\n";
        }
        git_footer_html();
@@ -2938,7 +3131,8 @@ sub git_commit {
        if (!defined $parent) {
                $parent = "--root";
        }
-       open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $parent, $hash
+       open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
+               @diff_opts, $parent, $hash, "--"
                or die_error(undef, "Open git-diff-tree failed");
        my @difftree = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading git-diff-tree failed");
@@ -3043,7 +3237,8 @@ sub git_blobdiff {
        if (defined $hash_base && defined $hash_parent_base) {
                if (defined $file_name) {
                        # read raw output
-                       open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base,
+                       open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+                               $hash_parent_base, $hash_base,
                                "--", $file_name
                                or die_error(undef, "Open git-diff-tree failed");
                        @difftree = map { chomp; $_ } <$fd>;
@@ -3057,7 +3252,8 @@ sub git_blobdiff {
                        # try to find filename from $hash
 
                        # read filtered raw output
-                       open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base
+                       open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+                               $hash_parent_base, $hash_base, "--"
                                or die_error(undef, "Open git-diff-tree failed");
                        @difftree =
                                # ':100644 100644 03b21826... 3b93d5e7... M     ls-files.c'
@@ -3127,7 +3323,8 @@ sub git_blobdiff {
                }
 
                # open patch output
-               open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, $hash_parent, $hash
+               open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts,
+                       $hash_parent, $hash, "--"
                        or die_error(undef, "Open git-diff failed");
        } else  {
                die_error('404 Not Found', "Missing one of the blob diff parameters")
@@ -3204,6 +3401,51 @@ sub git_commitdiff {
        if (!%co) {
                die_error(undef, "Unknown commit object");
        }
+
+       # we need to prepare $formats_nav before any parameter munging
+       my $formats_nav;
+       if ($format eq 'html') {
+               $formats_nav =
+                       $cgi->a({-href => href(action=>"commitdiff_plain",
+                                              hash=>$hash, hash_parent=>$hash_parent)},
+                               "raw");
+
+               if (defined $hash_parent) {
+                       # commitdiff with two commits given
+                       my $hash_parent_short = $hash_parent;
+                       if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
+                               $hash_parent_short = substr($hash_parent, 0, 7);
+                       }
+                       $formats_nav .=
+                               ' (from: ' .
+                               $cgi->a({-href => href(action=>"commitdiff",
+                                                      hash=>$hash_parent)},
+                                       esc_html($hash_parent_short)) .
+                               ')';
+               } elsif (!$co{'parent'}) {
+                       # --root commitdiff
+                       $formats_nav .= ' (initial)';
+               } elsif (scalar @{$co{'parents'}} == 1) {
+                       # single parent commit
+                       $formats_nav .=
+                               ' (parent: ' .
+                               $cgi->a({-href => href(action=>"commitdiff",
+                                                      hash=>$co{'parent'})},
+                                       esc_html(substr($co{'parent'}, 0, 7))) .
+                               ')';
+               } else {
+                       # merge commit
+                       $formats_nav .=
+                               ' (merge: ' .
+                               join(' ', map {
+                                       $cgi->a({-href => href(action=>"commitdiff",
+                                                              hash=>$_)},
+                                               esc_html(substr($_, 0, 7)));
+                               } @{$co{'parents'}} ) .
+                               ')';
+               }
+       }
+
        if (!defined $hash_parent) {
                $hash_parent = $co{'parent'} || '--root';
        }
@@ -3213,7 +3455,8 @@ sub git_commitdiff {
        my @difftree;
        if ($format eq 'html') {
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       "--patch-with-raw", "--full-index", $hash_parent, $hash
+                       "--no-commit-id", "--patch-with-raw", "--full-index",
+                       $hash_parent, $hash, "--"
                        or die_error(undef, "Open git-diff-tree failed");
 
                while (chomp(my $line = <$fd>)) {
@@ -3224,7 +3467,7 @@ sub git_commitdiff {
 
        } elsif ($format eq 'plain') {
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       '-p', $hash_parent, $hash
+                       '-p', $hash_parent, $hash, "--"
                        or die_error(undef, "Open git-diff-tree failed");
 
        } else {
@@ -3241,19 +3484,17 @@ sub git_commitdiff {
        if ($format eq 'html') {
                my $refs = git_get_references();
                my $ref = format_ref_marker($refs, $co{'id'});
-               my $formats_nav =
-                       $cgi->a({-href => href(action=>"commitdiff_plain",
-                                              hash=>$hash, hash_parent=>$hash_parent)},
-                               "raw");
 
                git_header_html(undef, $expires);
                git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
                git_print_authorship(\%co);
                print "<div class=\"page_body\">\n";
-               print "<div class=\"log\">\n";
-               git_print_simplified_log($co{'comment'}, 1); # skip title
-               print "</div>\n"; # class="log"
+               if (@{$co{'comment'}} > 1) {
+                       print "<div class=\"log\">\n";
+                       git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
+                       print "</div>\n"; # class="log"
+               }
 
        } elsif ($format eq 'plain') {
                my $refs = git_get_references("tags");
@@ -3385,18 +3626,8 @@ sub git_search {
                die_error(undef, "Unknown commit object");
        }
 
-       my $commit_search = 1;
-       my $author_search = 0;
-       my $committer_search = 0;
-       my $pickaxe_search = 0;
-       if ($searchtext =~ s/^author\\://i) {
-               $author_search = 1;
-       } elsif ($searchtext =~ s/^committer\\://i) {
-               $committer_search = 1;
-       } elsif ($searchtext =~ s/^pickaxe\\://i) {
-               $commit_search = 0;
-               $pickaxe_search = 1;
-
+       $searchtype ||= 'commit';
+       if ($searchtype eq 'pickaxe') {
                # pickaxe may take all resources of your box and run for several minutes
                # with every query - so decide by yourself how public you make this feature
                my ($have_pickaxe) = gitweb_check_feature('pickaxe');
@@ -3404,23 +3635,26 @@ sub git_search {
                        die_error('403 Permission denied', "Permission denied");
                }
        }
+
        git_header_html();
        git_print_page_nav('','', $hash,$co{'tree'},$hash);
        git_print_header_div('commit', esc_html($co{'title'}), $hash);
 
        print "<table cellspacing=\"0\">\n";
        my $alternate = 1;
-       if ($commit_search) {
+       if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
                $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next;
+               open my $fd, "-|", git_cmd(), "rev-list",
+                       "--header", "--parents", $hash, "--"
+                       or next;
                while (my $commit_text = <$fd>) {
                        if (!grep m/$searchtext/i, $commit_text) {
                                next;
                        }
-                       if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+                       if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
                                next;
                        }
-                       if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+                       if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
                                next;
                        }
                        my @commit_lines = split "\n", $commit_text;
@@ -3462,7 +3696,7 @@ sub git_search {
                close $fd;
        }
 
-       if ($pickaxe_search) {
+       if ($searchtype eq 'pickaxe') {
                $/ = "\n";
                my $git_command = git_cmd_str();
                open my $fd, "-|", "$git_command rev-list $hash | " .
@@ -3522,6 +3756,31 @@ sub git_search {
        git_footer_html();
 }
 
+sub git_search_help {
+       git_header_html();
+       git_print_page_nav('','', $hash,$hash,$hash);
+       print <<EOT;
+<dl>
+<dt><b>commit</b></dt>
+<dd>The commit messages and authorship information will be scanned for the given string.</dd>
+<dt><b>author</b></dt>
+<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
+<dt><b>committer</b></dt>
+<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
+EOT
+       my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+       if ($have_pickaxe) {
+               print <<EOT;
+<dt><b>pickaxe</b></dt>
+<dd>All commits that caused the string to appear or disappear from any file (changes that
+added, removed or "modified" the string) will be listed. This search can take a while and
+takes a lot of strain on the server, so please use it wisely.</dd>
+EOT
+       }
+       print "</dl>\n";
+       git_footer_html();
+}
+
 sub git_shortlog {
        my $head = git_get_head_hash($project);
        if (!defined $hash) {
@@ -3533,7 +3792,7 @@ sub git_shortlog {
        my $refs = git_get_references();
 
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash
+       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
                or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
@@ -3561,7 +3820,8 @@ sub git_shortlog {
 
 sub git_rss {
        # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
-       open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project)
+       open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
+               git_get_head_hash($project), "--"
                or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading git-rev-list failed");
@@ -3585,7 +3845,7 @@ sub git_rss {
                }
                my %cd = parse_date($co{'committer_epoch'});
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       $co{'parent'}, $co{'id'}
+                       $co{'parent'}, $co{'id'}, "--"
                        or next;
                my @difftree = map { chomp; $_ } <$fd>;
                close $fd
@@ -3630,7 +3890,7 @@ sub git_opml {
 <?xml version="1.0" encoding="utf-8"?>
 <opml version="1.0">
 <head>
-  <title>$site_name Git OPML Export</title>
+  <title>$site_name OPML Export</title>
 </head>
 <body>
 <outline text="git RSS feeds">
diff --git a/grep.c b/grep.c
index c411ddd4d52e2aa68edd979733dd0b15f51d5f32..0fc078ec0ac42e39125c8e5a8f05a3b6ecae4fa3 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -34,7 +34,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        }
 }
 
-static struct grep_expr *compile_pattern_expr(struct grep_pat **);
+static struct grep_expr *compile_pattern_or(struct grep_pat **);
 static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
 {
        struct grep_pat *p;
@@ -52,7 +52,7 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
                return x;
        case GREP_OPEN_PAREN:
                *list = p->next;
-               x = compile_pattern_expr(list);
+               x = compile_pattern_or(list);
                if (!x)
                        return NULL;
                if (!*list || (*list)->token != GREP_CLOSE_PAREN)
@@ -138,6 +138,9 @@ void compile_grep_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
 
+       if (opt->all_match)
+               opt->extended = 1;
+
        for (p = opt->pattern_list; p; p = p->next) {
                switch (p->token) {
                case GREP_PATTERN: /* atom */
@@ -309,40 +312,63 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
        return hit;
 }
 
-static int match_expr_eval(struct grep_opt *opt,
+static int match_expr_eval(struct grep_opt *o,
                           struct grep_expr *x,
                           char *bol, char *eol,
-                          enum grep_context ctx)
+                          enum grep_context ctx,
+                          int collect_hits)
 {
+       int h = 0;
+
        switch (x->node) {
        case GREP_NODE_ATOM:
-               return match_one_pattern(opt, x->u.atom, bol, eol, ctx);
+               h = match_one_pattern(o, x->u.atom, bol, eol, ctx);
                break;
        case GREP_NODE_NOT:
-               return !match_expr_eval(opt, x->u.unary, bol, eol, ctx);
+               h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);
+               break;
        case GREP_NODE_AND:
-               return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) &&
-                       match_expr_eval(opt, x->u.binary.right, bol, eol, ctx));
+               if (!collect_hits)
+                       return (match_expr_eval(o, x->u.binary.left,
+                                               bol, eol, ctx, 0) &&
+                               match_expr_eval(o, x->u.binary.right,
+                                               bol, eol, ctx, 0));
+               h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+               h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0);
+               break;
        case GREP_NODE_OR:
-               return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) ||
-                       match_expr_eval(opt, x->u.binary.right, bol, eol, ctx));
+               if (!collect_hits)
+                       return (match_expr_eval(o, x->u.binary.left,
+                                               bol, eol, ctx, 0) ||
+                               match_expr_eval(o, x->u.binary.right,
+                                               bol, eol, ctx, 0));
+               h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+               x->u.binary.left->hit |= h;
+               h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
+               break;
+       default:
+               die("Unexpected node type (internal error) %d\n", x->node);
        }
-       die("Unexpected node type (internal error) %d\n", x->node);
+       if (collect_hits)
+               x->hit |= h;
+       return h;
 }
 
 static int match_expr(struct grep_opt *opt, char *bol, char *eol,
-                     enum grep_context ctx)
+                     enum grep_context ctx, int collect_hits)
 {
        struct grep_expr *x = opt->pattern_expression;
-       return match_expr_eval(opt, x, bol, eol, ctx);
+       return match_expr_eval(opt, x, bol, eol, ctx, collect_hits);
 }
 
 static int match_line(struct grep_opt *opt, char *bol, char *eol,
-                     enum grep_context ctx)
+                     enum grep_context ctx, int collect_hits)
 {
        struct grep_pat *p;
        if (opt->extended)
-               return match_expr(opt, bol, eol, ctx);
+               return match_expr(opt, bol, eol, ctx, collect_hits);
+
+       /* we do not call with collect_hits without being extended */
        for (p = opt->pattern_list; p; p = p->next) {
                if (match_one_pattern(opt, p, bol, eol, ctx))
                        return 1;
@@ -350,7 +376,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol,
        return 0;
 }
 
-int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+static int grep_buffer_1(struct grep_opt *opt, const char *name,
+                        char *buf, unsigned long size, int collect_hits)
 {
        char *bol = buf;
        unsigned long left = size;
@@ -386,7 +413,7 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
 
        while (left) {
                char *eol, ch;
-               int hit = 0;
+               int hit;
 
                eol = end_of_line(bol, &left);
                ch = *eol;
@@ -395,9 +422,12 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
                if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
                        ctx = GREP_CONTEXT_BODY;
 
-               hit = match_line(opt, bol, eol, ctx);
+               hit = match_line(opt, bol, eol, ctx, collect_hits);
                *eol = ch;
 
+               if (collect_hits)
+                       goto next_line;
+
                /* "grep -v -e foo -e bla" should list lines
                 * that do not have either, so inversion should
                 * be done outside.
@@ -477,6 +507,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
        }
 
        free(prev);
+       if (collect_hits)
+               return 0;
 
        if (opt->status_only)
                return 0;
@@ -496,3 +528,49 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
        return !!last_hit;
 }
 
+static void clr_hit_marker(struct grep_expr *x)
+{
+       /* All-hit markers are meaningful only at the very top level
+        * OR node.
+        */
+       while (1) {
+               x->hit = 0;
+               if (x->node != GREP_NODE_OR)
+                       return;
+               x->u.binary.left->hit = 0;
+               x = x->u.binary.right;
+       }
+}
+
+static int chk_hit_marker(struct grep_expr *x)
+{
+       /* Top level nodes have hit markers.  See if they all are hits */
+       while (1) {
+               if (x->node != GREP_NODE_OR)
+                       return x->hit;
+               if (!x->u.binary.left->hit)
+                       return 0;
+               x = x->u.binary.right;
+       }
+}
+
+int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+{
+       /*
+        * we do not have to do the two-pass grep when we do not check
+        * buffer-wide "all-match".
+        */
+       if (!opt->all_match)
+               return grep_buffer_1(opt, name, buf, size, 0);
+
+       /* Otherwise the toplevel "or" terms hit a bit differently.
+        * We first clear hit markers from them.
+        */
+       clr_hit_marker(opt->pattern_expression);
+       grep_buffer_1(opt, name, buf, size, 1);
+
+       if (!chk_hit_marker(opt->pattern_expression))
+               return 0;
+
+       return grep_buffer_1(opt, name, buf, size, 0);
+}
diff --git a/grep.h b/grep.h
index af9098cfe8699680aeaa11790dad3e8d556ed395..d252dd25f81526d9b8663b4d3c9585d69a901397 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -35,6 +35,7 @@ enum grep_expr_node {
 
 struct grep_expr {
        enum grep_expr_node node;
+       unsigned hit;
        union {
                struct grep_pat *atom;
                struct grep_expr *unary;
@@ -59,6 +60,7 @@ struct grep_opt {
        unsigned count:1;
        unsigned word_regexp:1;
        unsigned fixed:1;
+       unsigned all_match:1;
 #define GREP_BINARY_DEFAULT    0
 #define GREP_BINARY_NOMATCH    1
 #define GREP_BINARY_TEXT       2
index 670ff007beb8a5ba3043045a0b31399cd108c310..ecefdfd4f8c9c17282f5cec10640343359278028 100644 (file)
@@ -1864,7 +1864,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
 static struct ref *local_refs, **local_tail;
 static struct ref *remote_refs, **remote_tail;
 
-static int one_local_ref(const char *refname, const unsigned char *sha1)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct ref *ref;
        int len = strlen(refname) + 1;
@@ -1913,7 +1913,7 @@ static void one_remote_ref(char *refname)
 static void get_local_heads(void)
 {
        local_tail = &local_refs;
-       for_each_ref(one_local_ref);
+       for_each_ref(one_local_ref, NULL);
 }
 
 static void get_dav_remote_heads(void)
index 80bc6cb45b1a91c31dbdf18b48399b7a7a6b2737..70640e14d8370d83c15a01aa13ac1b6183c8b6c5 100644 (file)
@@ -13,63 +13,93 @@ static const char index_pack_usage[] =
 struct object_entry
 {
        unsigned long offset;
+       unsigned long size;
+       unsigned int hdr_size;
        enum object_type type;
        enum object_type real_type;
        unsigned char sha1[20];
 };
 
+union delta_base {
+       unsigned char sha1[20];
+       unsigned long offset;
+};
+
+/*
+ * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
+ * to memcmp() only the first 20 bytes.
+ */
+#define UNION_BASE_SZ  20
+
 struct delta_entry
 {
        struct object_entry *obj;
-       unsigned char base_sha1[20];
+       union delta_base base;
 };
 
 static const char *pack_name;
-static unsigned char *pack_base;
-static unsigned long pack_size;
 static struct object_entry *objects;
 static struct delta_entry *deltas;
 static int nr_objects;
 static int nr_deltas;
 
-static void open_pack_file(void)
+/* We always read in 4kB chunks. */
+static unsigned char input_buffer[4096];
+static unsigned long input_offset, input_len, consumed_bytes;
+static SHA_CTX input_ctx;
+static int input_fd;
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
 {
-       int fd;
-       struct stat st;
+       if (min <= input_len)
+               return input_buffer + input_offset;
+       if (min > sizeof(input_buffer))
+               die("cannot fill %d bytes", min);
+       if (input_offset) {
+               SHA1_Update(&input_ctx, input_buffer, input_offset);
+               memmove(input_buffer, input_buffer + input_offset, input_len);
+               input_offset = 0;
+       }
+       do {
+               int ret = xread(input_fd, input_buffer + input_len,
+                               sizeof(input_buffer) - input_len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       die("read error on input: %s", strerror(errno));
+               }
+               input_len += ret;
+       } while (input_len < min);
+       return input_buffer;
+}
+
+static void use(int bytes)
+{
+       if (bytes > input_len)
+               die("used more bytes than were available");
+       input_len -= bytes;
+       input_offset += bytes;
+       consumed_bytes += bytes;
+}
 
-       fd = open(pack_name, O_RDONLY);
-       if (fd < 0)
+static void open_pack_file(void)
+{
+       input_fd = open(pack_name, O_RDONLY);
+       if (input_fd < 0)
                die("cannot open packfile '%s': %s", pack_name,
                    strerror(errno));
-       if (fstat(fd, &st)) {
-               int err = errno;
-               close(fd);
-               die("cannot fstat packfile '%s': %s", pack_name,
-                   strerror(err));
-       }
-       pack_size = st.st_size;
-       pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
-       if (pack_base == MAP_FAILED) {
-               int err = errno;
-               close(fd);
-               die("cannot mmap packfile '%s': %s", pack_name,
-                   strerror(err));
-       }
-       close(fd);
+       SHA1_Init(&input_ctx);
 }
 
 static void parse_pack_header(void)
 {
-       const struct pack_header *hdr;
-       unsigned char sha1[20];
-       SHA_CTX ctx;
-
-       /* Ensure there are enough bytes for the header and final SHA1 */
-       if (pack_size < sizeof(struct pack_header) + 20)
-               die("packfile '%s' is too small", pack_name);
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
 
        /* Header consistency check */
-       hdr = (void *)pack_base;
        if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
                die("packfile '%s' signature mismatch", pack_name);
        if (!pack_version_ok(hdr->hdr_version))
@@ -77,13 +107,8 @@ static void parse_pack_header(void)
                    pack_name, ntohl(hdr->hdr_version));
 
        nr_objects = ntohl(hdr->hdr_entries);
-
-       /* Check packfile integrity */
-       SHA1_Init(&ctx);
-       SHA1_Update(&ctx, pack_base, pack_size - 20);
-       SHA1_Final(sha1, &ctx);
-       if (hashcmp(sha1, pack_base + pack_size - 20))
-               die("packfile '%s' SHA1 mismatch", pack_name);
+       use(sizeof(struct pack_header));
+       /*fprintf(stderr, "Indexing %d objects\n", nr_objects);*/
 }
 
 static void bad_object(unsigned long offset, const char *format,
@@ -101,86 +126,121 @@ static void bad_object(unsigned long offset, const char *format, ...)
            pack_name, offset, buf);
 }
 
-static void *unpack_entry_data(unsigned long offset,
-                              unsigned long *current_pos, unsigned long size)
+static void *unpack_entry_data(unsigned long offset, unsigned long size)
 {
-       unsigned long pack_limit = pack_size - 20;
-       unsigned long pos = *current_pos;
        z_stream stream;
        void *buf = xmalloc(size);
 
        memset(&stream, 0, sizeof(stream));
        stream.next_out = buf;
        stream.avail_out = size;
-       stream.next_in = pack_base + pos;
-       stream.avail_in = pack_limit - pos;
+       stream.next_in = fill(1);
+       stream.avail_in = input_len;
        inflateInit(&stream);
 
        for (;;) {
                int ret = inflate(&stream, 0);
-               if (ret == Z_STREAM_END)
+               use(input_len - stream.avail_in);
+               if (stream.total_out == size && ret == Z_STREAM_END)
                        break;
                if (ret != Z_OK)
                        bad_object(offset, "inflate returned %d", ret);
+               stream.next_in = fill(1);
+               stream.avail_in = input_len;
        }
        inflateEnd(&stream);
-       if (stream.total_out != size)
-               bad_object(offset, "size mismatch (expected %lu, got %lu)",
-                          size, stream.total_out);
-       *current_pos = pack_limit - stream.avail_in;
        return buf;
 }
 
-static void *unpack_raw_entry(unsigned long offset,
-                             enum object_type *obj_type,
-                             unsigned long *obj_size,
-                             unsigned char *delta_base,
-                             unsigned long *next_obj_offset)
+static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
 {
-       unsigned long pack_limit = pack_size - 20;
-       unsigned long pos = offset;
-       unsigned char c;
-       unsigned long size;
+       unsigned char *p, c;
+       unsigned long size, base_offset;
        unsigned shift;
-       enum object_type type;
-       void *data;
 
-       c = pack_base[pos++];
-       type = (c >> 4) & 7;
+       obj->offset = consumed_bytes;
+
+       p = fill(1);
+       c = *p;
+       use(1);
+       obj->type = (c >> 4) & 7;
        size = (c & 15);
        shift = 4;
        while (c & 0x80) {
-               if (pos >= pack_limit)
-                       bad_object(offset, "object extends past end of pack");
-               c = pack_base[pos++];
+               p = fill(1);
+               c = *p;
+               use(1);
                size += (c & 0x7fUL) << shift;
                shift += 7;
        }
+       obj->size = size;
 
-       switch (type) {
-       case OBJ_DELTA:
-               if (pos + 20 >= pack_limit)
-                       bad_object(offset, "object extends past end of pack");
-               hashcpy(delta_base, pack_base + pos);
-               pos += 20;
-               /* fallthru */
+       switch (obj->type) {
+       case OBJ_REF_DELTA:
+               hashcpy(delta_base->sha1, fill(20));
+               use(20);
+               break;
+       case OBJ_OFS_DELTA:
+               memset(delta_base, 0, sizeof(*delta_base));
+               p = fill(1);
+               c = *p;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                               bad_object(obj->offset, "offset value overflow for delta base object");
+                       p = fill(1);
+                       c = *p;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               delta_base->offset = obj->offset - base_offset;
+               if (delta_base->offset >= obj->offset)
+                       bad_object(obj->offset, "delta base offset is out of bound");
+               break;
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
-               data = unpack_entry_data(offset, &pos, size);
                break;
        default:
-               bad_object(offset, "bad object type %d", type);
+               bad_object(obj->offset, "bad object type %d", obj->type);
        }
+       obj->hdr_size = consumed_bytes - obj->offset;
 
-       *obj_type = type;
-       *obj_size = size;
-       *next_obj_offset = pos;
+       return unpack_entry_data(obj->offset, obj->size);
+}
+
+static void * get_data_from_pack(struct object_entry *obj)
+{
+       unsigned long from = obj[0].offset + obj[0].hdr_size;
+       unsigned long len = obj[1].offset - from;
+       unsigned pg_offset = from % getpagesize();
+       unsigned char *map, *data;
+       z_stream stream;
+       int st;
+
+       map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
+                  input_fd, from - pg_offset);
+       if (map == MAP_FAILED)
+               die("cannot mmap packfile '%s': %s", pack_name, strerror(errno));
+       data = xmalloc(obj->size);
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = data;
+       stream.avail_out = obj->size;
+       stream.next_in = map + pg_offset;
+       stream.avail_in = len;
+       inflateInit(&stream);
+       while ((st = inflate(&stream, Z_FINISH)) == Z_OK);
+       inflateEnd(&stream);
+       if (st != Z_STREAM_END || stream.total_out != obj->size)
+               die("serious inflate inconsistency");
+       munmap(map, len + pg_offset);
        return data;
 }
 
-static int find_delta(const unsigned char *base_sha1)
+static int find_delta(const union delta_base *base)
 {
        int first = 0, last = nr_deltas;
 
@@ -189,7 +249,7 @@ static int find_delta(const unsigned char *base_sha1)
                 struct delta_entry *delta = &deltas[next];
                 int cmp;
 
-                cmp = hashcmp(base_sha1, delta->base_sha1);
+                cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
                 if (!cmp)
                         return next;
                 if (cmp < 0) {
@@ -201,18 +261,18 @@ static int find_delta(const unsigned char *base_sha1)
         return -first-1;
 }
 
-static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
-                                    int *first_index, int *last_index)
+static int find_delta_childs(const union delta_base *base,
+                            int *first_index, int *last_index)
 {
-       int first = find_delta(base_sha1);
+       int first = find_delta(base);
        int last = first;
        int end = nr_deltas - 1;
 
        if (first < 0)
                return -1;
-       while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1))
+       while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
                --first;
-       while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1))
+       while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
                ++last;
        *first_index = first;
        *last_index = last;
@@ -252,25 +312,34 @@ static void resolve_delta(struct delta_entry *delta, void *base_data,
        unsigned long delta_size;
        void *result;
        unsigned long result_size;
-       enum object_type delta_type;
-       unsigned char base_sha1[20];
-       unsigned long next_obj_offset;
+       union delta_base delta_base;
        int j, first, last;
 
        obj->real_type = type;
-       delta_data = unpack_raw_entry(obj->offset, &delta_type,
-                                     &delta_size, base_sha1,
-                                     &next_obj_offset);
+       delta_data = get_data_from_pack(obj);
+       delta_size = obj->size;
        result = patch_delta(base_data, base_size, delta_data, delta_size,
                             &result_size);
        free(delta_data);
        if (!result)
                bad_object(obj->offset, "failed to apply delta");
        sha1_object(result, result_size, type, obj->sha1);
-       if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
+
+       hashcpy(delta_base.sha1, obj->sha1);
+       if (!find_delta_childs(&delta_base, &first, &last)) {
+               for (j = first; j <= last; j++)
+                       if (deltas[j].obj->type == OBJ_REF_DELTA)
+                               resolve_delta(&deltas[j], result, result_size, type);
+       }
+
+       memset(&delta_base, 0, sizeof(delta_base));
+       delta_base.offset = obj->offset;
+       if (!find_delta_childs(&delta_base, &first, &last)) {
                for (j = first; j <= last; j++)
-                       resolve_delta(&deltas[j], result, result_size, type);
+                       if (deltas[j].obj->type == OBJ_OFS_DELTA)
+                               resolve_delta(&deltas[j], result, result_size, type);
        }
+
        free(result);
 }
 
@@ -278,16 +347,16 @@ static int compare_delta_entry(const void *a, const void *b)
 {
        const struct delta_entry *delta_a = a;
        const struct delta_entry *delta_b = b;
-       return hashcmp(delta_a->base_sha1, delta_b->base_sha1);
+       return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
 }
 
-static void parse_pack_objects(void)
+/* Parse all objects and return the pack content SHA1 hash */
+static void parse_pack_objects(unsigned char *sha1)
 {
        int i;
-       unsigned long offset = sizeof(struct pack_header);
-       unsigned char base_sha1[20];
+       struct delta_entry *delta = deltas;
        void *data;
-       unsigned long data_size;
+       struct stat st;
 
        /*
         * First pass:
@@ -297,22 +366,32 @@ static void parse_pack_objects(void)
         */
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               obj->offset = offset;
-               data = unpack_raw_entry(offset, &obj->type, &data_size,
-                                       base_sha1, &offset);
+               data = unpack_raw_entry(obj, &delta->base);
                obj->real_type = obj->type;
-               if (obj->type == OBJ_DELTA) {
-                       struct delta_entry *delta = &deltas[nr_deltas++];
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       nr_deltas++;
                        delta->obj = obj;
-                       hashcpy(delta->base_sha1, base_sha1);
+                       delta++;
                } else
-                       sha1_object(data, data_size, obj->type, obj->sha1);
+                       sha1_object(data, obj->size, obj->type, obj->sha1);
                free(data);
        }
-       if (offset != pack_size - 20)
+       objects[i].offset = consumed_bytes;
+
+       /* Check pack integrity */
+       SHA1_Update(&input_ctx, input_buffer, input_offset);
+       SHA1_Final(sha1, &input_ctx);
+       if (hashcmp(fill(20), sha1))
+               die("packfile '%s' SHA1 mismatch", pack_name);
+       use(20);
+
+       /* If input_fd is a file, we should have reached its end now. */
+       if (fstat(input_fd, &st))
+               die("cannot fstat packfile '%s': %s", pack_name, strerror(errno));
+       if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
                die("packfile '%s' has junk at the end", pack_name);
 
-       /* Sort deltas by base SHA1 for fast searching */
+       /* Sort deltas by base SHA1/offset for fast searching */
        qsort(deltas, nr_deltas, sizeof(struct delta_entry),
              compare_delta_entry);
 
@@ -326,22 +405,36 @@ static void parse_pack_objects(void)
         */
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               int j, first, last;
+               union delta_base base;
+               int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
 
-               if (obj->type == OBJ_DELTA)
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
                        continue;
-               if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+               hashcpy(base.sha1, obj->sha1);
+               ref = !find_delta_childs(&base, &ref_first, &ref_last);
+               memset(&base, 0, sizeof(base));
+               base.offset = obj->offset;
+               ofs = !find_delta_childs(&base, &ofs_first, &ofs_last);
+               if (!ref && !ofs)
                        continue;
-               data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
-                                       base_sha1, &offset);
-               for (j = first; j <= last; j++)
-                       resolve_delta(&deltas[j], data, data_size, obj->type);
+               data = get_data_from_pack(obj);
+               if (ref)
+                       for (j = ref_first; j <= ref_last; j++)
+                               if (deltas[j].obj->type == OBJ_REF_DELTA)
+                                       resolve_delta(&deltas[j], data,
+                                                     obj->size, obj->type);
+               if (ofs)
+                       for (j = ofs_first; j <= ofs_last; j++)
+                               if (deltas[j].obj->type == OBJ_OFS_DELTA)
+                                       resolve_delta(&deltas[j], data,
+                                                     obj->size, obj->type);
                free(data);
        }
 
        /* Check for unresolved deltas */
        for (i = 0; i < nr_deltas; i++) {
-               if (deltas[i].obj->real_type == OBJ_DELTA)
+               if (deltas[i].obj->real_type == OBJ_REF_DELTA ||
+                   deltas[i].obj->real_type == OBJ_OFS_DELTA)
                        die("packfile '%s' has unresolved deltas",  pack_name);
        }
 }
@@ -353,6 +446,10 @@ static int sha1_compare(const void *_a, const void *_b)
        return hashcmp(a->sha1, b->sha1);
 }
 
+/*
+ * On entry *sha1 contains the pack content SHA1 hash, on exit it is
+ * the SHA1 hash of sorted object names.
+ */
 static void write_index_file(const char *index_name, unsigned char *sha1)
 {
        struct sha1file *f;
@@ -412,7 +509,7 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
                sha1write(f, obj->sha1, 20);
                SHA1_Update(&ctx, obj->sha1, 20);
        }
-       sha1write(f, pack_base + pack_size - 20, 20);
+       sha1write(f, sha1, 20);
        sha1close(f, NULL, 1);
        free(sorted_by_sha);
        SHA1_Final(sha1, &ctx);
@@ -458,9 +555,9 @@ int main(int argc, char **argv)
 
        open_pack_file();
        parse_pack_header();
-       objects = xcalloc(nr_objects, sizeof(struct object_entry));
+       objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
        deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
-       parse_pack_objects();
+       parse_pack_objects(sha1);
        free(deltas);
        write_index_file(index_name, sha1);
        free(objects);
index fbe139920ab0eada09dc921c152236d9676da44c..8787df5cc647dda24f09b077276cbe0aa054fc47 100644 (file)
@@ -252,26 +252,6 @@ int log_tree_diff_flush(struct rev_info *opt)
        return 1;
 }
 
-static int diff_root_tree(struct rev_info *opt,
-                         const unsigned char *new, const char *base)
-{
-       int retval;
-       void *tree;
-       struct tree_desc empty, real;
-
-       tree = read_object_with_reference(new, tree_type, &real.size, NULL);
-       if (!tree)
-               die("unable to read root tree (%s)", sha1_to_hex(new));
-       real.buf = tree;
-
-       empty.buf = "";
-       empty.size = 0;
-       retval = diff_tree(&empty, &real, base, &opt->diffopt);
-       free(tree);
-       log_tree_diff_flush(opt);
-       return retval;
-}
-
 static int do_diff_combined(struct rev_info *opt, struct commit *commit)
 {
        unsigned const char *sha1 = commit->object.sha1;
@@ -297,8 +277,10 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
        /* Root commit? */
        parents = commit->parents;
        if (!parents) {
-               if (opt->show_root_diff)
-                       diff_root_tree(opt, sha1, "");
+               if (opt->show_root_diff) {
+                       diff_root_tree_sha1(sha1, "", &opt->diffopt);
+                       log_tree_diff_flush(opt);
+               }
                return !opt->loginfo;
        }
 
index 92813001e25f20ae7dd7a1e0f0ef11ec5d746030..de244e206375d43f2bd327a0c3058ea24d53704e 100644 (file)
--- a/object.c
+++ b/object.c
@@ -138,42 +138,56 @@ struct object *lookup_unknown_object(const unsigned char *sha1)
        return obj;
 }
 
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p)
+{
+       struct object *obj;
+       int eaten = 0;
+
+       if (!strcmp(type, blob_type)) {
+               struct blob *blob = lookup_blob(sha1);
+               parse_blob_buffer(blob, buffer, size);
+               obj = &blob->object;
+       } else if (!strcmp(type, tree_type)) {
+               struct tree *tree = lookup_tree(sha1);
+               obj = &tree->object;
+               if (!tree->object.parsed) {
+                       parse_tree_buffer(tree, buffer, size);
+                       eaten = 1;
+               }
+       } else if (!strcmp(type, commit_type)) {
+               struct commit *commit = lookup_commit(sha1);
+               parse_commit_buffer(commit, buffer, size);
+               if (!commit->buffer) {
+                       commit->buffer = buffer;
+                       eaten = 1;
+               }
+               obj = &commit->object;
+       } else if (!strcmp(type, tag_type)) {
+               struct tag *tag = lookup_tag(sha1);
+               parse_tag_buffer(tag, buffer, size);
+               obj = &tag->object;
+       } else {
+               obj = NULL;
+       }
+       *eaten_p = eaten;
+       return obj;
+}
+
 struct object *parse_object(const unsigned char *sha1)
 {
        unsigned long size;
        char type[20];
+       int eaten;
        void *buffer = read_sha1_file(sha1, type, &size);
+
        if (buffer) {
                struct object *obj;
                if (check_sha1_signature(sha1, buffer, size, type) < 0)
                        printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
-               if (!strcmp(type, blob_type)) {
-                       struct blob *blob = lookup_blob(sha1);
-                       parse_blob_buffer(blob, buffer, size);
-                       obj = &blob->object;
-               } else if (!strcmp(type, tree_type)) {
-                       struct tree *tree = lookup_tree(sha1);
-                       obj = &tree->object;
-                       if (!tree->object.parsed) {
-                               parse_tree_buffer(tree, buffer, size);
-                               buffer = NULL;
-                       }
-               } else if (!strcmp(type, commit_type)) {
-                       struct commit *commit = lookup_commit(sha1);
-                       parse_commit_buffer(commit, buffer, size);
-                       if (!commit->buffer) {
-                               commit->buffer = buffer;
-                               buffer = NULL;
-                       }
-                       obj = &commit->object;
-               } else if (!strcmp(type, tag_type)) {
-                       struct tag *tag = lookup_tag(sha1);
-                       parse_tag_buffer(tag, buffer, size);
-                       obj = &tag->object;
-               } else {
-                       obj = NULL;
-               }
-               free(buffer);
+
+               obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
+               if (!eaten)
+                       free(buffer);
                return obj;
        }
        return NULL;
index 3d4ff4611f55d766cf2f82baa9771a80b50c85f9..caee733cdeda28b1679848fa9be4be870420b54a 100644 (file)
--- a/object.h
+++ b/object.h
@@ -59,6 +59,12 @@ void created_object(const unsigned char *sha1, struct object *obj);
 /** Returns the object, having parsed it to find out what it is. **/
 struct object *parse_object(const unsigned char *sha1);
 
+/* Given the result of read_sha1_file(), returns the object after
+ * parsing it.  eaten_p indicates if the object has a borrowed copy
+ * of buffer and the caller should not free() it.
+ */
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p);
+
 /** Returns the object, with potentially excess memory allocated. **/
 struct object *lookup_unknown_object(const unsigned  char *sha1);
 
diff --git a/pack.h b/pack.h
index eb07b033ae54941d4bd00ee6bc30c7d50fd039b0..4814800f2806a245a675ea9832f894dc95b27b89 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -16,7 +16,4 @@ struct pack_header {
 };
 
 extern int verify_pack(struct packed_git *, int);
-extern int check_reuse_pack_delta(struct packed_git *, unsigned long,
-                                 unsigned char *, unsigned long *,
-                                 enum object_type *);
 #endif
diff --git a/quote.c b/quote.c
index dc5c0a7715e4782024ede6dd90b150f6e71022ee..a418a0f803f91d218b66ae89a1e8615a94ff27bc 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -349,3 +349,41 @@ void write_name_quoted(const char *prefix, int prefix_len,
        else
                goto no_quote;
 }
+
+/* quoting as a string literal for other languages */
+
+void perl_quote_print(FILE *stream, const char *src)
+{
+       const char sq = '\'';
+       const char bq = '\\';
+       char c;
+
+       fputc(sq, stream);
+       while ((c = *src++)) {
+               if (c == sq || c == bq)
+                       fputc(bq, stream);
+               fputc(c, stream);
+       }
+       fputc(sq, stream);
+}
+
+void python_quote_print(FILE *stream, const char *src)
+{
+       const char sq = '\'';
+       const char bq = '\\';
+       const char nl = '\n';
+       char c;
+
+       fputc(sq, stream);
+       while ((c = *src++)) {
+               if (c == nl) {
+                       fputc(bq, stream);
+                       fputc('n', stream);
+                       continue;
+               }
+               if (c == sq || c == bq)
+                       fputc(bq, stream);
+               fputc(c, stream);
+       }
+       fputc(sq, stream);
+}
diff --git a/quote.h b/quote.h
index 1a29e791dd59324138a8d1e73704114da199fc12..b55e69975091193f62b293f90a776ca9f6112ab4 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -52,4 +52,8 @@ extern char *unquote_c_style(const char *quoted, const char **endp);
 extern void write_name_quoted(const char *prefix, int prefix_len,
                              const char *name, int quote, FILE *out);
 
+/* quoting as a string literal for other languages */
+extern void perl_quote_print(FILE *stream, const char *src);
+extern void python_quote_print(FILE *stream, const char *src);
+
 #endif
index ea2dbd4e3398ca90823dcd453cbb52c5dcea135c..de1d6a4b1c3f51f8763484b6efea0a8ccd289bb3 100644 (file)
@@ -9,12 +9,26 @@ static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
 static const char *unpacker[] = { "unpack-objects", NULL };
 
+static int deny_non_fast_forwards = 0;
 static int report_status;
 
 static char capabilities[] = "report-status";
 static int capabilities_sent;
 
-static int show_ref(const char *path, const unsigned char *sha1)
+static int receive_pack_config(const char *var, const char *value)
+{
+       git_default_config(var, value);
+
+       if (strcmp(var, "receive.denynonfastforwards") == 0)
+       {
+               deny_non_fast_forwards = git_config_bool(var, value);
+               return 0;
+       }
+
+       return 0;
+}
+
+static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        if (capabilities_sent)
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
@@ -27,9 +41,9 @@ static int show_ref(const char *path, const unsigned char *sha1)
 
 static void write_head_info(void)
 {
-       for_each_ref(show_ref);
+       for_each_ref(show_ref, NULL);
        if (!capabilities_sent)
-               show_ref("capabilities^{}", null_sha1);
+               show_ref("capabilities^{}", null_sha1, 0, NULL);
 
 }
 
@@ -43,34 +57,6 @@ struct command {
 
 static struct command *commands;
 
-static int is_all_zeroes(const char *hex)
-{
-       int i;
-       for (i = 0; i < 40; i++)
-               if (*hex++ != '0')
-                       return 0;
-       return 1;
-}
-
-static int verify_old_ref(const char *name, char *hex_contents)
-{
-       int fd, ret;
-       char buffer[60];
-
-       if (is_all_zeroes(hex_contents))
-               return 0;
-       fd = open(name, O_RDONLY);
-       if (fd < 0)
-               return -1;
-       ret = read(fd, buffer, 40);
-       close(fd);
-       if (ret != 40)
-               return -1;
-       if (memcmp(buffer, hex_contents, 40))
-               return -1;
-       return 0;
-}
-
 static char update_hook[] = "hooks/update";
 
 static int run_update_hook(const char *refname,
@@ -107,8 +93,8 @@ static int update(struct command *cmd)
        const char *name = cmd->ref_name;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
-       char new_hex[60], *old_hex, *lock_name;
-       int newfd, namelen, written;
+       char new_hex[41], old_hex[41];
+       struct ref_lock *lock;
 
        cmd->error_string = NULL;
        if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) {
@@ -117,13 +103,8 @@ static int update(struct command *cmd)
                             name);
        }
 
-       namelen = strlen(name);
-       lock_name = xmalloc(namelen + 10);
-       memcpy(lock_name, name, namelen);
-       memcpy(lock_name + namelen, ".lock", 6);
-
        strcpy(new_hex, sha1_to_hex(new_sha1));
-       old_hex = sha1_to_hex(old_sha1);
+       strcpy(old_hex, sha1_to_hex(old_sha1));
        if (!has_sha1_file(new_sha1)) {
                cmd->error_string = "bad pack";
                return error("unpack should have generated %s, "
@@ -144,47 +125,20 @@ static int update(struct command *cmd)
                        return error("denying non-fast forward;"
                                     " you should pull first");
        }
-       safe_create_leading_directories(lock_name);
-
-       newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
-       if (newfd < 0) {
-               cmd->error_string = "can't lock";
-               return error("unable to create %s (%s)",
-                            lock_name, strerror(errno));
-       }
-
-       /* Write the ref with an ending '\n' */
-       new_hex[40] = '\n';
-       new_hex[41] = 0;
-       written = write(newfd, new_hex, 41);
-       /* Remove the '\n' again */
-       new_hex[40] = 0;
-
-       close(newfd);
-       if (written != 41) {
-               unlink(lock_name);
-               cmd->error_string = "can't write";
-               return error("unable to write %s", lock_name);
-       }
-       if (verify_old_ref(name, old_hex) < 0) {
-               unlink(lock_name);
-               cmd->error_string = "raced";
-               return error("%s changed during push", name);
-       }
        if (run_update_hook(name, old_hex, new_hex)) {
-               unlink(lock_name);
                cmd->error_string = "hook declined";
                return error("hook declined to update %s", name);
        }
-       else if (rename(lock_name, name) < 0) {
-               unlink(lock_name);
-               cmd->error_string = "can't rename";
-               return error("unable to replace %s", name);
-       }
-       else {
-               fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
-               return 0;
+
+       lock = lock_any_ref_for_update(name, old_sha1);
+       if (!lock) {
+               cmd->error_string = "failed to lock";
+               return error("failed to lock %s", name);
        }
+       write_ref_sha1(lock, new_sha1, "push");
+
+       fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+       return 0;
 }
 
 static char update_post_hook[] = "hooks/post-update";
@@ -335,9 +289,12 @@ int main(int argc, char **argv)
        if (!dir)
                usage(receive_pack_usage);
 
-       if(!enter_repo(dir, 0))
+       if (!enter_repo(dir, 0))
                die("'%s': unable to chdir or not a git archive", dir);
 
+       setup_ident();
+       git_config(receive_pack_config);
+
        write_head_info();
 
        /* EOF */
diff --git a/refs.c b/refs.c
index 98327d798375d2bc966f877af227a03070ce5d1b..f003a0b1080267b419296b3b37312b858b8b215d 100644 (file)
--- a/refs.c
+++ b/refs.c
 
 #include <errno.h>
 
+struct ref_list {
+       struct ref_list *next;
+       unsigned char flag; /* ISSYMREF? ISPACKED? */
+       unsigned char sha1[20];
+       char name[FLEX_ARRAY];
+};
+
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+       /*
+        * 42: the answer to everything.
+        *
+        * In this case, it happens to be the answer to
+        *  40 (length of sha1 hex representation)
+        *  +1 (space in between hex and name)
+        *  +1 (newline at the end of the line)
+        */
+       int len = strlen(line) - 42;
+
+       if (len <= 0)
+               return NULL;
+       if (get_sha1_hex(line, sha1) < 0)
+               return NULL;
+       if (!isspace(line[40]))
+               return NULL;
+       line += 41;
+       if (isspace(*line))
+               return NULL;
+       if (line[len] != '\n')
+               return NULL;
+       line[len] = 0;
+       return line;
+}
+
+static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
+                               int flag, struct ref_list *list)
+{
+       int len;
+       struct ref_list **p = &list, *entry;
+
+       /* Find the place to insert the ref into.. */
+       while ((entry = *p) != NULL) {
+               int cmp = strcmp(entry->name, name);
+               if (cmp > 0)
+                       break;
+
+               /* Same as existing entry? */
+               if (!cmp)
+                       return list;
+               p = &entry->next;
+       }
+
+       /* Allocate it and add it in.. */
+       len = strlen(name) + 1;
+       entry = xmalloc(sizeof(struct ref_list) + len);
+       hashcpy(entry->sha1, sha1);
+       memcpy(entry->name, name, len);
+       entry->flag = flag;
+       entry->next = *p;
+       *p = entry;
+       return list;
+}
+
+/*
+ * Future: need to be in "struct repository"
+ * when doing a full libification.
+ */
+struct cached_refs {
+       char did_loose;
+       char did_packed;
+       struct ref_list *loose;
+       struct ref_list *packed;
+} cached_refs;
+
+static void free_ref_list(struct ref_list *list)
+{
+       struct ref_list *next;
+       for ( ; list; list = next) {
+               next = list->next;
+               free(list);
+       }
+}
+
+static void invalidate_cached_refs(void)
+{
+       struct cached_refs *ca = &cached_refs;
+
+       if (ca->did_loose && ca->loose)
+               free_ref_list(ca->loose);
+       if (ca->did_packed && ca->packed)
+               free_ref_list(ca->packed);
+       ca->loose = ca->packed = NULL;
+       ca->did_loose = ca->did_packed = 0;
+}
+
+static struct ref_list *get_packed_refs(void)
+{
+       if (!cached_refs.did_packed) {
+               struct ref_list *refs = NULL;
+               FILE *f = fopen(git_path("packed-refs"), "r");
+               if (f) {
+                       struct ref_list *list = NULL;
+                       char refline[PATH_MAX];
+                       while (fgets(refline, sizeof(refline), f)) {
+                               unsigned char sha1[20];
+                               const char *name = parse_ref_line(refline, sha1);
+                               if (!name)
+                                       continue;
+                               list = add_ref(name, sha1, REF_ISPACKED, list);
+                       }
+                       fclose(f);
+                       refs = list;
+               }
+               cached_refs.packed = refs;
+               cached_refs.did_packed = 1;
+       }
+       return cached_refs.packed;
+}
+
+static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+{
+       DIR *dir = opendir(git_path("%s", base));
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *ref = xmalloc(baselen + 257);
+
+               memcpy(ref, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       ref[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+                       struct stat st;
+                       int flag;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       if (has_extension(de->d_name, ".lock"))
+                               continue;
+                       memcpy(ref + baselen, de->d_name, namelen+1);
+                       if (stat(git_path("%s", ref), &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               list = get_ref_dir(ref, list);
+                               continue;
+                       }
+                       if (!resolve_ref(ref, sha1, 1, &flag)) {
+                               error("%s points nowhere!", ref);
+                               continue;
+                       }
+                       list = add_ref(ref, sha1, flag, list);
+               }
+               free(ref);
+               closedir(dir);
+       }
+       return list;
+}
+
+static struct ref_list *get_loose_refs(void)
+{
+       if (!cached_refs.did_loose) {
+               cached_refs.loose = get_ref_dir("refs", NULL);
+               cached_refs.did_loose = 1;
+       }
+       return cached_refs.loose;
+}
+
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
 
-const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH, len;
        char buffer[256];
+       static char ref_buffer[256];
+
+       if (flag)
+               *flag = 0;
 
        for (;;) {
+               const char *path = git_path("%s", ref);
                struct stat st;
                char *buf;
                int fd;
@@ -27,17 +205,31 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
                 * reading.
                 */
                if (lstat(path, &st) < 0) {
+                       struct ref_list *list = get_packed_refs();
+                       while (list) {
+                               if (!strcmp(ref, list->name)) {
+                                       hashcpy(sha1, list->sha1);
+                                       if (flag)
+                                               *flag |= REF_ISPACKED;
+                                       return ref;
+                               }
+                               list = list->next;
+                       }
                        if (reading || errno != ENOENT)
                                return NULL;
                        hashclr(sha1);
-                       return path;
+                       return ref;
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
                        len = readlink(path, buffer, sizeof(buffer)-1);
                        if (len >= 5 && !memcmp("refs/", buffer, 5)) {
-                               path = git_path("%.*s", len, buffer);
+                               buffer[len] = 0;
+                               strcpy(ref_buffer, buffer);
+                               ref = ref_buffer;
+                               if (flag)
+                                       *flag |= REF_ISSYMREF;
                                continue;
                        }
                }
@@ -68,19 +260,24 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
                while (len && isspace(*buf))
                        buf++, len--;
                while (len && isspace(buf[len-1]))
-                       buf[--len] = 0;
-               path = git_path("%.*s", len, buf);
+                       len--;
+               buf[len] = 0;
+               memcpy(ref_buffer, buf, len + 1);
+               ref = ref_buffer;
+               if (flag)
+                       *flag |= REF_ISSYMREF;
        }
        if (len < 40 || get_sha1_hex(buffer, sha1))
                return NULL;
-       return path;
+       return ref;
 }
 
-int create_symref(const char *git_HEAD, const char *refs_heads_master)
+int create_symref(const char *ref_target, const char *refs_heads_master)
 {
        const char *lockpath;
        char ref[1000];
        int fd, len, written;
+       const char *git_HEAD = git_path("%s", ref_target);
 
 #ifndef NO_SYMLINK_HEAD
        if (prefer_symlink_refs) {
@@ -118,104 +315,101 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
        return 0;
 }
 
-int read_ref(const char *filename, unsigned char *sha1)
+int read_ref(const char *ref, unsigned char *sha1)
 {
-       if (resolve_ref(filename, sha1, 1))
+       if (resolve_ref(ref, sha1, 1, NULL))
                return 0;
        return -1;
 }
 
-static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
+static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
+                          void *cb_data)
 {
-       int retval = 0;
-       DIR *dir = opendir(git_path("%s", base));
-
-       if (dir) {
-               struct dirent *de;
-               int baselen = strlen(base);
-               char *path = xmalloc(baselen + 257);
-
-               if (!strncmp(base, "./", 2)) {
-                       base += 2;
-                       baselen -= 2;
+       int retval;
+       struct ref_list *packed = get_packed_refs();
+       struct ref_list *loose = get_loose_refs();
+
+       while (packed && loose) {
+               struct ref_list *entry;
+               int cmp = strcmp(packed->name, loose->name);
+               if (!cmp) {
+                       packed = packed->next;
+                       continue;
                }
-               memcpy(path, base, baselen);
-               if (baselen && base[baselen-1] != '/')
-                       path[baselen++] = '/';
-
-               while ((de = readdir(dir)) != NULL) {
-                       unsigned char sha1[20];
-                       struct stat st;
-                       int namelen;
+               if (cmp > 0) {
+                       entry = loose;
+                       loose = loose->next;
+               } else {
+                       entry = packed;
+                       packed = packed->next;
+               }
+               if (strncmp(base, entry->name, trim))
+                       continue;
+               if (is_null_sha1(entry->sha1))
+                       continue;
+               if (!has_sha1_file(entry->sha1)) {
+                       error("%s does not point to a valid object!", entry->name);
+                       continue;
+               }
+               retval = fn(entry->name + trim, entry->sha1,
+                           entry->flag, cb_data);
+               if (retval)
+                       return retval;
+       }
 
-                       if (de->d_name[0] == '.')
-                               continue;
-                       namelen = strlen(de->d_name);
-                       if (namelen > 255)
-                               continue;
-                       if (has_extension(de->d_name, ".lock"))
-                               continue;
-                       memcpy(path + baselen, de->d_name, namelen+1);
-                       if (stat(git_path("%s", path), &st) < 0)
-                               continue;
-                       if (S_ISDIR(st.st_mode)) {
-                               retval = do_for_each_ref(path, fn, trim);
-                               if (retval)
-                                       break;
-                               continue;
-                       }
-                       if (read_ref(git_path("%s", path), sha1) < 0) {
-                               error("%s points nowhere!", path);
-                               continue;
-                       }
-                       if (!has_sha1_file(sha1)) {
-                               error("%s does not point to a valid "
-                                     "commit object!", path);
-                               continue;
-                       }
-                       retval = fn(path + trim, sha1);
+       packed = packed ? packed : loose;
+       while (packed) {
+               if (!strncmp(base, packed->name, trim)) {
+                       retval = fn(packed->name + trim, packed->sha1,
+                                   packed->flag, cb_data);
                        if (retval)
-                               break;
+                               return retval;
                }
-               free(path);
-               closedir(dir);
+               packed = packed->next;
        }
-       return retval;
+       return 0;
 }
 
-int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int head_ref(each_ref_fn fn, void *cb_data)
 {
        unsigned char sha1[20];
-       if (!read_ref(git_path("HEAD"), sha1))
-               return fn("HEAD", sha1);
+       int flag;
+
+       if (resolve_ref("HEAD", sha1, 1, &flag))
+               return fn("HEAD", sha1, flag, cb_data);
        return 0;
 }
 
-int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs", fn, 0);
+       return do_for_each_ref("refs/", fn, 0, cb_data);
 }
 
-int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_tag_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/tags", fn, 10);
+       return do_for_each_ref("refs/tags/", fn, 10, cb_data);
 }
 
-int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_branch_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/heads", fn, 11);
+       return do_for_each_ref("refs/heads/", fn, 11, cb_data);
 }
 
-int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_remote_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/remotes", fn, 13);
+       return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
 }
 
+/* NEEDSWORK: This is only used by ssh-upload and it should go; the
+ * caller should do resolve_ref or read_ref like everybody else.  Or
+ * maybe everybody else should use get_ref_sha1() instead of doing
+ * read_ref().
+ */
 int get_ref_sha1(const char *ref, unsigned char *sha1)
 {
        if (check_ref_format(ref))
                return -1;
-       return read_ref(git_path("refs/%s", ref), sha1);
+       return read_ref(mkpath("refs/%s", ref), sha1);
 }
 
 /*
@@ -273,22 +467,13 @@ int check_ref_format(const char *ref)
 static struct ref_lock *verify_lock(struct ref_lock *lock,
        const unsigned char *old_sha1, int mustexist)
 {
-       char buf[40];
-       int nr, fd = open(lock->ref_file, O_RDONLY);
-       if (fd < 0 && (mustexist || errno != ENOENT)) {
-               error("Can't verify ref %s", lock->ref_file);
-               unlock_ref(lock);
-               return NULL;
-       }
-       nr = read(fd, buf, 40);
-       close(fd);
-       if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
-               error("Can't verify ref %s", lock->ref_file);
+       if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+               error("Can't verify ref %s", lock->ref_name);
                unlock_ref(lock);
                return NULL;
        }
        if (hashcmp(lock->old_sha1, old_sha1)) {
-               error("Ref %s is at %s but expected %s", lock->ref_file,
+               error("Ref %s is at %s but expected %s", lock->ref_name,
                        sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
                unlock_ref(lock);
                return NULL;
@@ -296,54 +481,223 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,
        return lock;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *path,
-       int plen,
-       const unsigned char *old_sha1, int mustexist)
+static int remove_empty_dir_recursive(char *path, int len)
+{
+       DIR *dir = opendir(path);
+       struct dirent *e;
+       int ret = 0;
+
+       if (!dir)
+               return -1;
+       if (path[len-1] != '/')
+               path[len++] = '/';
+       while ((e = readdir(dir)) != NULL) {
+               struct stat st;
+               int namlen;
+               if ((e->d_name[0] == '.') &&
+                   ((e->d_name[1] == 0) ||
+                    ((e->d_name[1] == '.') && e->d_name[2] == 0)))
+                       continue; /* "." and ".." */
+
+               namlen = strlen(e->d_name);
+               if ((len + namlen < PATH_MAX) &&
+                   strcpy(path + len, e->d_name) &&
+                   !lstat(path, &st) &&
+                   S_ISDIR(st.st_mode) &&
+                   !remove_empty_dir_recursive(path, len + namlen))
+                       continue; /* happy */
+
+               /* path too long, stat fails, or non-directory still exists */
+               ret = -1;
+               break;
+       }
+       closedir(dir);
+       if (!ret) {
+               path[len] = 0;
+               ret = rmdir(path);
+       }
+       return ret;
+}
+
+static int remove_empty_directories(char *file)
 {
-       const char *orig_path = path;
+       /* we want to create a file but there is a directory there;
+        * if that is an empty directory (or a directory that contains
+        * only empty directories), remove them.
+        */
+       char path[PATH_MAX];
+       int len = strlen(file);
+
+       if (len >= PATH_MAX) /* path too long ;-) */
+               return -1;
+       strcpy(path, file);
+       return remove_empty_dir_recursive(path, len);
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+{
+       char *ref_file;
+       const char *orig_ref = ref;
        struct ref_lock *lock;
        struct stat st;
+       int last_errno = 0;
+       int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
 
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       plen = strlen(path) - plen;
-       path = resolve_ref(path, lock->old_sha1, mustexist);
-       if (!path) {
-               int last_errno = errno;
+       ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+       if (!ref && errno == EISDIR) {
+               /* we are trying to lock foo but we used to
+                * have foo/bar which now does not exist;
+                * it is normal for the empty directory 'foo'
+                * to remain.
+                */
+               ref_file = git_path("%s", orig_ref);
+               if (remove_empty_directories(ref_file)) {
+                       last_errno = errno;
+                       error("there are still refs under '%s'", orig_ref);
+                       goto error_return;
+               }
+               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+       }
+       if (!ref) {
+               last_errno = errno;
                error("unable to resolve reference %s: %s",
-                       orig_path, strerror(errno));
-               unlock_ref(lock);
-               errno = last_errno;
-               return NULL;
+                       orig_ref, strerror(errno));
+               goto error_return;
+       }
+       if (is_null_sha1(lock->old_sha1)) {
+               /* The ref did not exist and we are creating it.
+                * Make sure there is no existing ref that is packed
+                * whose name begins with our refname, nor a ref whose
+                * name is a proper prefix of our refname.
+                */
+               int namlen = strlen(ref); /* e.g. 'foo/bar' */
+               struct ref_list *list = get_packed_refs();
+               while (list) {
+                       /* list->name could be 'foo' or 'foo/bar/baz' */
+                       int len = strlen(list->name);
+                       int cmplen = (namlen < len) ? namlen : len;
+                       const char *lead = (namlen < len) ? list->name : ref;
+
+                       if (!strncmp(ref, list->name, cmplen) &&
+                           lead[cmplen] == '/') {
+                               error("'%s' exists; cannot create '%s'",
+                                     list->name, ref);
+                               goto error_return;
+                       }
+                       list = list->next;
+               }
        }
+
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
-       lock->ref_file = xstrdup(path);
-       lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen));
-       lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+       lock->ref_name = xstrdup(ref);
+       lock->log_file = xstrdup(git_path("logs/%s", ref));
+       ref_file = git_path("%s", ref);
+       lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
 
-       if (safe_create_leading_directories(lock->ref_file))
-               die("unable to create directory for %s", lock->ref_file);
-       lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1);
+       if (safe_create_leading_directories(ref_file)) {
+               last_errno = errno;
+               error("unable to create directory for %s", ref_file);
+               goto error_return;
+       }
+       lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1);
 
        return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+
+ error_return:
+       unlock_ref(lock);
+       errno = last_errno;
+       return NULL;
 }
 
-struct ref_lock *lock_ref_sha1(const char *ref,
-       const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
 {
+       char refpath[PATH_MAX];
        if (check_ref_format(ref))
                return NULL;
-       return lock_ref_sha1_basic(git_path("refs/%s", ref),
-               5 + strlen(ref), old_sha1, mustexist);
+       strcpy(refpath, mkpath("refs/%s", ref));
+       return lock_ref_sha1_basic(refpath, old_sha1, NULL);
 }
 
-struct ref_lock *lock_any_ref_for_update(const char *ref,
-       const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+{
+       return lock_ref_sha1_basic(ref, old_sha1, NULL);
+}
+
+static struct lock_file packlock;
+
+static int repack_without_ref(const char *refname)
 {
-       return lock_ref_sha1_basic(git_path("%s", ref),
-               strlen(ref), old_sha1, mustexist);
+       struct ref_list *list, *packed_ref_list;
+       int fd;
+       int found = 0;
+
+       packed_ref_list = get_packed_refs();
+       for (list = packed_ref_list; list; list = list->next) {
+               if (!strcmp(refname, list->name)) {
+                       found = 1;
+                       break;
+               }
+       }
+       if (!found)
+               return 0;
+       memset(&packlock, 0, sizeof(packlock));
+       fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+       if (fd < 0)
+               return error("cannot delete '%s' from packed refs", refname);
+
+       for (list = packed_ref_list; list; list = list->next) {
+               char line[PATH_MAX + 100];
+               int len;
+
+               if (!strcmp(refname, list->name))
+                       continue;
+               len = snprintf(line, sizeof(line), "%s %s\n",
+                              sha1_to_hex(list->sha1), list->name);
+               /* this should not happen but just being defensive */
+               if (len > sizeof(line))
+                       die("too long a refname '%s'", list->name);
+               write_or_die(fd, line, len);
+       }
+       return commit_lock_file(&packlock);
+}
+
+int delete_ref(const char *refname, unsigned char *sha1)
+{
+       struct ref_lock *lock;
+       int err, i, ret = 0, flag = 0;
+
+       lock = lock_ref_sha1_basic(refname, sha1, &flag);
+       if (!lock)
+               return 1;
+       if (!(flag & REF_ISPACKED)) {
+               /* loose */
+               i = strlen(lock->lk->filename) - 5; /* .lock */
+               lock->lk->filename[i] = 0;
+               err = unlink(lock->lk->filename);
+               if (err) {
+                       ret = 1;
+                       error("unlink(%s) failed: %s",
+                             lock->lk->filename, strerror(errno));
+               }
+               lock->lk->filename[i] = '.';
+       }
+       /* removing the loose one could have resurrected an earlier
+        * packed one.  Also, if it was not loose we need to repack
+        * without it.
+        */
+       ret |= repack_without_ref(refname);
+
+       err = unlink(lock->log_file);
+       if (err && errno != ENOENT)
+               fprintf(stderr, "warning: unlink(%s) failed: %s",
+                       lock->log_file, strerror(errno));
+       invalidate_cached_refs();
+       unlock_ref(lock);
+       return ret;
 }
 
 void unlock_ref(struct ref_lock *lock)
@@ -354,7 +708,7 @@ void unlock_ref(struct ref_lock *lock)
                if (lock->lk)
                        rollback_lock_file(lock->lk);
        }
-       free(lock->ref_file);
+       free(lock->ref_name);
        free(lock->log_file);
        free(lock);
 }
@@ -367,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock,
        char *logrec;
        const char *committer;
 
-       if (log_all_ref_updates) {
+       if (log_all_ref_updates &&
+           !strncmp(lock->ref_name, "refs/heads/", 11)) {
                if (safe_create_leading_directories(lock->log_file) < 0)
                        return error("unable to create directory for %s",
                                lock->log_file);
@@ -376,10 +731,20 @@ static int log_ref_write(struct ref_lock *lock,
 
        logfd = open(lock->log_file, oflags, 0666);
        if (logfd < 0) {
-               if (!log_all_ref_updates && errno == ENOENT)
+               if (!(oflags & O_CREAT) && errno == ENOENT)
                        return 0;
-               return error("Unable to append to %s: %s",
-                       lock->log_file, strerror(errno));
+
+               if ((oflags & O_CREAT) && errno == EISDIR) {
+                       if (remove_empty_directories(lock->log_file)) {
+                               return error("There are still logs under '%s'",
+                                            lock->log_file);
+                       }
+                       logfd = open(lock->log_file, oflags, 0666);
+               }
+
+               if (logfd < 0)
+                       return error("Unable to append to %s: %s",
+                                    lock->log_file, strerror(errno));
        }
 
        committer = git_committer_info(1);
@@ -426,12 +791,13 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return -1;
        }
+       invalidate_cached_refs();
        if (log_ref_write(lock, sha1, logmsg) < 0) {
                unlock_ref(lock);
                return -1;
        }
        if (commit_lock_file(lock->lk)) {
-               error("Couldn't set %s", lock->ref_file);
+               error("Couldn't set %s", lock->ref_name);
                unlock_ref(lock);
                return -1;
        }
@@ -440,7 +806,7 @@ int write_ref_sha1(struct ref_lock *lock,
        return 0;
 }
 
-int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1)
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
@@ -473,7 +839,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
                if (!lastgt)
                        die("Log %s is corrupt.", logfile);
                date = strtoul(lastgt + 1, &tz_c, 10);
-               if (date <= at_time) {
+               if (date <= at_time || cnt == 0) {
                        if (lastrec) {
                                if (get_sha1_hex(lastrec, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
@@ -504,6 +870,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
                        return 0;
                }
                lastrec = rec;
+               if (cnt > 0)
+                       cnt--;
        }
 
        rec = logdata;
diff --git a/refs.h b/refs.h
index 553155c04a4a3586e7a0f07f26979b50b9216aa3..a57d43726a0fee5686e2c0d505f250c6b4adc150 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -2,7 +2,7 @@
 #define REFS_H
 
 struct ref_lock {
-       char *ref_file;
+       char *ref_name;
        char *log_file;
        struct lock_file *lk;
        unsigned char old_sha1[20];
@@ -14,20 +14,23 @@ struct ref_lock {
  * Calls the specified function for each ref file until it returns nonzero,
  * and returns the value
  */
-extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1));
+#define REF_ISSYMREF 01
+#define REF_ISPACKED 02
+typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
+extern int head_ref(each_ref_fn, void *);
+extern int for_each_ref(each_ref_fn, void *);
+extern int for_each_tag_ref(each_ref_fn, void *);
+extern int for_each_branch_ref(each_ref_fn, void *);
+extern int for_each_remote_ref(each_ref_fn, void *);
 
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 
 /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
-extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
 
 /** Locks any ref (for 'HEAD' type refs). */
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1);
 
 /** Release any lock taken but not written. **/
 extern void unlock_ref(struct ref_lock *lock);
@@ -36,7 +39,7 @@ extern void unlock_ref(struct ref_lock *lock);
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
 /** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
+extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1);
 
 /** Returns 0 if target has the right format for a ref. **/
 extern int check_ref_format(const char *target);
index 36cdfcd1ad80caf8545cc02da7881fa92414d665..3dbc26c49c585088ff4861992ce70a94465df401 100644 (file)
@@ -465,7 +465,7 @@ static void limit_list(struct rev_info *revs)
 static int all_flags;
 static struct rev_info *all_revs;
 
-static int handle_one_ref(const char *path, const unsigned char *sha1)
+static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *object = get_reference(all_revs, path, sha1, all_flags);
        add_pending_object(all_revs, object, "");
@@ -476,7 +476,7 @@ static void handle_all(struct rev_info *revs, unsigned flags)
 {
        all_revs = revs;
        all_flags = flags;
-       for_each_ref(handle_one_ref);
+       for_each_ref(handle_one_ref, NULL);
 }
 
 static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
@@ -729,6 +729,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        int i, flags, seen_dashdash, show_merge;
        const char **unrecognized = argv + 1;
        int left = 1;
+       int all_match = 0;
 
        /* First, search for "--" */
        seen_dashdash = 0;
@@ -964,6 +965,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                add_message_grep(revs, arg+7);
                                continue;
                        }
+                       if (!strcmp(arg, "--all-match")) {
+                               all_match = 1;
+                               continue;
+                       }
 
                        opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
                        if (opts > 0) {
@@ -1025,8 +1030,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        if (diff_setup_done(&revs->diffopt) < 0)
                die("diff_setup_done failed");
 
-       if (revs->grep_filter)
+       if (revs->grep_filter) {
+               revs->grep_filter->all_match = all_match;
                compile_grep_patterns(revs->grep_filter);
+       }
 
        return left;
 }
index 5bb123a37696384c5413dac128529d1c1f679940..fbd792ccf4a55ddb5bb09273ceb9e103d242db0e 100644 (file)
@@ -215,7 +215,7 @@ static int ref_newer(const unsigned char *new_sha1,
 static struct ref *local_refs, **local_tail;
 static struct ref *remote_refs, **remote_tail;
 
-static int one_local_ref(const char *refname, const unsigned char *sha1)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct ref *ref;
        int len = strlen(refname) + 1;
@@ -230,7 +230,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1)
 static void get_local_heads(void)
 {
        local_tail = &local_refs;
-       for_each_ref(one_local_ref);
+       for_each_ref(one_local_ref, NULL);
 }
 
 static int receive_status(int in)
index 2fb8f571033918b870cde35979b377c98b9845b5..6cd38be3291e95e75a7d2348e825d49c8f7ad2af 100644 (file)
@@ -7,7 +7,7 @@
 /* refs */
 static FILE *info_ref_fp;
 
-static int add_info_ref(const char *path, const unsigned char *sha1)
+static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = parse_object(sha1);
 
@@ -34,7 +34,7 @@ static int update_info_refs(int force)
        info_ref_fp = fopen(path1, "w");
        if (!info_ref_fp)
                return error("unable to update %s", path0);
-       for_each_ref(add_info_ref);
+       for_each_ref(add_info_ref, NULL);
        fclose(info_ref_fp);
        rename(path1, path0);
        free(path0);
diff --git a/setup.c b/setup.c
index 9a46a58a4a34a345eb9f9b623a255c9bcba4c757..2afdba414a073705440f887593a1b5daa1023758 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -244,8 +244,6 @@ int check_repository_format_version(const char *var, const char *value)
                repository_format_version = git_config_int(var, value);
        else if (strcmp(var, "core.sharedrepository") == 0)
                shared_repository = git_config_perm(var, value);
-       else if (strcmp(var, "receive.denynonfastforwards") == 0)
-               deny_non_fast_forwards = git_config_bool(var, value);
        return 0;
 }
 
index 5fcad2893fec7739262b06e2883ee3c261768099..570706919aeae6ba6d5c12df3babf110be6d76a9 100644 (file)
@@ -877,26 +877,61 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l
        return unpack_sha1_rest(&stream, hdr, *size);
 }
 
+static unsigned long get_delta_base(struct packed_git *p,
+                                   unsigned long offset,
+                                   enum object_type kind,
+                                   unsigned long delta_obj_offset,
+                                   unsigned long *base_obj_offset)
+{
+       unsigned char *base_info = (unsigned char *) p->pack_base + offset;
+       unsigned long base_offset;
+
+       /* there must be at least 20 bytes left regardless of delta type */
+       if (p->pack_size <= offset + 20)
+               die("truncated pack file");
+
+       if (kind == OBJ_OFS_DELTA) {
+               unsigned used = 0;
+               unsigned char c = base_info[used++];
+               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");
+                       c = base_info[used++];
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               base_offset = delta_obj_offset - base_offset;
+               if (base_offset >= delta_obj_offset)
+                       die("delta base offset out of bound");
+               offset += used;
+       } else if (kind == OBJ_REF_DELTA) {
+               /* The base entry _must_ be in the same pack */
+               base_offset = find_pack_entry_one(base_info, p);
+               if (!base_offset)
+                       die("failed to find delta-pack base object %s",
+                               sha1_to_hex(base_info));
+               offset += 20;
+       } else
+               die("I am totally screwed");
+       *base_obj_offset = base_offset;
+       return offset;
+}
+
 /* forward declaration for a mutually recursive function */
 static int packed_object_info(struct packed_git *p, unsigned long offset,
                              char *type, unsigned long *sizep);
 
 static int packed_delta_info(struct packed_git *p,
                             unsigned long offset,
+                            enum object_type kind,
+                            unsigned long obj_offset,
                             char *type,
                             unsigned long *sizep)
 {
        unsigned long base_offset;
-       unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset;
 
-       if (p->pack_size < offset + 20)
-               die("truncated pack file");
-       /* The base entry _must_ be in the same pack */
-       base_offset = find_pack_entry_one(base_sha1, p);
-       if (!base_offset)
-               die("failed to find delta-pack base object %s",
-                   sha1_to_hex(base_sha1));
-       offset += 20;
+       offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
 
        /* We choose to only get the type of the base object and
         * ignore potentially corrupt pack file that expects the delta
@@ -959,25 +994,6 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of
        return offset + used;
 }
 
-int check_reuse_pack_delta(struct packed_git *p, unsigned long offset,
-                          unsigned char *base, unsigned long *sizep,
-                          enum object_type *kindp)
-{
-       unsigned long ptr;
-       int status = -1;
-
-       use_packed_git(p);
-       ptr = offset;
-       ptr = unpack_object_header(p, ptr, kindp, sizep);
-       if (*kindp != OBJ_DELTA)
-               goto done;
-       hashcpy(base, (unsigned char *) p->pack_base + ptr);
-       status = 0;
- done:
-       unuse_packed_git(p);
-       return status;
-}
-
 void packed_object_info_detail(struct packed_git *p,
                               unsigned long offset,
                               char *type,
@@ -986,11 +1002,12 @@ void packed_object_info_detail(struct packed_git *p,
                               unsigned int *delta_chain_length,
                               unsigned char *base_sha1)
 {
-       unsigned long val;
+       unsigned long obj_offset, val;
        unsigned char *next_sha1;
        enum object_type kind;
 
        *delta_chain_length = 0;
+       obj_offset = offset;
        offset = unpack_object_header(p, offset, &kind, size);
 
        for (;;) {
@@ -1005,7 +1022,13 @@ void packed_object_info_detail(struct packed_git *p,
                        strcpy(type, type_names[kind]);
                        *store_size = 0; /* notyet */
                        return;
-               case OBJ_DELTA:
+               case OBJ_OFS_DELTA:
+                       get_delta_base(p, offset, kind, obj_offset, &offset);
+                       if (*delta_chain_length == 0) {
+                               /* TODO: find base_sha1 as pointed by offset */
+                       }
+                       break;
+               case OBJ_REF_DELTA:
                        if (p->pack_size <= offset + 20)
                                die("pack file %s records an incomplete delta base",
                                    p->pack_name);
@@ -1015,6 +1038,7 @@ void packed_object_info_detail(struct packed_git *p,
                        offset = find_pack_entry_one(next_sha1, p);
                        break;
                }
+               obj_offset = offset;
                offset = unpack_object_header(p, offset, &kind, &val);
                (*delta_chain_length)++;
        }
@@ -1023,15 +1047,15 @@ void packed_object_info_detail(struct packed_git *p,
 static int packed_object_info(struct packed_git *p, unsigned long offset,
                              char *type, unsigned long *sizep)
 {
-       unsigned long size;
+       unsigned long size, obj_offset = offset;
        enum object_type kind;
 
        offset = unpack_object_header(p, offset, &kind, &size);
 
-       if (kind == OBJ_DELTA)
-               return packed_delta_info(p, offset, type, sizep);
-
        switch (kind) {
+       case OBJ_OFS_DELTA:
+       case OBJ_REF_DELTA:
+               return packed_delta_info(p, offset, kind, obj_offset, type, sizep);
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
@@ -1077,23 +1101,15 @@ static void *unpack_compressed_entry(struct packed_git *p,
 static void *unpack_delta_entry(struct packed_git *p,
                                unsigned long offset,
                                unsigned long delta_size,
+                               enum object_type kind,
+                               unsigned long obj_offset,
                                char *type,
                                unsigned long *sizep)
 {
        void *delta_data, *result, *base;
        unsigned long result_size, base_size, base_offset;
-       unsigned char *base_sha1;
-
-       if (p->pack_size < offset + 20)
-               die("truncated pack file");
-       /* The base entry _must_ be in the same pack */
-       base_sha1 = (unsigned char*)p->pack_base + offset;
-       base_offset = find_pack_entry_one(base_sha1, p);
-       if (!base_offset)
-               die("failed to find delta-pack base object %s",
-                   sha1_to_hex(base_sha1));
-       offset += 20;
 
+       offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
        base = unpack_entry_gently(p, base_offset, type, &base_size);
        if (!base)
                die("failed to read delta base object at %lu from %s",
@@ -1130,13 +1146,14 @@ static void *unpack_entry(struct pack_entry *entry,
 void *unpack_entry_gently(struct packed_git *p, unsigned long offset,
                          char *type, unsigned long *sizep)
 {
-       unsigned long size;
+       unsigned long size, obj_offset = offset;
        enum object_type kind;
 
        offset = unpack_object_header(p, offset, &kind, &size);
        switch (kind) {
-       case OBJ_DELTA:
-               return unpack_delta_entry(p, offset, size, type, sizep);
+       case OBJ_OFS_DELTA:
+       case OBJ_REF_DELTA:
+               return unpack_delta_entry(p, offset, size, kind, obj_offset, type, sizep);
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
index 6ffee22081aee3fc7b27b9ccc93c4c721652ec4b..6d7cd78381414aa2fef31d31d46fbb24b0aaab1d 100644 (file)
@@ -247,26 +247,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                NULL
        };
        static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
-       const char **p, *pathname;
-       char *real_path = NULL;
-       int refs_found = 0, am;
-       unsigned long at_time = (unsigned long)-1;
+       const char **p, *ref;
+       char *real_ref = NULL;
+       int refs_found = 0;
+       int at, reflog_len;
        unsigned char *this_result;
        unsigned char sha1_from_ref[20];
 
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
-       /* At a given period of time? "@{2 hours ago}" */
-       for (am = 1; am < len - 1; am++) {
-               if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
-                       int date_len = len - am - 3;
-                       char *date_spec = xmalloc(date_len + 1);
-                       strlcpy(date_spec, str + am + 2, date_len + 1);
-                       at_time = approxidate(date_spec);
-                       free(date_spec);
-                       len = am;
-                       break;
+       /* basic@{time or number} format to query ref-log */
+       reflog_len = at = 0;
+       if (str[len-1] == '}') {
+               for (at = 1; at < len - 1; at++) {
+                       if (str[at] == '@' && str[at+1] == '{') {
+                               reflog_len = (len-1) - (at+2);
+                               len = at;
+                               break;
+                       }
                }
        }
 
@@ -276,10 +275,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 
        for (p = fmt; *p; p++) {
                this_result = refs_found ? sha1_from_ref : sha1;
-               pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
-               if (pathname) {
+               ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL);
+               if (ref) {
                        if (!refs_found++)
-                               real_path = xstrdup(pathname);
+                               real_ref = xstrdup(ref);
                        if (!warn_ambiguous_refs)
                                break;
                }
@@ -291,14 +290,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (warn_ambiguous_refs && refs_found > 1)
                fprintf(stderr, warning, len, str);
 
-       if (at_time != (unsigned long)-1) {
-               read_ref_at(
-                       real_path + strlen(git_path(".")) - 1,
-                       at_time,
-                       sha1);
+       if (reflog_len) {
+               /* Is it asking for N-th entry, or approxidate? */
+               int nth, i;
+               unsigned long at_time;
+               for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
+                       char ch = str[at+2+i];
+                       if ('0' <= ch && ch <= '9')
+                               nth = nth * 10 + ch - '0';
+                       else
+                               nth = -1;
+               }
+               if (0 <= nth)
+                       at_time = 0;
+               else
+                       at_time = approxidate(str + at + 2);
+               read_ref_at(real_ref, at_time, nth, sha1);
        }
 
-       free(real_path);
+       free(real_ref);
        return 0;
 }
 
index b3b920edb146c3ad1ed8c795150c162a9e3f0e17..6a917f2ff48661a5d70fdcbcc700cbf659004818 100755 (executable)
@@ -30,11 +30,8 @@ rm -f .git/$m
 test_expect_success \
        "fail to create $n" \
        "touch .git/$n_dir
-        git-update-ref $n $A >out 2>err
-        test "'$? = 1 &&
-        test "" = "$(cat out)" &&
-        grep "error: unable to resolve reference" err &&
-        grep '"$n err"
+        git-update-ref $n $A >out 2>err"'
+        test $? != 0'
 rm -f .git/$n_dir out err
 
 test_expect_success \
index 6907cbcd295614bf895ef2a8d0be59e08e570f09..acb54b6a079b2e790c3bf6eb58007188af7ef1a1 100755 (executable)
@@ -17,13 +17,10 @@ test_expect_success \
      git-commit -m "Initial commit." &&
      HEAD=$(git-rev-parse --verify HEAD)'
 
-test_expect_success \
-    'git branch --help should return success now.' \
-    'git-branch --help'
-
 test_expect_failure \
     'git branch --help should not have created a bogus branch' \
-    'test -f .git/refs/heads/--help'
+    'git-branch --help </dev/null >/dev/null 2>/dev/null || :
+     test -f .git/refs/heads/--help'
 
 test_expect_success \
     'git branch abc should create a branch' \
@@ -34,7 +31,7 @@ test_expect_success \
     'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
 
 cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     branch: Created from HEAD
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     branch: Created from master
 EOF
 test_expect_success \
     'git branch -l d/e/f should create a branch and a log' \
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
new file mode 100755 (executable)
index 0000000..b1e9f2e
--- /dev/null
@@ -0,0 +1,99 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Amos Waterland
+# Copyright (c) 2006 Christian Couder
+#
+
+test_description='git pack-refs should not change the branch semantic
+
+This test runs git pack-refs and git show-ref and checks that the branch
+semantic is still the same.
+'
+. ./test-lib.sh
+
+echo '[core] logallrefupdates = true' >>.git/config
+
+test_expect_success \
+    'prepare a trivial repository' \
+    'echo Hello > A &&
+     git-update-index --add A &&
+     git-commit -m "Initial commit." &&
+     HEAD=$(git-rev-parse --verify HEAD)'
+
+SHA1=
+
+test_expect_success \
+    'see if git show-ref works as expected' \
+    'git-branch a &&
+     SHA1=`cat .git/refs/heads/a` &&
+     echo "$SHA1 refs/heads/a" >expect &&
+     git-show-ref a >result &&
+     diff expect result'
+
+test_expect_success \
+    'see if a branch still exists when packed' \
+    'git-branch b &&
+     git-pack-refs --all &&
+     rm .git/refs/heads/b &&
+     echo "$SHA1 refs/heads/b" >expect &&
+     git-show-ref b >result &&
+     diff expect result'
+
+test_expect_failure \
+    'git branch c/d should barf if branch c exists' \
+    'git-branch c &&
+     git-pack-refs --all &&
+     rm .git/refs/heads/c &&
+     git-branch c/d'
+
+test_expect_success \
+    'see if a branch still exists after git pack-refs --prune' \
+    'git-branch e &&
+     git-pack-refs --all --prune &&
+     echo "$SHA1 refs/heads/e" >expect &&
+     git-show-ref e >result &&
+     diff expect result'
+
+test_expect_failure \
+    'see if git pack-refs --prune remove ref files' \
+    'git-branch f &&
+     git-pack-refs --all --prune &&
+     ls .git/refs/heads/f'
+
+test_expect_success \
+    'git branch g should work when git branch g/h has been deleted' \
+    'git-branch g/h &&
+     git-pack-refs --all --prune &&
+     git-branch -d g/h &&
+     git-branch g &&
+     git-pack-refs --all &&
+     git-branch -d g'
+
+test_expect_failure \
+    'git branch i/j/k should barf if branch i exists' \
+    'git-branch i &&
+     git-pack-refs --all --prune &&
+     git-branch i/j/k'
+
+test_expect_success \
+    'test git branch k after branch k/l/m and k/lm have been deleted' \
+    'git-branch k/l &&
+     git-branch k/lm &&
+     git-branch -d k/l &&
+     git-branch k/l/m &&
+     git-branch -d k/l/m &&
+     git-branch -d k/lm &&
+     git-branch k'
+
+test_expect_success \
+    'test git branch n after some branch deletion and pruning' \
+    'git-branch n/o &&
+     git-branch n/op &&
+     git-branch -d n/o &&
+     git-branch n/o/p &&
+     git-branch -d n/op &&
+     git-pack-refs --all --prune &&
+     git-branch -d n/o/p &&
+     git-branch n'
+
+test_done
index 360a67060e1d1fd501fed7729712213360c456b2..8b19d3ccea5a0d5ff697661202347fbdfc6c330a 100755 (executable)
@@ -52,13 +52,10 @@ test_expect_success \
     'rebase topic branch against new master and check git-am did not get halted' \
     'git-rebase master && test ! -d .dotest'
 
-if test -z "$no_python"
-then
-    test_expect_success \
+test_expect_success \
        'rebase --merge topic branch that was partially merged upstream' \
        'git-checkout -f my-topic-branch-merge &&
         git-rebase --merge master-merge &&
         test ! -d .git/.dotest-merge'
-fi
 
 test_done
index d34c6cf6f3143bac81b69f79329d2d893e379c39..0779aaa9aba16f0f8502505b4df5cc49cfe8af82 100755 (executable)
@@ -7,12 +7,6 @@ test_description='git rebase --merge test'
 
 . ./test-lib.sh
 
-if test "$no_python"; then
-       echo "Skipping: no python => no recursive merge"
-       test_done
-       exit 0
-fi
-
 T="A quick brown fox
 jumps over the lazy dog."
 for i in 1 2 3 4 5 6 7 8 9 10
index bb2531536160bb0a6510cc316d8d74a1da2312e7..977c498f00f2d8e2066bd586130ee84fe1a196a3 100755 (executable)
@@ -10,12 +10,6 @@ test_description='git rebase --merge --skip tests'
 # we assume the default git-am -3 --skip strategy is tested independently
 # and always works :)
 
-if test "$no_python"; then
-       echo "Skipping: no python => no recursive merge"
-       test_done
-       exit 0
-fi
-
 test_expect_success setup '
        echo hello > hello &&
        git add hello &&
index ea48205537b60376b1fab346effb82548d48954e..58e5f74aeae880c69776bfa8e51bc9e5fe2463cf 100644 (file)
@@ -5,7 +5,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 
- create mode 040000 dir
+ create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
 $
index 278eb6670116d0036413a81fc129615974458e5d..cf08e9279c1b2dc61cb53cf1e4940143cb3bf05c 100755 (executable)
@@ -26,6 +26,7 @@ commit id embedding:
 
 . ./test-lib.sh
 TAR=${TAR:-tar}
+UNZIP=${UNZIP:-unzip}
 
 test_expect_success \
     'populate workdir' \
@@ -95,4 +96,38 @@ test_expect_success \
     'validate file contents with prefix' \
     'diff -r a c/prefix/a'
 
+test_expect_success \
+    'git-archive --format=zip' \
+    'git-archive --format=zip HEAD >d.zip'
+
+test_expect_success \
+    'extract ZIP archive' \
+    '(mkdir d && cd d && $UNZIP ../d.zip)'
+
+test_expect_success \
+    'validate filenames' \
+    '(cd d/a && find .) | sort >d.lst &&
+     diff a.lst d.lst'
+
+test_expect_success \
+    'validate file contents' \
+    'diff -r a d/a'
+
+test_expect_success \
+    'git-archive --format=zip with prefix' \
+    'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+
+test_expect_success \
+    'extract ZIP archive with prefix' \
+    '(mkdir e && cd e && $UNZIP ../e.zip)'
+
+test_expect_success \
+    'validate filenames with prefix' \
+    '(cd e/prefix/a && find .) | sort >e.lst &&
+     diff a.lst e.lst'
+
+test_expect_success \
+    'validate file contents with prefix' \
+    'diff -r a e/prefix/a'
+
 test_done
index 8f7366da8d53feb95d08fd57c04b96c7bcbb9625..499cafb882b83b780171c8601b9f4c54b294ed43 100755 (executable)
 test_description='Test criss-cross merge'
 . ./test-lib.sh
 
-if test "$no_python"; then
-       echo "Skipping: no python => no recursive merge"
-       test_done
-       exit 0
-fi
-
 test_expect_success 'prepare repository' \
 'echo "1
 2
index 5ac25647abd366f04867a5acec6a987f314c2ace..497ef36ad883e79cd799a4642e0c4414d8550e1c 100755 (executable)
@@ -3,12 +3,6 @@
 test_description='Merge-recursive merging renames'
 . ./test-lib.sh
 
-if test "$no_python"; then
-       echo "Skipping: no python => no recursive merge"
-       test_done
-       exit 0
-fi
-
 test_expect_success setup \
 '
 cat >A <<\EOF &&
index 7e2f4f088a359fe138b085730ba1b3465342e9e9..37d235e06e2cbfbd761fd02d7e73648a14a60daf 100644 (file)
@@ -215,6 +215,24 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
        return retval;
 }
 
+int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       tree = read_object_with_reference(new, tree_type, &real.size, NULL);
+       if (!tree)
+               die("unable to read root tree (%s)", sha1_to_hex(new));
+       real.buf = tree;
+
+       empty.size = 0;
+       empty.buf = "";
+       retval = diff_tree(&empty, &real, base, opt);
+       free(tree);
+       return retval;
+}
+
 static int count_paths(const char **paths)
 {
        int i = 0;
index 189b239cc093ab1d585627a647c7c36bf65efe18..ddaa72f0a98e9b7f424279b74798bc3cfaadbd48 100644 (file)
@@ -16,7 +16,7 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n
 #define OUR_REF (1U << 1)
 #define WANTED (1U << 2)
 static int multi_ack, nr_our_refs;
-static int use_thin_pack;
+static int use_thin_pack, use_ofs_delta;
 static struct object_array have_obj;
 static struct object_array want_obj;
 static unsigned int timeout;
@@ -137,7 +137,9 @@ static void create_pack_file(void)
                close(pu_pipe[1]);
                close(pe_pipe[0]);
                close(pe_pipe[1]);
-               execl_git_cmd("pack-objects", "--stdout", "--progress", NULL);
+               execl_git_cmd("pack-objects", "--stdout", "--progress",
+                             use_ofs_delta ? "--delta-base-offset" : NULL,
+                             NULL);
                kill(pid_rev_list, SIGKILL);
                die("git-upload-pack: unable to exec git-pack-objects");
        }
@@ -393,6 +395,8 @@ static void receive_needs(void)
                        multi_ack = 1;
                if (strstr(line+45, "thin-pack"))
                        use_thin_pack = 1;
+               if (strstr(line+45, "ofs-delta"))
+                       use_ofs_delta = 1;
                if (strstr(line+45, "side-band-64k"))
                        use_sideband = LARGE_PACKET_MAX;
                else if (strstr(line+45, "side-band"))
@@ -416,9 +420,9 @@ static void receive_needs(void)
        }
 }
 
-static int send_ref(const char *refname, const unsigned char *sha1)
+static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       static const char *capabilities = "multi_ack thin-pack side-band side-band-64k";
+       static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta";
        struct object *o = parse_object(sha1);
 
        if (!o)
@@ -444,8 +448,8 @@ static int send_ref(const char *refname, const unsigned char *sha1)
 static void upload_pack(void)
 {
        reset_timeout();
-       head_ref(send_ref);
-       for_each_ref(send_ref);
+       head_ref(send_ref, NULL);
+       for_each_ref(send_ref, NULL);
        packet_flush(1);
        receive_needs();
        if (want_obj.nr) {
index 4b74e6858400108fe4967cfd371d20915d806379..7dd68575d1909d1a48246814c83d407f8225ea9d 100644 (file)
@@ -41,10 +41,8 @@ void wt_status_prepare(struct wt_status *s)
 
        s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0;
 
-       head = resolve_ref(git_path("HEAD"), sha1, 0);
-       s->branch = head ?
-                   strdup(head + strlen(get_git_dir()) + 1) :
-                   NULL;
+       head = resolve_ref("HEAD", sha1, 0, NULL);
+       s->branch = head ? xstrdup(head) : NULL;
 
        s->reference = "HEAD";
        s->amend = 0;
@@ -72,25 +70,25 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p)
        color_printf(color(WT_STATUS_HEADER), "#\t");
        switch (p->status) {
        case DIFF_STATUS_ADDED:
-               color_printf(c, "new file: %s", p->one->path); break;
+               color_printf(c, "new file:   %s", p->one->path); break;
        case DIFF_STATUS_COPIED:
-               color_printf(c, "copied: %s -> %s",
+               color_printf(c, "copied:     %s -> %s",
                                p->one->path, p->two->path);
                break;
        case DIFF_STATUS_DELETED:
-               color_printf(c, "deleted: %s", p->one->path); break;
+               color_printf(c, "deleted:    %s", p->one->path); break;
        case DIFF_STATUS_MODIFIED:
-               color_printf(c, "modified: %s", p->one->path); break;
+               color_printf(c, "modified:   %s", p->one->path); break;
        case DIFF_STATUS_RENAMED:
-               color_printf(c, "renamed: %s -> %s",
+               color_printf(c, "renamed:    %s -> %s",
                                p->one->path, p->two->path);
                break;
        case DIFF_STATUS_TYPE_CHANGED:
                color_printf(c, "typechange: %s", p->one->path); break;
        case DIFF_STATUS_UNKNOWN:
-               color_printf(c, "unknown: %s", p->one->path); break;
+               color_printf(c, "unknown:    %s", p->one->path); break;
        case DIFF_STATUS_UNMERGED:
-               color_printf(c, "unmerged: %s", p->one->path); break;
+               color_printf(c, "unmerged:   %s", p->one->path); break;
        default:
                die("bug: unhandled diff status %c", p->status);
        }