Merge branch 'jc/pickaxe'
authorJunio C Hamano <junkio@cox.net>
Wed, 8 Nov 2006 00:33:59 +0000 (16:33 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 8 Nov 2006 00:33:59 +0000 (16:33 -0800)
125 files changed:
.gitignore
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-format.txt
Documentation/everyday.txt
Documentation/git-blame.txt
Documentation/git-cherry.txt
Documentation/git-clone.txt
Documentation/git-daemon.txt
Documentation/git-diff-index.txt
Documentation/git-fetch-pack.txt
Documentation/git-for-each-ref.txt [new file with mode: 0644]
Documentation/git-index-pack.txt
Documentation/git-pack-objects.txt
Documentation/git-pack-refs.txt [new file with mode: 0644]
Documentation/git-pull.txt
Documentation/git-rebase.txt
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-show-ref.txt [new file with mode: 0644]
Documentation/git-update-ref.txt
Documentation/git.txt
Documentation/urls.txt
Makefile
archive.h
blame.c
builtin-annotate.c [new file with mode: 0644]
builtin-apply.c
builtin-archive.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-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-packed.c
builtin-prune.c
builtin-push.c
builtin-repo-config.c
builtin-rev-parse.c
builtin-show-branch.c
builtin-show-ref.c [new file with mode: 0644]
builtin-symbolic-ref.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-update-ref.c
builtin.h
cache-tree.c
cache.h
check-builtins.sh [new file with mode: 0755]
combine-diff.c
config.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
contrib/emacs/vc-git.el
daemon.c
describe.c
diff.h
environment.c
fetch-clone.c [deleted file]
fetch-pack.c
fetch.c
fsck-objects.c
git-annotate.perl [deleted file]
git-branch.sh [deleted file]
git-checkout.sh
git-cherry.sh [deleted file]
git-clone.sh
git-commit.sh
git-cvsimport.perl
git-cvsserver.perl
git-fetch.sh
git-merge-one-file.sh
git-merge.sh
git-repack.sh
git-revert.sh
git-send-email.perl
git-svn.perl
git-svnimport.perl
git-tag.sh
git.c
git.spec.in
gitweb/gitweb.css
gitweb/gitweb.perl
http-push.c
imap-send.c
index-pack.c
log-tree.c
merge-recursive.c
object.c
object.h
pack.h
pager.c
path.c
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
show-index.c
t/t1004-read-tree-m-u-wf.sh [new file with mode: 0755]
t/t1400-update-ref.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh [new file with mode: 0755]
t/t3401-rebase-partial.sh
t/t3402-rebase-merge.sh
t/t3403-rebase-skip.sh
t/t6021-merge-criss-cross.sh
t/t6022-merge-rename.sh
t/test-lib.sh
tree-diff.c
unpack-trees.c
upload-pack.c
wt-status.c
xdiff/xemit.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 90722c21fad453791f53d1e8b62df23450622eae..8a3d31631c915cb4ddd0ea79b4983d87b1959f78 100644 (file)
@@ -101,8 +101,13 @@ send it "To:" the mailing list, and optionally "cc:" him.  If it
 is trivially correct or after the list reached a consensus, send
 it "To:" the maintainer and optionally "cc:" the list.
 
+Also note that your maintainer does not actively involve himself in
+maintaining what are in contrib/ hierarchy.  When you send fixes and
+enhancements to them, do not forget to "cc: " the person who primarily
+worked on that hierarchy in contrib/.
 
-(6) Sign your work
+
+(4) Sign your work
 
 To improve tracking of who did what, we've borrowed the
 "sign-off" procedure from the Linux kernel project on patches
index 84e38911eeecd3f4e5f195ada61cb9bc831a4627..9d3c71c3b87a9603973de42c76c4570a94420398 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
@@ -230,6 +234,22 @@ pull.octopus::
 pull.twohead::
        The default merge strategy to use when pulling a single branch.
 
+remote.<name>.url::
+       The URL of a remote repository.  See gitlink:git-fetch[1] or
+       gitlink:git-push[1].
+
+remote.<name>.fetch::
+       The default set of "refspec" for gitlink:git-fetch[1]. See
+       gitlink:git-fetch[1].
+
+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].
@@ -281,7 +301,16 @@ imap::
        The configuration variables in the 'imap' section are described
        in gitlink:git-imap-send[1].
 
-receive.denyNonFastforwads::
+receive.unpackLimit::
+       If the number of objects received in a push is below this
+       limit then the objects will be unpacked into loose object
+       files. However if the number of received objects equals or
+       exceeds this limit then the received pack will be stored as
+       a pack, after adding any missing delta bases.  Storing the
+       pack from a push can make the push operation complete faster,
+       especially on slow filesystems.
+
+receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
        not a fast forward. Use this to prevent such an update via a push,
        even if that push is forced. This configuration variable is
index 617d8f526f914360c612d2e2822f1c883c9f5115..e4520e28e53661159454e02c703be772d43bbc09 100644 (file)
@@ -144,8 +144,10 @@ the file that rename/copy produces, respectively.
        dissimilarity index <number>
        index <hash>..<hash> <mode>
 
-3.  TAB, LF, and backslash characters in pathnames are
-    represented as `\t`, `\n`, and `\\`, respectively.
+3.  TAB, LF, double quote and backslash characters in pathnames
+    are represented as `\t`, `\n`, `\"` and `\\`, respectively.
+    If there is need for such substitution then the whole
+    pathname is put in double quotes.
 
 
 combined diff format
@@ -156,31 +158,91 @@ to produce 'combined diff', which looks like this:
 
 ------------
 diff --combined describe.c
-@@@ +98,7 @@@
-   return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+index fabadb8,cc95eb0..4866510
+--- a/describe.c
++++ b/describe.c
+@@@ -98,20 -98,12 +98,20 @@@
+       return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
   }
-
+  
 - static void describe(char *arg)
  -static void describe(struct commit *cmit, int last_one)
 ++static void describe(char *arg, int last_one)
   {
- +     unsigned char sha1[20];
- +     struct commit *cmit;
+ +     unsigned char sha1[20];
+ +     struct commit *cmit;
+       struct commit_list *list;
+       static int initialized = 0;
+       struct commit_name *n;
+  
+ +     if (get_sha1(arg, sha1) < 0)
+ +             usage(describe_usage);
+ +     cmit = lookup_commit_reference(sha1);
+ +     if (!cmit)
+ +             usage(describe_usage);
+ +
+       if (!initialized) {
+               initialized = 1;
+               for_each_ref(get_name);
 ------------
 
+1.   It is preceded with a "git diff" header, that looks like
+     this (when '-c' option is used):
+
+       diff --combined file
++
+or like this (when '--cc' option is used):
+
+       diff --c file
+
+2.   It is followed by one or more extended header lines
+     (this example shows a merge with two parents):
+
+       index <hash>,<hash>..<hash>
+       mode <mode>,<mode>..<mode>
+       new file mode <mode>
+       deleted file mode <mode>,<mode>
++
+The `mode <mode>,<mode>..<mode>` line appears only if at least one of
+the <mode> is diferent from the rest. Extended headers with
+information about detected contents movement (renames and
+copying detection) are designed to work with diff of two
+<tree-ish> and are not used by combined diff format.
+
+3.   It is followed by two-line from-file/to-file header
+
+       --- a/file
+       +++ b/file
++
+Similar to two-line header for traditional 'unified' diff
+format, `/dev/null` is used to signal created or deleted
+files.
+
+4.   Chunk header format is modified to prevent people from
+     accidentally feeding it to `patch -p1`. Combined diff format
+     was created for review of merge commit changes, and was not
+     meant for apply. The change is similar to the change in the
+     extended 'index' header:
+
+       @@@ <from-file-range> <from-file-range> <to-file-range> @@@
++
+There are (number of parents + 1) `@` characters in the chunk
+header for combined diff format.
+
 Unlike the traditional 'unified' diff format, which shows two
 files A and B with a single column that has `-` (minus --
 appears in A but removed in B), `+` (plus -- missing in A but
-added to B), or ` ` (space -- unchanged) prefix, this format
+added to B), or `" "` (space -- unchanged) prefix, this format
 compares two or more files file1, file2,... with one file X, and
 shows how X differs from each of fileN.  One column for each of
 fileN is prepended to the output line to note how X's line is
 different from it.
 
 A `-` character in the column N means that the line appears in
-fileN but it does not appear in the last file.  A `+` character
+fileN but it does not appear in the result.  A `+` character
 in the column N means that the line appears in the last file,
-and fileN does not have that line.
+and fileN does not have that line (in other words, the line was
+added, from the point of view of that parent).
 
 In the above example output, the function signature was changed
 from both files (hence two `-` removals from both file1 and
index b935c180881571a964356e63a2e718c2721b5e6b..9677671892700ae246dcfe36dc806a548fafe2d2 100644 (file)
@@ -1,22 +1,7 @@
 Everyday GIT With 20 Commands Or So
 ===================================
 
-GIT suite has over 100 commands, and the manual page for each of
-them discusses what the command does and how it is used in
-detail, but until you know what command should be used in order
-to achieve what you want to do, you cannot tell which manual
-page to look at, and if you know that already you do not need
-the manual.
-
-Does that mean you need to know all of them before you can use
-git?  Not at all.  Depending on the role you play, the set of
-commands you need to know is slightly different, but in any case
-what you need to learn is far smaller than the full set of
-commands to carry out your day-to-day work.  This document is to
-serve as a cheat-sheet and a set of pointers for people playing
-various roles.
-
-<<Basic Repository>> commands are needed by people who has a
+<<Basic Repository>> commands are needed by people who have a
 repository --- that is everybody, because every working tree of
 git is a repository.
 
@@ -25,28 +10,27 @@ essential for anybody who makes a commit, even for somebody who
 works alone.
 
 If you work with other people, you will need commands listed in
-<<Individual Developer (Participant)>> section as well.
+the <<Individual Developer (Participant)>> section as well.
 
-People who play <<Integrator>> role need to learn some more
+People who play the <<Integrator>> role need to learn some more
 commands in addition to the above.
 
 <<Repository Administration>> commands are for system
-administrators who are responsible to care and feed git
-repositories to support developers.
+administrators who are responsible for the care and feeding
+of git repositories.
 
 
 Basic Repository[[Basic Repository]]
 ------------------------------------
 
-Everybody uses these commands to feed and care git repositories.
+Everybody uses these commands to maintain git repositories.
 
   * gitlink:git-init-db[1] or gitlink:git-clone[1] to create a
     new repository.
 
-  * gitlink:git-fsck-objects[1] to validate the repository.
+  * gitlink:git-fsck-objects[1] to check the repository for errors.
 
-  * gitlink:git-prune[1] to garbage collect cruft in the
-    repository.
+  * gitlink:git-prune[1] to remove unused objects in the repository.
 
   * gitlink:git-repack[1] to pack loose objects for efficiency.
 
@@ -78,8 +62,8 @@ $ git repack -a -d <1>
 $ git prune
 ------------
 +
-<1> pack all the objects reachable from the refs into one pack
-and remove unneeded other packs
+<1> pack all the objects reachable from the refs into one pack,
+then remove the other packs.
 
 
 Individual Developer (Standalone)[[Individual Developer (Standalone)]]
@@ -93,9 +77,6 @@ following commands.
 
   * gitlink:git-log[1] to see what happened.
 
-  * gitlink:git-whatchanged[1] to find out where things have
-    come from.
-
   * gitlink:git-checkout[1] and gitlink:git-branch[1] to switch
     branches.
 
@@ -120,7 +101,7 @@ following commands.
 Examples
 ~~~~~~~~
 
-Extract a tarball and create a working tree and a new repository to keep track of it.::
+Use a tarball as a starting point for a new repository:
 +
 ------------
 $ tar zxf frotz.tar.gz
@@ -203,7 +184,7 @@ $ cd my2.6
 $ edit/compile/test; git commit -a -s <1>
 $ git format-patch origin <2>
 $ git pull <3>
-$ git whatchanged -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
+$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
 $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
 $ git reset --hard ORIG_HEAD <6>
 $ git prune <7>
@@ -372,12 +353,19 @@ example of managing a shared central repository.
 
 Examples
 ~~~~~~~~
+We assume the following in /etc/services::
++
+------------
+$ grep 9418 /etc/services
+git            9418/tcp                # Git Version Control System
+------------
+
 Run git-daemon to serve /pub/scm from inetd.::
 +
 ------------
 $ grep git /etc/inetd.conf
 git    stream  tcp     nowait  nobody \
-  /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
+  /usr/bin/git-daemon git-daemon --inetd --export-all /pub/scm
 ------------
 +
 The actual configuration line should be on one line.
@@ -397,7 +385,7 @@ service git
         wait            = no
         user            = nobody
         server          = /usr/bin/git-daemon
-        server_args     = --inetd --syslog --export-all --base-path=/pub/scm
+        server_args     = --inetd --export-all --base-path=/pub/scm
         log_on_failure  += USERID
 }
 ------------
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]
index 893baaa6f656616b2151c572450e037fabee7622..27b67b81a58956c48452b7aee46b9742ca02e31a 100644 (file)
@@ -7,17 +7,33 @@ git-cherry - Find commits not merged upstream
 
 SYNOPSIS
 --------
-'git-cherry' [-v] <upstream> [<head>]
+'git-cherry' [-v] <upstream> [<head>] [<limit>]
 
 DESCRIPTION
 -----------
 The changeset (or "diff") of each commit between the fork-point and <head>
 is compared against each commit between the fork-point and <upstream>.
 
-Every commit with a changeset that doesn't exist in the other branch
-has its id (sha1) reported, prefixed by a symbol.  Those existing only
+Every commit that doesn't exist in the <upstream> branch
+has its id (sha1) reported, prefixed by a symbol.  The ones that have
+equivalent change already
 in the <upstream> branch are prefixed with a minus (-) sign, and those
-that only exist in the <head> branch are prefixed with a plus (+) symbol.
+that only exist in the <head> branch are prefixed with a plus (+) symbol:
+
+               __*__*__*__*__> <upstream>
+              /
+    fork-point
+              \__+__+__-__+__+__-__+__> <head>
+
+
+If a <limit> has been given then the commits along the <head> branch up
+to and including <limit> are not reported:
+
+               __*__*__*__*__> <upstream>
+              /
+    fork-point
+              \__*__*__<limit>__-__+__> <head>
+
 
 Because git-cherry compares the changeset rather than the commit id
 (sha1), you can use git-cherry to find out if a commit you made locally
index f973c64313699713100ba63822d80fd86979ff78..86060472ad88088bcb59df5eb9acce097f68cb83 100644 (file)
@@ -75,6 +75,7 @@ OPTIONS
        this option is used, neither the `origin` branch nor the
        default `remotes/origin` file is created.
 
+--origin <name>::
 -o <name>::
        Instead of using the branch name 'origin' to keep track
        of the upstream repository, use <name> instead.  Note
index d562232e522fba78721328b1c76ce08490b0ee3e..993adc7c5adf63f144d7ce257a670363abe18322 100644 (file)
@@ -37,6 +37,8 @@ from `git-fetch`, `git-ls-remote`, and `git-clone`.
 This is ideally suited for read-only updates, i.e., pulling from
 git repositories.
 
+An `upload-archive` also exists to serve `git-archive`.
+
 OPTIONS
 -------
 --strict-paths::
@@ -155,8 +157,18 @@ upload-pack::
        disable it by setting `daemon.uploadpack` configuration
        item to `false`.
 
+upload-archive::
+       This serves `git-archive --remote`.
+
 EXAMPLES
 --------
+We assume the following in /etc/services::
++
+------------
+$ grep 9418 /etc/services
+git            9418/tcp                # Git Version Control System
+------------
+
 git-daemon as inetd server::
        To set up `git-daemon` as an inetd service that handles any
        repository under the whitelisted set of directories, /pub/foo
@@ -165,8 +177,7 @@ git-daemon as inetd server::
 +
 ------------------------------------------------
        git stream tcp nowait nobody  /usr/bin/git-daemon
-               git-daemon --inetd --verbose
-               --syslog --export-all
+               git-daemon --inetd --verbose --export-all
                /pub/foo /pub/bar
 ------------------------------------------------
 
@@ -179,8 +190,7 @@ git-daemon as inetd server for virtual hosts::
 +
 ------------------------------------------------
        git stream tcp nowait nobody /usr/bin/git-daemon
-               git-daemon --inetd --verbose
-               --syslog --export-all
+               git-daemon --inetd --verbose --export-all
                --interpolated-path=/pub/%H%D
                /pub/www.example.org/software
                /pub/www.example.com/software
index 9cd43f105bd78778359ded7c6b3300502d642ca9..2df581c2c9e598c676fcb95c35c91209d15562ca 100644 (file)
@@ -54,7 +54,7 @@ If '--cached' is specified, it allows you to ask:
 
 For example, let's say that you have worked on your working directory, updated
 some files in the index and are ready to commit. You want to see exactly
-*what* you are going to commit is without having to write a new tree
+*what* you are going to commit, without having to write a new tree
 object and compare it that way, and to do that, you just do
 
        git-diff-index --cached HEAD
@@ -68,7 +68,7 @@ matches my working directory. But doing a "git-diff-index" does:
   -100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        commit.c
   +100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        git-commit.c
 
-You can trivially see that the above is a rename.
+You can see easily that the above is a rename.
 
 In fact, "git-diff-index --cached" *should* always be entirely equivalent to
 actually doing a "git-write-tree" and comparing that. Except this one is much
index bff9aa693938b1bee26e8912917cc4139f947e65..3e6cd880b0e6aeda3056da1361ffcae52b89b531 100644 (file)
@@ -32,7 +32,8 @@ OPTIONS
 -k::
        Do not invoke 'git-unpack-objects' on received data, but
        create a single packfile out of it instead, and store it
-       in the object database.
+       in the object database. If provided twice then the pack is
+       locked against repacking.
 
 --exec=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
new file mode 100644 (file)
index 0000000..2bf6aef
--- /dev/null
@@ -0,0 +1,185 @@
+git-for-each-ref(1)
+===================
+
+NAME
+----
+git-for-each-ref - Output information on each ref
+
+SYNOPSIS
+--------
+'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python] [--sort=<key>]\* [--format=<format>] [<pattern>]
+
+DESCRIPTION
+-----------
+
+Iterate over all refs that match `<pattern>` and show them
+according to the given `<format>`, after sorting them according
+to the given set of `<key>`.  If `<max>` is given, stop after
+showing that many refs.  The interporated values in `<format>`
+can optionally be quoted as string literals in the specified
+host language allowing their direct evaluation in that language.
+
+OPTIONS
+-------
+<count>::
+       By default the command shows all refs that match
+       `<pattern>`.  This option makes it stop after showing
+       that many refs.
+
+<key>::
+       A field name to sort on.  Prefix `-` to sort in
+       descending order of the value.  When unspecified,
+       `refname` is used.  More than one sort keys can be
+       given.
+
+<format>::
+       A string that interpolates `%(fieldname)` from the
+       object pointed at by a ref being shown.  If `fieldname`
+       is prefixed with an asterisk (`*`) and the ref points
+       at a tag object, the value for the field in the object
+       tag refers is used.  When unspecified, defaults to
+       `%(objectname) SPC %(objecttype) TAB %(refname)`.
+       It also interpolates `%%` to `%`, and `%xx` where `xx`
+       are hex digits interpolates to character with hex code
+       `xx`; for example `%00` interpolates to `\0` (NUL),
+       `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+
+<pattern>::
+       If given, the name of the ref is matched against this
+       using fnmatch(3).  Refs that do not match the pattern
+       are not shown.
+
+--shell, --perl, --python::
+       If given, strings that substitute `%(fieldname)`
+       placeholders are quoted as string literals suitable for
+       the specified host language.  This is meant to produce
+       a scriptlet that can directly be `eval`ed.
+
+
+FIELD NAMES
+-----------
+
+Various values from structured fields in referenced objects can
+be used to interpolate into the resulting output, or as sort
+keys.
+
+For all objects, the following names can be used:
+
+refname::
+       The name of the ref (the part after $GIT_DIR/refs/).
+
+objecttype::
+       The type of the object (`blob`, `tree`, `commit`, `tag`).
+
+objectsize::
+       The size of the object (the same as `git-cat-file -s` reports).
+
+objectname::
+       The object name (aka SHA-1).
+
+In addition to the above, for commit and tag objects, the header
+field names (`tree`, `parent`, `object`, `type`, and `tag`) can
+be used to specify the value in the header field.
+
+Fields that have name-email-date tuple as its value (`author`,
+`committer`, and `tagger`) can be suffixed with `name`, `email`,
+and `date` to extract the named component.
+
+The first line of the message in a commit and tag object is
+`subject`, the remaining lines are `body`.  The whole message
+is `contents`.
+
+For sorting purposes, fields with numeric values sort in numeric
+order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
+All other fields are used to sort in their byte-value order.
+
+In any case, a field name that refers to a field inapplicable to
+the object referred by the ref does not cause an error.  It
+returns an empty string instead.
+
+
+EXAMPLES
+--------
+
+An example directly producing formatted text.  Show the most recent
+3 tagged commits::
+
+------------
+#!/bin/sh
+
+git-for-each-ref --count=3 --sort='-*authordate' \
+--format='From: %(*authorname) %(*authoremail)
+Subject: %(*subject)
+Date: %(*authordate)
+Ref: %(*refname)
+
+%(*body)
+' 'refs/tags'
+------------
+
+
+A simple example showing the use of shell eval on the output,
+demonstrating the use of --shell.  List the prefixes of all heads::
+------------
+#!/bin/sh
+
+git-for-each-ref --shell --format="ref=%(refname)" refs/heads | \
+while read entry
+do
+       eval "$entry"
+       echo `dirname $ref`
+done
+------------
+
+
+A bit more elaborate report on tags, demonstrating that the format
+may be an entire script::
+------------
+#!/bin/sh
+
+fmt='
+       r=%(refname)
+       t=%(*objecttype)
+       T=${r#refs/tags/}
+
+       o=%(*objectname)
+       n=%(*authorname)
+       e=%(*authoremail)
+       s=%(*subject)
+       d=%(*authordate)
+       b=%(*body)
+
+       kind=Tag
+       if test "z$t" = z
+       then
+               # could be a lightweight tag
+               t=%(objecttype)
+               kind="Lightweight tag"
+               o=%(objectname)
+               n=%(authorname)
+               e=%(authoremail)
+               s=%(subject)
+               d=%(authordate)
+               b=%(body)
+       fi
+       echo "$kind $T points at a $t object $o"
+       if test "z$t" = zcommit
+       then
+               echo "The commit was authored by $n $e
+at $d, and titled
+
+    $s
+
+Its message reads as:
+"
+               echo "$b" | sed -e "s/^/    /"
+               echo
+       fi
+'
+
+eval=`git-for-each-ref --shell --format="$fmt" \
+       --sort='*objecttype' \
+       --sort=-taggerdate \
+       refs/tags`
+eval "$eval"
+------------
index 71ce557276fd1bfe5e3ee1d3f03f94e066f23e81..2229ee86b72865c656112aae87b9593d199bc2b8 100644 (file)
@@ -8,7 +8,8 @@ git-index-pack - Build pack index file for an existing packed archive
 
 SYNOPSIS
 --------
-'git-index-pack' [-o <index-file>] <pack-file>
+'git-index-pack' [-v] [-o <index-file>] <pack-file>
+'git-index-pack' --stdin [--fix-thin] [--keep] [-v] [-o <index-file>] [<pack-file>]
 
 
 DESCRIPTION
@@ -21,6 +22,9 @@ objects/pack/ directory of a git repository.
 
 OPTIONS
 -------
+-v::
+       Be verbose about what is going on, including progress status.
+
 -o <index-file>::
        Write the generated pack index into the specified
        file.  Without this option the name of pack index
@@ -29,6 +33,52 @@ OPTIONS
        fails if the name of packed archive does not end
        with .pack).
 
+--stdin::
+       When this flag is provided, the pack is read from stdin
+       instead and a copy is then written to <pack-file>. If
+       <pack-file> is not specified, the pack is written to
+       objects/pack/ directory of the current git repository with
+       a default name determined from the pack content.  If
+       <pack-file> is not specified consider using --keep to
+       prevent a race condition between this process and
+       gitlink::git-repack[1] .
+
+--fix-thin::
+       It is possible for gitlink:git-pack-objects[1] to build
+       "thin" pack, which records objects in deltified form based on
+       objects not included in the pack to reduce network traffic.
+       Those objects are expected to be present on the receiving end
+       and they must be included in the pack for that pack to be self
+       contained and indexable. Without this option any attempt to
+       index a thin pack will fail. This option only makes sense in
+       conjunction with --stdin.
+
+--keep::
+       Before moving the index into its final destination
+       create an empty .keep file for the associated pack file.
+       This option is usually necessary with --stdin to prevent a
+       simultaneous gitlink:git-repack[1] process from deleting
+       the newly constructed pack and index before refs can be
+       updated to use objects contained in the pack.
+
+--keep='why'::
+       Like --keep create a .keep file before moving the index into
+       its final destination, but rather than creating an empty file
+       place 'why' followed by an LF into the .keep file.  The 'why'
+       message can later be searched for within all .keep files to
+       locate any which have outlived their usefulness.
+
+
+Note
+----
+
+Once the index has been created, the list of object names is sorted
+and the SHA1 hash of that list is printed to stdout. If --stdin was
+also used then this is prefixed by either "pack\t", or "keep\t" if a
+new .keep file was successfully created. This is useful to remove a
+.keep file used as a lock to prevent the race with gitlink:git-repack[1]
+mentioned above.
+
 
 Author
 ------
index f52e8fa8bfb6cd1b121eab7e8594368f5b1c6513..fdc6f9728921e3bf71a57d441c99c8fb7d280803 100644 (file)
@@ -9,8 +9,8 @@ git-pack-objects - Create a packed archive of objects
 SYNOPSIS
 --------
 [verse]
-'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
-       [--local] [--incremental] [--window=N] [--depth=N]
+'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+       [--local] [--incremental] [--window=N] [--depth=N] [--all-progress]
        [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
 
 
@@ -47,9 +47,8 @@ base-name::
        <base-name> to determine the name of the created file.
        When this option is used, the two files are written in
        <base-name>-<SHA1>.{pack,idx} files.  <SHA1> is a hash
-       of object names (currently in random order so it does
-       not have any useful meaning) to make the resulting
-       filename reasonably unique, and written to the standard
+       of the sorted object names to make the resulting filename
+       based on the pack content, and written to the standard
        output of the command.
 
 --stdout::
@@ -100,6 +99,23 @@ base-name::
         Only create a packed archive if it would contain at
         least one object.
 
+--progress::
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if
+       the standard error stream is not directed to a terminal.
+
+--all-progress::
+       When --stdout is specified then progress report is
+       displayed during the object count and deltification phases
+       but inhibited during the write-out phase. The reason is
+       that in some cases the output stream is directly linked
+       to another command which may wish to display progress
+       status of its own as it processes incoming pack data.
+       This flag is like --progress except that it forces progress
+       report for the write-out phase as well even if --stdout is
+       used.
+
 -q::
        This flag makes the command not to report its progress
        on the standard error stream.
@@ -111,6 +127,17 @@ base-name::
        This flag tells the command not to reuse existing deltas
        but compute them from scratch.
 
+--delta-base-offset::
+       A packed archive can express base object of a delta as
+       either 20-byte object name or as an offset in the
+       stream, but older version of git does not understand the
+       latter.  By default, git-pack-objects only uses the
+       former format for better compatibility.  This option
+       allows the command to use the latter format for
+       compactness.  Depending on the average delta chain
+       length, this option typically shrinks the resulting
+       packfile by 3-5 per-cent.
+
 
 Author
 ------
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
new file mode 100644 (file)
index 0000000..5da5105
--- /dev/null
@@ -0,0 +1,54 @@
+git-pack-refs(1)
+================
+
+NAME
+----
+git-pack-refs - Pack heads and tags for efficient repository access
+
+SYNOPSIS
+--------
+'git-pack-refs' [--all] [--prune]
+
+DESCRIPTION
+-----------
+
+Traditionally, tips of branches and tags (collectively known as
+'refs') were stored one file per ref under `$GIT_DIR/refs`
+directory.  While many branch tips tend to be updated often,
+most tags and some branch tips are never updated.  When a
+repository has hundreds or thousands of tags, this
+one-file-per-ref format both wastes storage and hurts
+performance.
+
+This command is used to solve the storage and performance
+problem by stashing the refs in a single file,
+`$GIT_DIR/packed-refs`.  When a ref is missing from the
+traditional `$GIT_DIR/refs` hierarchy, it is looked up in this
+file and used if found.
+
+Subsequent updates to branches always creates new file under
+`$GIT_DIR/refs` hierarchy.
+
+OPTIONS
+-------
+
+\--all::
+
+The command by default packs all tags and leaves branch tips
+alone.  This is because branches are expected to be actively
+developed and packing their tips does not help performance.
+This option causes branch tips to be packed as well.  Useful for
+a repository with many branches of historical interests.
+
+\--prune::
+
+After packing the refs, remove loose refs under `$GIT_DIR/refs`
+hierarchy.  This should probably become default.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+GIT
+---
+Part of the gitlink:git[7] suite
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 10f2924f4df1eb29c1baf4484d2837377b6cfcb3..03e867a403a034e49e63fc0123970b44a199e8b5 100644 (file)
@@ -51,20 +51,69 @@ would be:
     D---E---F---G master
 ------------
 
-While, starting from the same point, the result of either of the following
-commands:
+The latter form is just a short-hand of `git checkout topic`
+followed by `git rebase master`.
 
-    git-rebase --onto master~1 master
-    git-rebase --onto master~1 master topic
+Here is how you would transplant a topic branch based on one
+branch to another, to pretend that you forked the topic branch
+from the latter branch, using `rebase --onto`.
 
-would be:
+First let's assume your 'topic' is based on branch 'next'.
+For example feature developed in 'topic' depends on some
+functionality which is found in 'next'.
 
 ------------
-              A'--B'--C' topic
-             /
-    D---E---F---G master
+    o---o---o---o---o  master
+         \
+          o---o---o---o---o  next
+                           \
+                            o---o---o  topic
+------------
+
+We would want to make 'topic' forked from branch 'master',
+for example because the functionality 'topic' branch depend on
+got merged into more stable 'master' branch, like this:
+
+------------
+    o---o---o---o---o  master
+        |            \
+        |             o'--o'--o'  topic
+         \
+          o---o---o---o---o  next
 ------------
 
+We can get this using the following command:
+
+    git-rebase --onto master next topic
+
+
+Another example of --onto option is to rebase part of a
+branch.  If we have the following situation:
+
+------------
+                            H---I---J topicB
+                           /
+                  E---F---G  topicA
+                 /
+    A---B---C---D  master
+------------
+
+then the command
+
+    git-rebase --onto master topicA topicB
+
+would result in:
+
+------------
+                 H'--I'--J'  topicB
+                /
+                | E---F---G  topicA
+                |/
+    A---B---C---D  master
+------------
+
+This is useful when topicB does not depend on topicA.
+
 In case of conflict, git-rebase will stop at the first problematic commit
 and leave conflict markers in the tree.  You can use git diff to locate
 the markers (<<<<<<) and make edits to resolve the conflict.  For each
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 8a1ab61e943aba92aed6734c949b196850914a3a..8199615dde6630124c155bb4a296603dc8829c83 100644 (file)
@@ -3,19 +3,19 @@ git-repo-config(1)
 
 NAME
 ----
-git-repo-config - Get and set options in .git/config
+git-repo-config - Get and set repository or global options.
 
 
 SYNOPSIS
 --------
 [verse]
-'git-repo-config' [type] name [value [value_regex]]
-'git-repo-config' [type] --replace-all name [value [value_regex]]
-'git-repo-config' [type] --get name [value_regex]
-'git-repo-config' [type] --get-all name [value_regex]
-'git-repo-config' [type] --unset name [value_regex]
-'git-repo-config' [type] --unset-all name [value_regex]
-'git-repo-config' -l | --list
+'git-repo-config' [--global] [type] name [value [value_regex]]
+'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
+'git-repo-config' [--global] [type] --get name [value_regex]
+'git-repo-config' [--global] [type] --get-all name [value_regex]
+'git-repo-config' [--global] [type] --unset name [value_regex]
+'git-repo-config' [--global] [type] --unset-all name [value_regex]
+'git-repo-config' [--global] -l | --list
 
 DESCRIPTION
 -----------
@@ -41,8 +41,9 @@ This command will fail if:
 . Can not write to .git/config,
 . no section was provided,
 . the section or key is invalid,
-. you try to unset an option which does not exist, or
-. you try to unset/set an option for which multiple lines match.
+. you try to unset an option which does not exist,
+. you try to unset/set an option for which multiple lines match, or
+. you use --global option without $HOME being properly set.
 
 
 OPTIONS
@@ -64,14 +65,17 @@ OPTIONS
 --get-regexp::
        Like --get-all, but interprets the name as a regular expression.
 
+--global::
+       Use global ~/.gitconfig file rather than the repository .git/config.
+
 --unset::
-       Remove the line matching the key from .git/config.
+       Remove the line matching the key from config file.
 
 --unset-all::
-       Remove all matching lines from .git/config.
+       Remove all matching lines from config file.
 
 -l, --list::
-       List all variables set in .git/config.
+       List all variables set in config file.
 
 
 ENVIRONMENT
@@ -79,6 +83,7 @@ ENVIRONMENT
 
 GIT_CONFIG::
        Take the configuration from the given file instead of .git/config.
+       Using the "--global" option forces this to ~/.gitconfig.
 
 GIT_CONFIG_LOCAL::
        Currently the same as $GIT_CONFIG; when Git will support global
index cda80b18f45c51b038838a62d0707172f2f273c9..4eaf5a0d1ea26798298c4f572c29d90af9dc7cf1 100644 (file)
@@ -122,14 +122,30 @@ blobs contained in a commit.
   your repository whose object name starts with dae86e.
 
 * An output from `git-describe`; i.e. a closest tag, followed by a
-  dash, a 'g', and an abbreviated object name.
+  dash, a `g`, and an abbreviated object name.
 
 * A symbolic ref name.  E.g. 'master' typically means the commit
   object referenced by $GIT_DIR/refs/heads/master.  If you
   happen to have both heads/master and tags/master, you can
   explicitly say 'heads/master' to tell git which one you mean.
+  When ambiguous, a `<name>` is disambiguated by taking the
+  first match in the following rules:
 
-* A suffix '@' followed by a date specification enclosed in a brace
+  . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
+    useful only for `HEAD`, `FETCH_HEAD` and `MERGE_HEAD`);
+
+  . otherwise, `$GIT_DIR/refs/<name>` if exists;
+
+  . otherwise, `$GIT_DIR/refs/tags/<name>` if exists;
+
+  . otherwise, `$GIT_DIR/refs/heads/<name>` if exists;
+
+  . otherwise, `$GIT_DIR/refs/remotes/<name>` if exists;
+
+  . otherwise, `$GIT_DIR/refs/remotes/<name>/HEAD` if exists.
+
+* A ref followed by the suffix '@' with a date specification
+  enclosed in a brace
   pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
   second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
   of the ref at a prior point in time.  This suffix may only be
@@ -146,8 +162,9 @@ blobs contained in a commit.
 * A suffix '{tilde}<n>' to a revision parameter means the commit
   object that is the <n>th generation grand-parent of the named
   commit object, following only the first parent.  I.e. rev~3 is
-  equivalent to rev{caret}{caret}{caret} which is equivalent to\
-  rev{caret}1{caret}1{caret}1.
+  equivalent to rev{caret}{caret}{caret} which is equivalent to
+  rev{caret}1{caret}1{caret}1.  See below for a illustration of
+  the usage of this form.
 
 * A suffix '{caret}' followed by an object type name enclosed in
   brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
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 7074e324584b60746d8797e918f3bdd7d0cb8e41..4facf2309512380fc796e5f64e400c6ebcb08dbd 100644 (file)
@@ -72,185 +72,6 @@ GIT COMMANDS
 We divide git into high level ("porcelain") commands and low level
 ("plumbing") commands.
 
-Low-level commands (plumbing)
------------------------------
-
-Although git includes its
-own porcelain layer, its low-level commands are sufficient to support
-development of alternative porcelains.  Developers of such porcelains
-might start by reading about gitlink:git-update-index[1] and
-gitlink:git-read-tree[1].
-
-We divide the low-level commands into commands that manipulate objects (in
-the repository, index, and working tree), commands that interrogate and
-compare objects, and commands that move objects and references between
-repositories.
-
-Manipulation commands
-~~~~~~~~~~~~~~~~~~~~~
-gitlink:git-apply[1]::
-       Reads a "diff -up1" or git generated patch file and
-       applies it to the working tree.
-
-gitlink:git-checkout-index[1]::
-       Copy files from the index to the working tree.
-
-gitlink:git-commit-tree[1]::
-       Creates a new commit object.
-
-gitlink:git-hash-object[1]::
-       Computes the object ID from a file.
-
-gitlink:git-index-pack[1]::
-       Build pack idx file for an existing packed archive.
-
-gitlink:git-init-db[1]::
-       Creates an empty git object database, or reinitialize an
-       existing one.
-
-gitlink:git-merge-index[1]::
-       Runs a merge for files needing merging.
-
-gitlink:git-mktag[1]::
-       Creates a tag object.
-
-gitlink:git-mktree[1]::
-       Build a tree-object from ls-tree formatted text.
-
-gitlink:git-pack-objects[1]::
-       Creates a packed archive of objects.
-
-gitlink:git-prune-packed[1]::
-       Remove extra objects that are already in pack files.
-
-gitlink:git-read-tree[1]::
-       Reads tree information into the index.
-
-gitlink:git-repo-config[1]::
-       Get and set options in .git/config.
-
-gitlink:git-unpack-objects[1]::
-       Unpacks objects out of a packed archive.
-
-gitlink:git-update-index[1]::
-       Registers files in the working tree to the index.
-
-gitlink:git-write-tree[1]::
-       Creates a tree from the index.
-
-
-Interrogation commands
-~~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-cat-file[1]::
-       Provide content or type/size information for repository objects.
-
-gitlink:git-describe[1]::
-       Show the most recent tag that is reachable from a commit.
-
-gitlink:git-diff-index[1]::
-       Compares content and mode of blobs between the index and repository.
-
-gitlink:git-diff-files[1]::
-       Compares files in the working tree and the index.
-
-gitlink:git-diff-stages[1]::
-       Compares two "merge stages" in the index.
-
-gitlink:git-diff-tree[1]::
-       Compares the content and mode of blobs found via two tree objects.
-
-gitlink:git-fsck-objects[1]::
-       Verifies the connectivity and validity of the objects in the database.
-
-gitlink:git-ls-files[1]::
-       Information about files in the index and the working tree.
-
-gitlink:git-ls-tree[1]::
-       Displays a tree object in human readable form.
-
-gitlink:git-merge-base[1]::
-       Finds as good common ancestors as possible for a merge.
-
-gitlink:git-name-rev[1]::
-       Find symbolic names for given revs.
-
-gitlink:git-pack-redundant[1]::
-       Find redundant pack files.
-
-gitlink:git-rev-list[1]::
-       Lists commit objects in reverse chronological order.
-
-gitlink:git-show-index[1]::
-       Displays contents of a pack idx file.
-
-gitlink:git-tar-tree[1]::
-       Creates a tar archive of the files in the named tree object.
-
-gitlink:git-unpack-file[1]::
-       Creates a temporary file with a blob's contents.
-
-gitlink:git-var[1]::
-       Displays a git logical variable.
-
-gitlink:git-verify-pack[1]::
-       Validates packed git archive files.
-
-In general, the interrogate commands do not touch the files in
-the working tree.
-
-
-Synching repositories
-~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-fetch-pack[1]::
-       Updates from a remote repository (engine for ssh and
-       local transport).
-
-gitlink:git-http-fetch[1]::
-       Downloads a remote git repository via HTTP by walking
-       commit chain.
-
-gitlink:git-local-fetch[1]::
-       Duplicates another git repository on a local system by
-       walking commit chain.
-
-gitlink:git-peek-remote[1]::
-       Lists references on a remote repository using
-       upload-pack protocol (engine for ssh and local
-       transport).
-
-gitlink:git-receive-pack[1]::
-       Invoked by 'git-send-pack' to receive what is pushed to it.
-
-gitlink:git-send-pack[1]::
-       Pushes to a remote repository, intelligently.
-
-gitlink:git-http-push[1]::
-       Push missing objects using HTTP/DAV.
-
-gitlink:git-shell[1]::
-       Restricted shell for GIT-only SSH access.
-
-gitlink:git-ssh-fetch[1]::
-       Pulls from a remote repository over ssh connection by
-       walking commit chain.
-
-gitlink:git-ssh-upload[1]::
-       Helper "server-side" program used by git-ssh-fetch.
-
-gitlink:git-update-server-info[1]::
-       Updates auxiliary information on a dumb server to help
-       clients discover references and packs on it.
-
-gitlink:git-upload-archive[1]::
-       Invoked by 'git-archive' to send a generated archive.
-
-gitlink:git-upload-pack[1]::
-       Invoked by 'git-fetch-pack' to push
-       what are asked for.
-
-
 High-level commands (porcelain)
 -------------------------------
 
@@ -320,8 +141,11 @@ gitlink:git-merge[1]::
 gitlink:git-mv[1]::
        Move or rename a file, a directory, or a symlink.
 
+gitlink:git-pack-refs[1]::
+       Pack heads and tags for efficient repository access.
+
 gitlink:git-pull[1]::
-       Fetch from and merge with a remote repository.
+       Fetch from and merge with a remote repository or a local branch.
 
 gitlink:git-push[1]::
        Update remote refs along with associated objects.
@@ -491,6 +315,191 @@ gitlink:git-stripspace[1]::
        Filter out empty lines.
 
 
+Low-level commands (plumbing)
+-----------------------------
+
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+development of alternative porcelains.  Developers of such porcelains
+might start by reading about gitlink:git-update-index[1] and
+gitlink:git-read-tree[1].
+
+We divide the low-level commands into commands that manipulate objects (in
+the repository, index, and working tree), commands that interrogate and
+compare objects, and commands that move objects and references between
+repositories.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+gitlink:git-apply[1]::
+       Reads a "diff -up1" or git generated patch file and
+       applies it to the working tree.
+
+gitlink:git-checkout-index[1]::
+       Copy files from the index to the working tree.
+
+gitlink:git-commit-tree[1]::
+       Creates a new commit object.
+
+gitlink:git-hash-object[1]::
+       Computes the object ID from a file.
+
+gitlink:git-index-pack[1]::
+       Build pack idx file for an existing packed archive.
+
+gitlink:git-init-db[1]::
+       Creates an empty git object database, or reinitialize an
+       existing one.
+
+gitlink:git-merge-index[1]::
+       Runs a merge for files needing merging.
+
+gitlink:git-mktag[1]::
+       Creates a tag object.
+
+gitlink:git-mktree[1]::
+       Build a tree-object from ls-tree formatted text.
+
+gitlink:git-pack-objects[1]::
+       Creates a packed archive of objects.
+
+gitlink:git-prune-packed[1]::
+       Remove extra objects that are already in pack files.
+
+gitlink:git-read-tree[1]::
+       Reads tree information into the index.
+
+gitlink:git-repo-config[1]::
+       Get and set options in .git/config.
+
+gitlink:git-unpack-objects[1]::
+       Unpacks objects out of a packed archive.
+
+gitlink:git-update-index[1]::
+       Registers files in the working tree to the index.
+
+gitlink:git-write-tree[1]::
+       Creates a tree from the index.
+
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-cat-file[1]::
+       Provide content or type/size information for repository objects.
+
+gitlink:git-describe[1]::
+       Show the most recent tag that is reachable from a commit.
+
+gitlink:git-diff-index[1]::
+       Compares content and mode of blobs between the index and repository.
+
+gitlink:git-diff-files[1]::
+       Compares files in the working tree and the index.
+
+gitlink:git-diff-stages[1]::
+       Compares two "merge stages" in the index.
+
+gitlink:git-diff-tree[1]::
+       Compares the content and mode of blobs found via two tree objects.
+
+gitlink:git-for-each-ref[1]::
+       Output information on each ref.
+
+gitlink:git-fsck-objects[1]::
+       Verifies the connectivity and validity of the objects in the database.
+
+gitlink:git-ls-files[1]::
+       Information about files in the index and the working tree.
+
+gitlink:git-ls-tree[1]::
+       Displays a tree object in human readable form.
+
+gitlink:git-merge-base[1]::
+       Finds as good common ancestors as possible for a merge.
+
+gitlink:git-name-rev[1]::
+       Find symbolic names for given revs.
+
+gitlink:git-pack-redundant[1]::
+       Find redundant pack files.
+
+gitlink:git-rev-list[1]::
+       Lists commit objects in reverse chronological order.
+
+gitlink:git-show-index[1]::
+       Displays contents of a pack idx file.
+
+gitlink:git-show-ref[1]::
+       List references in a local repository.
+
+gitlink:git-tar-tree[1]::
+       Creates a tar archive of the files in the named tree object.
+
+gitlink:git-unpack-file[1]::
+       Creates a temporary file with a blob's contents.
+
+gitlink:git-var[1]::
+       Displays a git logical variable.
+
+gitlink:git-verify-pack[1]::
+       Validates packed git archive files.
+
+In general, the interrogate commands do not touch the files in
+the working tree.
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-fetch-pack[1]::
+       Updates from a remote repository (engine for ssh and
+       local transport).
+
+gitlink:git-http-fetch[1]::
+       Downloads a remote git repository via HTTP by walking
+       commit chain.
+
+gitlink:git-local-fetch[1]::
+       Duplicates another git repository on a local system by
+       walking commit chain.
+
+gitlink:git-peek-remote[1]::
+       Lists references on a remote repository using
+       upload-pack protocol (engine for ssh and local
+       transport).
+
+gitlink:git-receive-pack[1]::
+       Invoked by 'git-send-pack' to receive what is pushed to it.
+
+gitlink:git-send-pack[1]::
+       Pushes to a remote repository, intelligently.
+
+gitlink:git-http-push[1]::
+       Push missing objects using HTTP/DAV.
+
+gitlink:git-shell[1]::
+       Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
+       Pulls from a remote repository over ssh connection by
+       walking commit chain.
+
+gitlink:git-ssh-upload[1]::
+       Helper "server-side" program used by git-ssh-fetch.
+
+gitlink:git-update-server-info[1]::
+       Updates auxiliary information on a dumb server to help
+       clients discover references and packs on it.
+
+gitlink:git-upload-archive[1]::
+       Invoked by 'git-archive' to send a generated archive.
+
+gitlink:git-upload-pack[1]::
+       Invoked by 'git-fetch-pack' to push
+       what are asked for.
+
+
 Configuration Mechanism
 -----------------------
 
@@ -565,6 +574,9 @@ HEAD::
        a valid head 'name'
        (i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
 
+For a more complete list of ways to spell object names, see
+"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+
 
 File/Directory Structure
 ------------------------
index 26ecba53fbfad4af4da46680330f998bf92f1b3f..670827c323fe276124ba1a84d59386771db868f1 100644 (file)
@@ -51,6 +51,14 @@ lines are used for `git-push` and `git-fetch`/`git-pull`,
 respectively.  Multiple `Push:` and `Pull:` lines may
 be specified for additional branch mappings.
 
+Or, equivalently, in the `$GIT_DIR/config` (note the use
+of `fetch` instead of `Pull:`):
+
+[remote "<remote>"]
+       url = <url>
+       push = <refspec>
+       fetch = <refspec>
+
 The name of a file in `$GIT_DIR/branches` directory can be
 specified as an older notation short-hand; the named
 file should contain a single line, a URL in one of the
index 461fef636d59ebf46848b48636ae770e8684558f..a042df4a2ca7cb6e523151fb22d005ffe3d41b84 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
 
@@ -185,15 +187,12 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-cherry-pick git-status git-instaweb
 
-# The ones that do not have to link with lcrypto, lz nor xdiff.
-SIMPLE_PROGRAMS = \
-       git-daemon$X
-
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
        git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
        git-hash-object$X git-index-pack$X git-local-fetch$X \
        git-merge-base$X \
+       git-daemon$X \
        git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
        git-peek-remote$X git-receive-pack$X \
        git-send-pack$X git-shell$X \
@@ -210,12 +209,12 @@ 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))
 
 # what 'all' will build and 'install' will install, in gitexecdir
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \
+ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) \
        git-merge-recur$X
 
 # Backward compatibility -- to be removed after 1.0
@@ -258,15 +257,17 @@ LIB_OBJS = \
        quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
-       fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
+       revision.o pager.o tree-walk.o xdiff-interface.o \
        write_or_die.o trace.o list-objects.o grep.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
+       builtin-annotate.o \
        builtin-apply.o \
        builtin-archive.o \
+       builtin-branch.o \
        builtin-cat-file.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
@@ -278,6 +279,7 @@ BUILTIN_OBJS = \
        builtin-diff-stages.o \
        builtin-diff-tree.o \
        builtin-fmt-merge-msg.o \
+       builtin-for-each-ref.o \
        builtin-grep.o \
        builtin-init-db.o \
        builtin-log.o \
@@ -307,7 +309,9 @@ BUILTIN_OBJS = \
        builtin-update-ref.o \
        builtin-upload-archive.o \
        builtin-verify-pack.o \
-       builtin-write-tree.o
+       builtin-write-tree.o \
+       builtin-show-ref.o \
+       builtin-pack-refs.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 EXTLIBS = -lz
@@ -480,11 +484,9 @@ ifdef NEEDS_LIBICONV
 endif
 ifdef NEEDS_SOCKET
        EXTLIBS += -lsocket
-       SIMPLE_LIB += -lsocket
 endif
 ifdef NEEDS_NSL
        EXTLIBS += -lnsl
-       SIMPLE_LIB += -lnsl
 endif
 ifdef NO_D_TYPE_IN_DIRENT
        BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
@@ -676,6 +678,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
            -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
            -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
            -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+           -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+           -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
            $< >$@+
        chmod +x $@+
        mv $@+ $@
@@ -729,11 +733,6 @@ endif
 git-%$X: %.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-$(SIMPLE_PROGRAMS) : $(LIB_FILE)
-$(SIMPLE_PROGRAMS) : git-%$X : %.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIB_FILE) $(SIMPLE_LIB)
-
 ssh-pull.o: ssh-fetch.c
 ssh-push.o: ssh-upload.c
 git-local-fetch$X: fetch.o
@@ -934,3 +933,8 @@ check-docs::
                *) echo "no link: $$v";; \
                esac ; \
        done | sort
+
+### Make sure built-ins do not have dups and listed in git.c
+#
+check-builtins::
+       ./check-builtins.sh
index 16dcdb875c3715556ce8561e9ee9f7ae8b3766da..6838dc788f7620b0807a7044b611efc623bdcf0c 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -25,8 +25,6 @@ struct archiver {
        parse_extra_args_fn_t parse_extra;
 };
 
-extern struct archiver archivers[];
-
 extern int parse_archive_args(int argc,
                              const char **argv,
                              struct archiver *ar);
diff --git a/blame.c b/blame.c
index d0e506c02b2e2d0c8fa834ddc86c6b325ea1a8cb..1144c8533724b06534caad7f693bdb65df496682 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 {
@@ -38,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 {
@@ -157,11 +165,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
                                  unsigned mode, int stage);
 
 static unsigned char blob_sha1[20];
-static const charblame_file;
+static const char *blame_file;
 static int get_blob_sha1(struct tree *t, const char *pathname,
                         unsigned char *sha1)
 {
-       int i;
        const char *pathspec[2];
        blame_file = pathname;
        pathspec[0] = pathname;
@@ -169,12 +176,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
        hashclr(blob_sha1);
        read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
 
-       for (i = 0; i < 20; i++) {
-               if (blob_sha1[i] != 0)
-                       break;
-       }
-
-       if (i == 20)
+       if (is_null_sha1(blob_sha1))
                return -1;
 
        hashcpy(sha1, blob_sha1);
@@ -234,6 +236,9 @@ static void print_map(struct commit *cmit, struct commit *other)
            util2->num_lines ? util->num_lines : util2->num_lines;
        int num;
 
+       if (print_map == NULL)
+               ; /* to avoid "unused function" warning */
+
        for (i = 0; i < max; i++) {
                printf("i: %d ", i);
                num = -1;
@@ -241,7 +246,8 @@ static void print_map(struct commit *cmit, struct commit *other)
                if (i < util->num_lines) {
                        num = util->line_map[i];
                        printf("%d\t", num);
-               } else
+               }
+               else
                        printf("\t");
 
                if (i < util2->num_lines) {
@@ -249,7 +255,8 @@ static void print_map(struct commit *cmit, struct commit *other)
                        printf("%d\t", num2);
                        if (num != -1 && num2 != num)
                                printf("---");
-               } else
+               }
+               else
                        printf("\t");
 
                printf("\n");
@@ -268,12 +275,12 @@ static void fill_line_map(struct commit *commit, struct commit *other,
        int cur_chunk = 0;
        int i1, i2;
 
-       if (p->num && DEBUG)
-               print_patch(p);
-
-       if (DEBUG)
+       if (DEBUG) {
+               if (p->num)
+                       print_patch(p);
                printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
                       util2->num_lines);
+       }
 
        for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
                struct chunk *chunk = NULL;
@@ -295,7 +302,8 @@ static void fill_line_map(struct commit *commit, struct commit *other,
                                i2 += chunk->len2;
 
                        cur_chunk++;
-               } else {
+               }
+               else {
                        if (i2 >= util2->num_lines)
                                break;
 
@@ -329,19 +337,15 @@ static int map_line(struct commit *commit, int line)
        return info->line_map[line];
 }
 
-static struct util_infoget_util(struct commit *commit)
+static struct util_info *get_util(struct commit *commit)
 {
        struct util_info *util = commit->util;
 
        if (util)
                return util;
 
-       util = xmalloc(sizeof(struct util_info));
-       util->buf = NULL;
-       util->size = 0;
-       util->line_map = NULL;
+       util = xcalloc(1, sizeof(struct util_info));
        util->num_lines = -1;
-       util->pathname = NULL;
        commit->util = util;
        return util;
 }
@@ -371,7 +375,7 @@ static void alloc_line_map(struct commit *commit)
                if (util->buf[i] == '\n')
                        util->num_lines++;
        }
-       if(util->buf[util->size - 1] != '\n')
+       if (util->buf[util->size - 1] != '\n')
                util->num_lines++;
 
        util->line_map = xmalloc(sizeof(int) * util->num_lines);
@@ -380,9 +384,9 @@ static void alloc_line_map(struct commit *commit)
                util->line_map[i] = -1;
 }
 
-static void init_first_commit(struct commit* commit, const char* filename)
+static void init_first_commit(struct commit *commit, const char *filename)
 {
-       struct util_infoutil = commit->util;
+       struct util_info *util = commit->util;
        int i;
 
        util->pathname = filename;
@@ -397,18 +401,17 @@ static void init_first_commit(struct commit* commit, const char* filename)
                util->line_map[i] = i;
 }
 
-
 static void process_commits(struct rev_info *rev, const char *path,
-                           struct commit** initial)
+                           struct commit **initial)
 {
        int i;
-       struct util_infoutil;
+       struct util_info *util;
        int lines_left;
        int *blame_p;
        int *new_lines;
        int new_lines_len;
 
-       struct commitcommit = get_revision(rev);
+       struct commit *commit = get_revision(rev);
        assert(commit);
        init_first_commit(commit, path);
 
@@ -444,7 +447,7 @@ static void process_commits(struct rev_info *rev, const char *path,
                     parents != NULL; parents = parents->next)
                        num_parents++;
 
-               if(num_parents == 0)
+               if (num_parents == 0)
                        *initial = commit;
 
                if (fill_util_info(commit))
@@ -505,13 +508,12 @@ static void process_commits(struct rev_info *rev, const char *path,
        } while ((commit = get_revision(rev)) != NULL);
 }
 
-
-static int compare_tree_path(struct rev_info* revs,
-                            struct commit* c1, struct commit* c2)
+static int compare_tree_path(struct rev_info *revs,
+                            struct commit *c1, struct commit *c2)
 {
        int ret;
-       const charpaths[2];
-       struct util_infoutil = c2->util;
+       const char *paths[2];
+       struct util_info *util = c2->util;
        paths[0] = util->pathname;
        paths[1] = NULL;
 
@@ -522,12 +524,11 @@ static int compare_tree_path(struct rev_info* revs,
        return ret;
 }
 
-
-static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
-                                  const char* path)
+static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1,
+                                  const char *path)
 {
        int ret;
-       const charpaths[2];
+       const char *paths[2];
        paths[0] = path;
        paths[1] = NULL;
 
@@ -538,9 +539,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
        return ret;
 }
 
-static const char* find_rename(struct commit* commit, struct commit* parent)
+static const char *find_rename(struct commit *commit, struct commit *parent)
 {
-       struct util_infocutil = commit->util;
+       struct util_info *cutil = commit->util;
        struct diff_options diff_opts;
        const char *paths[1];
        int i;
@@ -566,9 +567,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent)
        for (i = 0; i < diff_queued_diff.nr; i++) {
                struct diff_filepair *p = diff_queued_diff.queue[i];
 
-               if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
+               if (p->status == 'R' &&
+                   !strcmp(p->one->path, cutil->pathname)) {
                        if (DEBUG)
-                               printf("rename %s -> %s\n", p->one->path, p->two->path);
+                               printf("rename %s -> %s\n",
+                                      p->one->path, p->two->path);
                        return p->two->path;
                }
        }
@@ -584,7 +587,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
                return;
 
        if (!commit->parents) {
-               struct util_infoutil = commit->util;
+               struct util_info *util = commit->util;
                if (!same_tree_as_empty_path(revs, commit->tree,
                                             util->pathname))
                        commit->object.flags |= TREECHANGE;
@@ -610,17 +613,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
 
                case REV_TREE_NEW:
                {
-
-                       struct util_info* util = commit->util;
+                       struct util_info *util = commit->util;
                        if (revs->remove_empty_trees &&
                            same_tree_as_empty_path(revs, p->tree,
                                                    util->pathname)) {
-                               const charnew_name = find_rename(commit, p);
+                               const char *new_name = find_rename(commit, p);
                                if (new_name) {
-                                       struct util_infoputil = get_util(p);
+                                       struct util_info *putil = get_util(p);
                                        if (!putil->pathname)
                                                putil->pathname = xstrdup(new_name);
-                               } else {
+                               }
+                               else {
                                        *pp = parent->next;
                                        continue;
                                }
@@ -641,47 +644,106 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
        commit->object.flags |= TREECHANGE;
 }
 
-
 struct commit_info
 {
-       charauthor;
-       charauthor_mail;
+       char *author;
+       char *author_mail;
        unsigned long author_time;
-       char* author_tz;
+       char *author_tz;
+
+       /* filled only when asked for details */
+       char *committer;
+       char *committer_mail;
+       unsigned long committer_time;
+       char *committer_tz;
+
+       char *summary;
 };
 
-static void get_commit_info(struct commit* commit, struct commit_info* ret)
+static void get_ac_line(const char *inbuf, const char *what,
+                       int bufsz, char *person, char **mail,
+                       unsigned long *time, char **tz)
 {
        int len;
-       char* tmp;
-       static char author_buf[1024];
-
-       tmp = strstr(commit->buffer, "\nauthor ") + 8;
-       len = strchr(tmp, '\n') - tmp;
-       ret->author = author_buf;
-       memcpy(ret->author, tmp, len);
+       char *tmp, *endp;
+
+       tmp = strstr(inbuf, what);
+       if (!tmp)
+               goto error_out;
+       tmp += strlen(what);
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               len = strlen(tmp);
+       else
+               len = endp - tmp;
+       if (bufsz <= len) {
+       error_out:
+               /* Ugh */
+               person = *mail = *tz = "(unknown)";
+               *time = 0;
+               return;
+       }
+       memcpy(person, tmp, len);
 
-       tmp = ret->author;
+       tmp = person;
        tmp += len;
        *tmp = 0;
-       while(*tmp != ' ')
+       while (*tmp != ' ')
                tmp--;
-       ret->author_tz = tmp+1;
+       *tz = tmp+1;
 
        *tmp = 0;
-       while(*tmp != ' ')
+       while (*tmp != ' ')
                tmp--;
-       ret->author_time = strtoul(tmp, NULL, 10);
+       *time = strtoul(tmp, NULL, 10);
 
        *tmp = 0;
-       while(*tmp != ' ')
+       while (*tmp != ' ')
                tmp--;
-       ret->author_mail = tmp + 1;
-
+       *mail = tmp + 1;
        *tmp = 0;
 }
 
-static const char* format_time(unsigned long time, const char* tz_str,
+static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed)
+{
+       int len;
+       char *tmp, *endp;
+       static char author_buf[1024];
+       static char committer_buf[1024];
+       static char summary_buf[1024];
+
+       ret->author = author_buf;
+       get_ac_line(commit->buffer, "\nauthor ",
+                   sizeof(author_buf), author_buf, &ret->author_mail,
+                   &ret->author_time, &ret->author_tz);
+
+       if (!detailed)
+               return;
+
+       ret->committer = committer_buf;
+       get_ac_line(commit->buffer, "\ncommitter ",
+                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+                   &ret->committer_time, &ret->committer_tz);
+
+       ret->summary = summary_buf;
+       tmp = strstr(commit->buffer, "\n\n");
+       if (!tmp) {
+       error_out:
+               sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+               return;
+       }
+       tmp += 2;
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               goto error_out;
+       len = endp - tmp;
+       if (len >= sizeof(summary_buf))
+               goto error_out;
+       memcpy(summary_buf, tmp, len);
+       summary_buf[len] = 0;
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
                               int show_raw_time)
 {
        static char time_buf[128];
@@ -706,15 +768,15 @@ static const char* format_time(unsigned long time, const char* tz_str,
        return time_buf;
 }
 
-static void topo_setter(struct commit* c, void* data)
+static void topo_setter(struct commit *c, void *data)
 {
-       struct util_infoutil = c->util;
+       struct util_info *util = c->util;
        util->topo_data = data;
 }
 
-static void* topo_getter(struct commit* c)
+static void *topo_getter(struct commit *c)
 {
-       struct util_infoutil = c->util;
+       struct util_info *util = c->util;
        return util->topo_data;
 }
 
@@ -737,6 +799,101 @@ static int read_ancestry(const char *graft_file,
        return 0;
 }
 
+static int lineno_width(int lines)
+{
+       int i, width;
+
+       for (width = 1, i = 10; i <= lines + 1; width++)
+               i *= 10;
+       return width;
+}
+
+static int find_orig_linenum(struct util_info *u, int lineno)
+{
+       int i;
+
+       for (i = 0; i < u->num_lines; i++)
+               if (lineno == u->line_map[i])
+                       return i + 1;
+       return 0;
+}
+
+static void emit_meta(struct commit *c, int lno,
+                     int sha1_len, int compatibility, int porcelain,
+                     int show_name, int show_number, int show_raw_time,
+                     int longest_file, int longest_author,
+                     int max_digits, int max_orig_digits)
+{
+       struct util_info *u;
+       int lineno;
+       struct commit_info ci;
+
+       u = c->util;
+       lineno = find_orig_linenum(u, lno);
+
+       if (porcelain) {
+               int group_size = -1;
+               struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1];
+               if (cc != c) {
+                       /* This is the beginning of this group */
+                       int i;
+                       for (i = lno + 1; i < num_blame_lines; i++)
+                               if (blame_lines[i] != c)
+                                       break;
+                       group_size = i - lno;
+               }
+               if (0 < group_size)
+                       printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1),
+                              lineno, lno + 1, group_size);
+               else
+                       printf("%s %d %d\n", sha1_to_hex(c->object.sha1),
+                              lineno, lno + 1);
+               if (!u->meta_given) {
+                       get_commit_info(c, &ci, 1);
+                       printf("author %s\n", ci.author);
+                       printf("author-mail %s\n", ci.author_mail);
+                       printf("author-time %lu\n", ci.author_time);
+                       printf("author-tz %s\n", ci.author_tz);
+                       printf("committer %s\n", ci.committer);
+                       printf("committer-mail %s\n", ci.committer_mail);
+                       printf("committer-time %lu\n", ci.committer_time);
+                       printf("committer-tz %s\n", ci.committer_tz);
+                       printf("filename ");
+                       if (quote_c_style(u->pathname, NULL, NULL, 0))
+                               quote_c_style(u->pathname, NULL, stdout, 0);
+                       else
+                               fputs(u->pathname, stdout);
+                       printf("\nsummary %s\n", ci.summary);
+
+                       u->meta_given = 1;
+               }
+               putchar('\t');
+               return;
+       }
+
+       get_commit_info(c, &ci, 0);
+       fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+       if (compatibility) {
+               printf("\t(%10s\t%10s\t%d)", ci.author,
+                      format_time(ci.author_time, ci.author_tz,
+                                  show_raw_time),
+                      lno + 1);
+       }
+       else {
+               if (show_name)
+                       printf(" %-*.*s", longest_file, longest_file,
+                              u->pathname);
+               if (show_number)
+                       printf(" %*d", max_orig_digits,
+                              lineno);
+               printf(" (%-*.*s %10s %*d) ",
+                      longest_author, longest_author, ci.author,
+                      format_time(ci.author_time, ci.author_tz,
+                                  show_raw_time),
+                      max_digits, lno + 1);
+       }
+}
+
 int main(int argc, const char **argv)
 {
        int i;
@@ -749,38 +906,43 @@ int main(int argc, const char **argv)
        int compatibility = 0;
        int show_raw_time = 0;
        int options = 1;
-       struct commitstart_commit;
+       struct commit *start_commit;
 
-       const charargs[10];
+       const char *args[10];
        struct rev_info rev;
 
        struct commit_info ci;
        const char *buf;
-       int max_digits;
-       int longest_file, longest_author;
-       int found_rename;
+       int max_digits, max_orig_digits;
+       int longest_file, longest_author, longest_file_lines;
+       int show_name = 0;
+       int show_number = 0;
+       int porcelain = 0;
 
-       const charprefix = setup_git_directory();
+       const char *prefix = setup_git_directory();
        git_config(git_default_config);
 
-       for(i = 1; i < argc; i++) {
-               if(options) {
-                       if(!strcmp(argv[i], "-h") ||
+       for (i = 1; i < argc; i++) {
+               if (options) {
+                       if (!strcmp(argv[i], "-h") ||
                           !strcmp(argv[i], "--help"))
                                usage(blame_usage);
-                       else if(!strcmp(argv[i], "-l") ||
-                               !strcmp(argv[i], "--long")) {
+                       if (!strcmp(argv[i], "-l") ||
+                           !strcmp(argv[i], "--long")) {
                                sha1_len = 40;
                                continue;
-                       } else if(!strcmp(argv[i], "-c") ||
-                                 !strcmp(argv[i], "--compatibility")) {
+                       }
+                       if (!strcmp(argv[i], "-c") ||
+                           !strcmp(argv[i], "--compatibility")) {
                                compatibility = 1;
                                continue;
-                       } else if(!strcmp(argv[i], "-t") ||
-                                 !strcmp(argv[i], "--time")) {
+                       }
+                       if (!strcmp(argv[i], "-t") ||
+                           !strcmp(argv[i], "--time")) {
                                show_raw_time = 1;
                                continue;
-                       } else if(!strcmp(argv[i], "-S")) {
+                       }
+                       if (!strcmp(argv[i], "-S")) {
                                if (i + 1 < argc &&
                                    !read_ancestry(argv[i + 1], &sha1_p)) {
                                        compatibility = 1;
@@ -788,33 +950,51 @@ int main(int argc, const char **argv)
                                        continue;
                                }
                                usage(blame_usage);
-                       } else if(!strcmp(argv[i], "--")) {
+                       }
+                       if (!strcmp(argv[i], "-f") ||
+                           !strcmp(argv[i], "--show-name")) {
+                               show_name = 1;
+                               continue;
+                       }
+                       if (!strcmp(argv[i], "-n") ||
+                           !strcmp(argv[i], "--show-number")) {
+                               show_number = 1;
+                               continue;
+                       }
+                       if (!strcmp(argv[i], "-p") ||
+                           !strcmp(argv[i], "--porcelain")) {
+                               porcelain = 1;
+                               sha1_len = 40;
+                               show_raw_time = 1;
+                               continue;
+                       }
+                       if (!strcmp(argv[i], "--")) {
                                options = 0;
                                continue;
-                       } else if(argv[i][0] == '-')
+                       }
+                       if (argv[i][0] == '-')
                                usage(blame_usage);
-                       else
-                               options = 0;
+                       options = 0;
                }
 
-               if(!options) {
-                       if(!filename)
+               if (!options) {
+                       if (!filename)
                                filename = argv[i];
-                       else if(!commit)
+                       else if (!commit)
                                commit = argv[i];
                        else
                                usage(blame_usage);
                }
        }
 
-       if(!filename)
+       if (!filename)
                usage(blame_usage);
        if (commit && sha1_p)
                usage(blame_usage);
-       else if(!commit)
+       else if (!commit)
                commit = "HEAD";
 
-       if(prefix)
+       if (prefix)
                sprintf(filename_buf, "%s%s", prefix, filename);
        else
                strcpy(filename_buf, filename);
@@ -832,7 +1012,6 @@ int main(int argc, const char **argv)
                return 1;
        }
 
-
        init_revisions(&rev, setup_git_directory());
        rev.remove_empty_trees = 1;
        rev.topo_order = 1;
@@ -850,62 +1029,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 11397f5504f98ccff47a90b228abc71b30327fb9..aad55261fae35fa644bc65aae79fc15aabc38244 100644 (file)
@@ -43,7 +43,7 @@ static int apply_verbosely;
 static int no_add;
 static int show_index_info;
 static int line_termination = '\n';
-static unsigned long p_context = -1;
+static unsigned long p_context = ULONG_MAX;
 static const char apply_usage[] =
 "git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
 
@@ -1043,10 +1043,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                 * then not having oldlines means the patch is creation,
                 * and not having newlines means the patch is deletion.
                 */
-               if (patch->is_new < 0 && !oldlines)
+               if (patch->is_new < 0 && !oldlines) {
                        patch->is_new = 1;
-               if (patch->is_delete < 0 && !newlines)
+                       patch->old_name = NULL;
+               }
+               if (patch->is_delete < 0 && !newlines) {
                        patch->is_delete = 1;
+                       patch->new_name = NULL;
+               }
        }
 
        if (0 < patch->is_new && oldlines)
index 9177379122ea29152e1213cc533f7cd7c569f8d3..2df1a84b85207961a44ce2c81665c5d473e20a9c 100644 (file)
 static const char archive_usage[] = \
 "git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
 
-struct archiver archivers[] = {
-       {
-               .name           = "tar",
-               .write_archive  = write_tar_archive,
-       },
-       {
-               .name           = "zip",
-               .write_archive  = write_zip_archive,
-               .parse_extra    = parse_extra_zip_args,
-       },
+static struct archiver_desc
+{
+       const char *name;
+       write_archive_fn_t write_archive;
+       parse_extra_args_fn_t parse_extra;
+} archivers[] = {
+       { "tar", write_tar_archive, NULL },
+       { "zip", write_zip_archive, parse_extra_zip_args },
 };
 
 static int run_remote_archiver(const char *remote, int argc,
@@ -88,7 +86,10 @@ static int init_archiver(const char *name, struct archiver *ar)
 
        for (i = 0; i < ARRAY_SIZE(archivers); i++) {
                if (!strcmp(name, archivers[i].name)) {
-                       memcpy(ar, &archivers[i], sizeof(struct archiver));
+                       memset(ar, 0, sizeof(*ar));
+                       ar->name = archivers[i].name;
+                       ar->write_archive = archivers[i].write_archive;
+                       ar->parse_extra = archivers[i].parse_extra;
                        rv = 0;
                        break;
                }
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644 (file)
index 0000000..368b68e
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+
+static const char builtin_branch_usage[] =
+"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]";
+
+
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int in_merge_bases(const unsigned char *sha1,
+                         struct commit *rev1,
+                         struct commit *rev2)
+{
+       struct commit_list *bases, *b;
+       int ret = 0;
+
+       bases = get_merge_bases(rev1, rev2, 1);
+       for (b = bases; b; b = b->next) {
+               if (!hashcmp(sha1, b->item->object.sha1)) {
+                       ret = 1;
+                       break;
+               }
+       }
+
+       free_commit_list(bases);
+       return ret;
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+       struct commit *rev, *head_rev;
+       unsigned char sha1[20];
+       char *name;
+       int i;
+
+       head_rev = lookup_commit_reference(head_sha1);
+       for (i = 0; i < argc; i++) {
+               if (!strcmp(head, argv[i]))
+                       die("Cannot delete the branch you are currently on.");
+
+               name = xstrdup(mkpath("refs/heads/%s", argv[i]));
+               if (!resolve_ref(name, sha1, 1, NULL))
+                       die("Branch '%s' not found.", argv[i]);
+
+               rev = lookup_commit_reference(sha1);
+               if (!rev || !head_rev)
+                       die("Couldn't look up commit objects.");
+
+               /* This checks whether the merge bases of branch and
+                * HEAD contains branch -- which means that the HEAD
+                * contains everything in both.
+                */
+
+               if (!force &&
+                   !in_merge_bases(sha1, rev, head_rev)) {
+                       fprintf(stderr,
+                               "The branch '%s' is not a strict subset of your current HEAD.\n"
+                               "If you are sure you want to delete it, run 'git branch -D %s'.\n",
+                               argv[i], argv[i]);
+                       exit(1);
+               }
+
+               if (delete_ref(name, sha1))
+                       printf("Error deleting branch '%s'\n", argv[i]);
+               else
+                       printf("Deleted branch %s.\n", argv[i]);
+
+               free(name);
+       }
+}
+
+static int ref_index, ref_alloc;
+static char **ref_list;
+
+static int append_ref(const char *refname, const unsigned char *sha1, int flags,
+               void *cb_data)
+{
+       if (ref_index >= ref_alloc) {
+               ref_alloc = alloc_nr(ref_alloc);
+               ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *));
+       }
+
+       ref_list[ref_index++] = xstrdup(refname);
+
+       return 0;
+}
+
+static int ref_cmp(const void *r1, const void *r2)
+{
+       return strcmp(*(char **)r1, *(char **)r2);
+}
+
+static void print_ref_list(int remote_only)
+{
+       int i;
+       char c;
+
+       if (remote_only)
+               for_each_remote_ref(append_ref, NULL);
+       else
+               for_each_branch_ref(append_ref, NULL);
+
+       qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
+
+       for (i = 0; i < ref_index; i++) {
+               c = ' ';
+               if (!strcmp(ref_list[i], head))
+                       c = '*';
+
+               printf("%c %s\n", c, ref_list[i]);
+       }
+}
+
+static void create_branch(const char *name, const char *start,
+                         int force, int reflog)
+{
+       struct ref_lock *lock;
+       struct commit *commit;
+       unsigned char sha1[20];
+       char ref[PATH_MAX], msg[PATH_MAX + 20];
+
+       snprintf(ref, sizeof ref, "refs/heads/%s", name);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid branch name.", name);
+
+       if (resolve_ref(ref, sha1, 1, NULL)) {
+               if (!force)
+                       die("A branch named '%s' already exists.", name);
+               else if (!strcmp(head, name))
+                       die("Cannot force update the current branch.");
+       }
+
+       if (get_sha1(start, sha1) ||
+           (commit = lookup_commit_reference(sha1)) == NULL)
+               die("Not a valid branch point: '%s'.", start);
+       hashcpy(sha1, commit->object.sha1);
+
+       lock = lock_any_ref_for_update(ref, NULL);
+       if (!lock)
+               die("Failed to lock ref for update: %s.", strerror(errno));
+
+       if (reflog) {
+               log_all_ref_updates = 1;
+               snprintf(msg, sizeof msg, "branch: Created from %s", start);
+       }
+
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               die("Failed to write ref: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+       int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+       int reflog = 0;
+       int i;
+
+       git_config(git_default_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-d")) {
+                       delete = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-D")) {
+                       delete = 1;
+                       force_delete = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force_create = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-r")) {
+                       remote_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-l")) {
+                       reflog = 1;
+                       continue;
+               }
+               usage(builtin_branch_usage);
+       }
+
+       head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
+       if (!head)
+               die("Failed to resolve HEAD as a valid ref.");
+       if (strncmp(head, "refs/heads/", 11))
+               die("HEAD not found below refs/heads!");
+       head += 11;
+
+       if (delete)
+               delete_branches(argc - i, argv + i, force_delete);
+       else if (i == argc)
+               print_ref_list(remote_only);
+       else if (i == argc - 1)
+               create_branch(argv[i], head, force_create, reflog);
+       else if (i == argc - 2)
+               create_branch(argv[i], argv[i + 1], force_create, reflog);
+       else
+               usage(builtin_branch_usage);
+
+       return 0;
+}
index 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..173bf38
--- /dev/null
@@ -0,0 +1,896 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include <fnmatch.h>
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 3
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+       const char *s;
+       unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+       struct ref_sort *next;
+       int atom; /* index into used_atom array */
+       unsigned reverse : 1;
+};
+
+struct refinfo {
+       char *refname;
+       unsigned char objectname[20];
+       struct atom_value *value;
+};
+
+static struct {
+       const char *name;
+       cmp_type cmp_type;
+} valid_atom[] = {
+       { "refname" },
+       { "objecttype" },
+       { "objectsize", FIELD_ULONG },
+       { "objectname" },
+       { "tree" },
+       { "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */
+       { "numparent", FIELD_ULONG },
+       { "object" },
+       { "type" },
+       { "tag" },
+       { "author" },
+       { "authorname" },
+       { "authoremail" },
+       { "authordate", FIELD_TIME },
+       { "committer" },
+       { "committername" },
+       { "committeremail" },
+       { "committerdate", FIELD_TIME },
+       { "tagger" },
+       { "taggername" },
+       { "taggeremail" },
+       { "taggerdate", FIELD_TIME },
+       { "creator" },
+       { "creatordate", FIELD_TIME },
+       { "subject" },
+       { "body" },
+       { "contents" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects.  refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+       const char *sp;
+       char *n;
+       int i, at;
+
+       sp = atom;
+       if (*sp == '*' && sp < ep)
+               sp++; /* deref */
+       if (ep <= sp)
+               die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+       /* Do we have the atom already used elsewhere? */
+       for (i = 0; i < used_atom_cnt; i++) {
+               int len = strlen(used_atom[i]);
+               if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+                       return i;
+       }
+
+       /* Is the atom a valid one? */
+       for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+               int len = strlen(valid_atom[i].name);
+               if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len))
+                       break;
+       }
+
+       if (ARRAY_SIZE(valid_atom) <= i)
+               die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+       /* Add it in, including the deref prefix */
+       at = used_atom_cnt;
+       used_atom_cnt++;
+       used_atom = xrealloc(used_atom,
+                            (sizeof *used_atom) * used_atom_cnt);
+       used_atom_type = xrealloc(used_atom_type,
+                                 (sizeof(*used_atom_type) * used_atom_cnt));
+       n = xmalloc(ep - atom + 1);
+       memcpy(n, atom, ep - atom);
+       n[ep-atom] = 0;
+       used_atom[at] = n;
+       used_atom_type[at] = valid_atom[i].cmp_type;
+       return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+       while (*cp) {
+               if (*cp == '%') {
+                       /* %( is the start of an atom;
+                        * %% is a quoteed per-cent.
+                        */
+                       if (cp[1] == '(')
+                               return cp;
+                       else if (cp[1] == '%')
+                               cp++; /* skip over two % */
+                       /* otherwise this is a singleton, literal % */
+               }
+               cp++;
+       }
+       return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static void verify_format(const char *format)
+{
+       const char *cp, *sp;
+       for (cp = format; *cp && (sp = find_next(cp)); ) {
+               const char *ep = strchr(sp, ')');
+               if (!ep)
+                       die("malformatted format string %s", sp);
+               /* sp points at "%(" and ep points at the closing ")" */
+               parse_atom(sp + 2, ep);
+               cp = ep + 1;
+       }
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object".  If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+       char type[20];
+       void *buf = read_sha1_file(sha1, type, sz);
+
+       if (buf)
+               *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+       else
+               *obj = NULL;
+       return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "objecttype"))
+                       v->s = type_names[obj->type];
+               else if (!strcmp(name, "objectsize")) {
+                       char *s = xmalloc(40);
+                       sprintf(s, "%lu", sz);
+                       v->ul = sz;
+                       v->s = s;
+               }
+               else if (!strcmp(name, "objectname")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(obj->sha1));
+                       v->s = s;
+               }
+       }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct tag *tag = (struct tag *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tag"))
+                       v->s = tag->tag;
+       }
+}
+
+static int num_parents(struct commit *commit)
+{
+       struct commit_list *parents;
+       int i;
+
+       for (i = 0, parents = commit->parents;
+            parents;
+            parents = parents->next)
+               i++;
+       return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct commit *commit = (struct commit *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tree")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+                       v->s = s;
+               }
+               if (!strcmp(name, "numparent")) {
+                       char *s = xmalloc(40);
+                       sprintf(s, "%lu", v->ul);
+                       v->s = s;
+                       v->ul = num_parents(commit);
+               }
+               else if (!strcmp(name, "parent")) {
+                       int num = num_parents(commit);
+                       int i;
+                       struct commit_list *parents;
+                       char *s = xmalloc(42 * num);
+                       v->s = s;
+                       for (i = 0, parents = commit->parents;
+                            parents;
+                            parents = parents->next, i = i + 42) {
+                               struct commit *parent = parents->item;
+                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
+                               if (parents->next)
+                                       s[i+40] = ' ';
+                       }
+               }
+       }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+       const char *eol;
+       while (*buf) {
+               if (!strncmp(buf, who, wholen) &&
+                   buf[wholen] == ' ')
+                       return buf + wholen + 1;
+               eol = strchr(buf, '\n');
+               if (!eol)
+                       return "";
+               eol++;
+               if (eol[1] == '\n')
+                       return ""; /* end of header */
+               buf = eol;
+       }
+       return "";
+}
+
+static char *copy_line(const char *buf)
+{
+       const char *eol = strchr(buf, '\n');
+       char *line;
+       int len;
+       if (!eol)
+               return "";
+       len = eol - buf;
+       line = xmalloc(len + 1);
+       memcpy(line, buf, len);
+       line[len] = 0;
+       return line;
+}
+
+static char *copy_name(const char *buf)
+{
+       const char *eol = strchr(buf, '\n');
+       const char *eoname = strstr(buf, " <");
+       char *line;
+       int len;
+       if (!(eoname && eol && eoname < eol))
+               return "";
+       len = eoname - buf;
+       line = xmalloc(len + 1);
+       memcpy(line, buf, len);
+       line[len] = 0;
+       return line;
+}
+
+static char *copy_email(const char *buf)
+{
+       const char *email = strchr(buf, '<');
+       const char *eoemail = strchr(email, '>');
+       char *line;
+       int len;
+       if (!email || !eoemail)
+               return "";
+       eoemail++;
+       len = eoemail - email;
+       line = xmalloc(len + 1);
+       memcpy(line, email, len);
+       line[len] = 0;
+       return line;
+}
+
+static void grab_date(const char *buf, struct atom_value *v)
+{
+       const char *eoemail = strstr(buf, "> ");
+       char *zone;
+       unsigned long timestamp;
+       long tz;
+
+       if (!eoemail)
+               goto bad;
+       timestamp = strtoul(eoemail + 2, &zone, 10);
+       if (timestamp == ULONG_MAX)
+               goto bad;
+       tz = strtol(zone, NULL, 10);
+       if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+               goto bad;
+       v->s = xstrdup(show_date(timestamp, tz, 0));
+       v->ul = timestamp;
+       return;
+ bad:
+       v->s = "";
+       v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       int wholen = strlen(who);
+       const char *wholine = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strncmp(who, name, wholen))
+                       continue;
+               if (name[wholen] != 0 &&
+                   strcmp(name + wholen, "name") &&
+                   strcmp(name + wholen, "email") &&
+                   strcmp(name + wholen, "date"))
+                       continue;
+               if (!wholine)
+                       wholine = find_wholine(who, wholen, buf, sz);
+               if (!wholine)
+                       return; /* no point looking for it */
+               if (name[wholen] == 0)
+                       v->s = copy_line(wholine);
+               else if (!strcmp(name + wholen, "name"))
+                       v->s = copy_name(wholine);
+               else if (!strcmp(name + wholen, "email"))
+                       v->s = copy_email(wholine);
+               else if (!strcmp(name + wholen, "date"))
+                       grab_date(wholine, v);
+       }
+
+       /* For a tag or a commit object, if "creator" or "creatordate" is
+        * requested, do something special.
+        */
+       if (strcmp(who, "tagger") && strcmp(who, "committer"))
+               return; /* "author" for commit object is not wanted */
+       if (!wholine)
+               wholine = find_wholine(who, wholen, buf, sz);
+       if (!wholine)
+               return;
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+
+               if (!strcmp(name, "creatordate"))
+                       grab_date(wholine, v);
+               else if (!strcmp(name, "creator"))
+                       v->s = copy_line(wholine);
+       }
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+       while (*buf) {
+               const char *eol = strchr(buf, '\n');
+               if (!eol)
+                       return;
+               if (eol[1] == '\n') {
+                       buf = eol + 1;
+                       break; /* found end of header */
+               }
+               buf = eol + 1;
+       }
+       while (*buf == '\n')
+               buf++;
+       if (!*buf)
+               return;
+       *sub = buf; /* first non-empty line */
+       buf = strchr(buf, '\n');
+       if (!buf)
+               return; /* no body */
+       while (*buf == '\n')
+               buf++; /* skip blank between subject and body */
+       *body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       const char *subpos = NULL, *bodypos = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strcmp(name, "subject") &&
+                   strcmp(name, "body") &&
+                   strcmp(name, "contents"))
+                       continue;
+               if (!subpos)
+                       find_subpos(buf, sz, &subpos, &bodypos);
+               if (!subpos)
+                       return;
+
+               if (!strcmp(name, "subject"))
+                       v->s = copy_line(subpos);
+               else if (!strcmp(name, "body"))
+                       v->s = bodypos;
+               else if (!strcmp(name, "contents"))
+                       v->s = subpos;
+       }
+}
+
+/* We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+       int i;
+       for (i = 0; i < used_atom_cnt; i++) {
+               struct atom_value *v = &val[i];
+               if (v->s == NULL)
+                       v->s = "";
+       }
+}
+
+/*
+ * val is a list of atom_value to hold returned values.  Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       grab_common_values(val, deref, obj, buf, sz);
+       switch (obj->type) {
+       case OBJ_TAG:
+               grab_tag_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("tagger", val, deref, obj, buf, sz);
+               break;
+       case OBJ_COMMIT:
+               grab_commit_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("author", val, deref, obj, buf, sz);
+               grab_person("committer", val, deref, obj, buf, sz);
+               break;
+       case OBJ_TREE:
+               // grab_tree_values(val, deref, obj, buf, sz);
+               break;
+       case OBJ_BLOB:
+               // grab_blob_values(val, deref, obj, buf, sz);
+               break;
+       default:
+               die("Eh?  Object of type %d?", obj->type);
+       }
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+       void *buf;
+       struct object *obj;
+       int eaten, i;
+       unsigned long size;
+       const unsigned char *tagged;
+
+       ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+       buf = get_obj(ref->objectname, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+
+       /* Fill in specials first */
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &ref->value[i];
+               if (!strcmp(name, "refname"))
+                       v->s = ref->refname;
+               else if (!strcmp(name, "*refname")) {
+                       int len = strlen(ref->refname);
+                       char *s = xmalloc(len + 4);
+                       sprintf(s, "%s^{}", ref->refname);
+                       v->s = s;
+               }
+       }
+
+       grab_values(ref->value, 0, obj, buf, size);
+       if (!eaten)
+               free(buf);
+
+       /* If there is no atom that wants to know about tagged
+        * object, we are done.
+        */
+       if (!need_tagged || (obj->type != OBJ_TAG))
+               return;
+
+       /* If it is a tag object, see if we use a value that derefs
+        * the object, and if we do grab the object it refers to.
+        */
+       tagged = ((struct tag *)obj)->tagged->sha1;
+
+       /* NEEDSWORK: This derefs tag only once, which
+        * is good to deal with chains of trust, but
+        * is not consistent with what deref_tag() does
+        * which peels the onion to the core.
+        */
+       buf = get_obj(tagged, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       grab_values(ref->value, 1, obj, buf, size);
+       if (!eaten)
+               free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom.  This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+       if (!ref->value) {
+               populate_value(ref);
+               fill_missing_values(ref->value);
+       }
+       *v = &ref->value[atom];
+}
+
+struct grab_ref_cbdata {
+       struct refinfo **grab_array;
+       const char **grab_pattern;
+       int grab_cnt;
+};
+
+/*
+ * A call-back given to for_each_ref().  It is unfortunate that we
+ * need to use global variables to pass extra information to this
+ * function.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct grab_ref_cbdata *cb = cb_data;
+       struct refinfo *ref;
+       int cnt;
+
+       if (*cb->grab_pattern) {
+               const char **pattern;
+               int namelen = strlen(refname);
+               for (pattern = cb->grab_pattern; *pattern; pattern++) {
+                       const char *p = *pattern;
+                       int plen = strlen(p);
+
+                       if ((plen <= namelen) &&
+                           !strncmp(refname, p, plen) &&
+                           (refname[plen] == '\0' ||
+                            refname[plen] == '/'))
+                               break;
+                       if (!fnmatch(p, refname, FNM_PATHNAME))
+                               break;
+               }
+               if (!*pattern)
+                       return 0;
+       }
+
+       /* We do not open the object yet; sort may only need refname
+        * to do its job and the resulting list may yet to be pruned
+        * by maxcount logic.
+        */
+       ref = xcalloc(1, sizeof(*ref));
+       ref->refname = xstrdup(refname);
+       hashcpy(ref->objectname, sha1);
+
+       cnt = cb->grab_cnt;
+       cb->grab_array = xrealloc(cb->grab_array,
+                                 sizeof(*cb->grab_array) * (cnt + 1));
+       cb->grab_array[cnt++] = ref;
+       cb->grab_cnt = cnt;
+       return 0;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+       struct atom_value *va, *vb;
+       int cmp;
+       cmp_type cmp_type = used_atom_type[s->atom];
+
+       get_value(a, s->atom, &va);
+       get_value(b, s->atom, &vb);
+       switch (cmp_type) {
+       case FIELD_STR:
+               cmp = strcmp(va->s, vb->s);
+               break;
+       default:
+               if (va->ul < vb->ul)
+                       cmp = -1;
+               else if (va->ul == vb->ul)
+                       cmp = 0;
+               else
+                       cmp = 1;
+               break;
+       }
+       return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+       struct refinfo *a = *((struct refinfo **)a_);
+       struct refinfo *b = *((struct refinfo **)b_);
+       struct ref_sort *s;
+
+       for (s = ref_sort; s; s = s->next) {
+               int cmp = cmp_ref_sort(s, a, b);
+               if (cmp)
+                       return cmp;
+       }
+       return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+       ref_sort = sort;
+       qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+       struct atom_value *v;
+       get_value(ref, atom, &v);
+       switch (quote_style) {
+       case QUOTE_NONE:
+               fputs(v->s, stdout);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_print(stdout, v->s);
+               break;
+       case QUOTE_PERL:
+               perl_quote_print(stdout, v->s);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_print(stdout, v->s);
+               break;
+       }
+}
+
+static int hex1(char ch)
+{
+       if ('0' <= ch && ch <= '9')
+               return ch - '0';
+       else if ('a' <= ch && ch <= 'f')
+               return ch - 'a' + 10;
+       else if ('A' <= ch && ch <= 'F')
+               return ch - 'A' + 10;
+       return -1;
+}
+static int hex2(const char *cp)
+{
+       if (cp[0] && cp[1])
+               return (hex1(cp[0]) << 4) | hex1(cp[1]);
+       else
+               return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+       while (*cp && (!ep || cp < ep)) {
+               if (*cp == '%') {
+                       if (cp[1] == '%')
+                               cp++;
+                       else {
+                               int ch = hex2(cp + 1);
+                               if (0 <= ch) {
+                                       putchar(ch);
+                                       cp += 3;
+                                       continue;
+                               }
+                       }
+               }
+               putchar(*cp);
+               cp++;
+       }
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+       const char *cp, *sp, *ep;
+
+       for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+               ep = strchr(sp, ')');
+               if (cp < sp)
+                       emit(cp, sp);
+               print_value(info, parse_atom(sp + 2, ep), quote_style);
+       }
+       if (*cp) {
+               sp = cp + strlen(cp);
+               emit(cp, sp);
+       }
+       putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+       static const char cstr_name[] = "refname";
+
+       struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+       sort->next = NULL;
+       sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+       return sort;
+}
+
+int cmd_for_each_ref(int ac, const char **av, char *prefix)
+{
+       int i, num_refs;
+       const char *format = NULL;
+       struct ref_sort *sort = NULL, **sort_tail = &sort;
+       int maxcount = 0;
+       int quote_style = -1; /* unspecified yet */
+       struct refinfo **refs;
+       struct grab_ref_cbdata cbdata;
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strncmp(arg, "--format=", 9)) {
+                       if (format)
+                               die("more than one --format?");
+                       format = arg + 9;
+                       continue;
+               }
+               if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_SHELL;
+                       continue;
+               }
+               if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_PERL;
+                       continue;
+               }
+               if (!strcmp(arg, "--python") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_PYTHON;
+                       continue;
+               }
+               if (!strncmp(arg, "--count=", 8)) {
+                       if (maxcount)
+                               die("more than one --count?");
+                       maxcount = atoi(arg + 8);
+                       if (maxcount <= 0)
+                               die("The number %s did not parse", arg);
+                       continue;
+               }
+               if (!strncmp(arg, "--sort=", 7)) {
+                       struct ref_sort *s = xcalloc(1, sizeof(*s));
+                       int len;
+
+                       s->next = NULL;
+                       *sort_tail = s;
+                       sort_tail = &s->next;
+
+                       arg += 7;
+                       if (*arg == '-') {
+                               s->reverse = 1;
+                               arg++;
+                       }
+                       len = strlen(arg);
+                       sort->atom = parse_atom(arg, arg+len);
+                       continue;
+               }
+               break;
+       }
+       if (quote_style < 0)
+               quote_style = QUOTE_NONE;
+
+       if (!sort)
+               sort = default_sort();
+       sort_atom_limit = used_atom_cnt;
+       if (!format)
+               format = "%(objectname) %(objecttype)\t%(refname)";
+
+       verify_format(format);
+
+       memset(&cbdata, 0, sizeof(cbdata));
+       cbdata.grab_pattern = av + i;
+       for_each_ref(grab_single_ref, &cbdata);
+       refs = cbdata.grab_array;
+       num_refs = cbdata.grab_cnt;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               if (used_atom[i][0] == '*') {
+                       need_tagged = 1;
+                       break;
+               }
+       }
+
+       sort_refs(sort, refs, num_refs);
+
+       if (!maxcount || num_refs < maxcount)
+               maxcount = num_refs;
+       for (i = 0; i < maxcount; i++)
+               show_ref(refs[i], format, quote_style);
+       return 0;
+}
index 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..69e5dd39ca6f007d016ca562454cdb75ec9362fb 100644 (file)
 #include <sys/time.h>
 #include <signal.h>
 
-static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
+static const char pack_usage[] = "\
+git-pack-objects [{ -q | --progress | --all-progress }] \n\
+       [--local] [--incremental] [--window=N] [--depth=N] \n\
+       [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
+       [--revs [--unpacked | --all]*] [--stdout | base-name] \n\
+       [<ref-list | <object-list]";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -29,6 +34,7 @@ struct object_entry {
        enum object_type type;
        enum object_type in_pack_type;  /* could be delta */
        unsigned long delta_size;       /* delta data size (uncompressed) */
+#define in_pack_header_size delta_size /* only when reusing pack data */
        struct object_entry *delta;     /* delta base object */
        struct packed_git *in_pack;     /* already in pack */
        unsigned int in_pack_offset;
@@ -60,6 +66,8 @@ static int non_empty;
 static int no_reuse_delta;
 static int local;
 static int incremental;
+static int allow_ofs_delta;
+
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects;
 static int nr_objects, nr_alloc, nr_result;
@@ -84,17 +92,25 @@ static int object_ix_hashsz;
  * Pack index for existing packs give us easy access to the offsets into
  * corresponding pack file where each object's data starts, but the entries
  * do not store the size of the compressed representation (uncompressed
- * size is easily available by examining the pack entry header).  We build
- * a hashtable of existing packs (pack_revindex), and keep reverse index
- * here -- pack index file is sorted by object name mapping to offset; this
- * pack_revindex[].revindex array is an ordered list of offsets, so if you
- * know the offset of an object, next offset is where its packed
- * representation ends.
+ * size is easily available by examining the pack entry header).  It is
+ * also rather expensive to find the sha1 for an object given its offset.
+ *
+ * We build a hashtable of existing packs (pack_revindex), and keep reverse
+ * index here -- pack index file is sorted by object name mapping to offset;
+ * this pack_revindex[].revindex array is a list of offset/index_nr pairs
+ * ordered by offset, so if you know the offset of an object, next offset
+ * is where its packed representation ends and the index_nr can be used to
+ * get the object sha1 from the main index.
  */
+struct revindex_entry {
+       unsigned int offset;
+       unsigned int nr;
+};
 struct pack_revindex {
        struct packed_git *p;
-       unsigned long *revindex;
-} *pack_revindex = NULL;
+       struct revindex_entry *revindex;
+};
+static struct  pack_revindex *pack_revindex;
 static int pack_revindex_hashsz;
 
 /*
@@ -141,14 +157,9 @@ static void prepare_pack_ix(void)
 
 static int cmp_offset(const void *a_, const void *b_)
 {
-       unsigned long a = *(unsigned long *) a_;
-       unsigned long b = *(unsigned long *) b_;
-       if (a < b)
-               return -1;
-       else if (a == b)
-               return 0;
-       else
-               return 1;
+       const struct revindex_entry *a = a_;
+       const struct revindex_entry *b = b_;
+       return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
 }
 
 /*
@@ -161,25 +172,27 @@ static void prepare_pack_revindex(struct pack_revindex *rix)
        int i;
        void *index = p->index_base + 256;
 
-       rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
+       rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
        for (i = 0; i < num_ent; i++) {
                unsigned int hl = *((unsigned int *)((char *) index + 24*i));
-               rix->revindex[i] = ntohl(hl);
+               rix->revindex[i].offset = ntohl(hl);
+               rix->revindex[i].nr = i;
        }
        /* This knows the pack format -- the 20-byte trailer
         * follows immediately after the last object data.
         */
-       rix->revindex[num_ent] = p->pack_size - 20;
-       qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset);
+       rix->revindex[num_ent].offset = p->pack_size - 20;
+       rix->revindex[num_ent].nr = -1;
+       qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
 }
 
-static unsigned long find_packed_object_size(struct packed_git *p,
-                                            unsigned long ofs)
+static struct revindex_entry * find_packed_object(struct packed_git *p,
+                                                 unsigned int ofs)
 {
        int num;
        int lo, hi;
        struct pack_revindex *rix;
-       unsigned long *revindex;
+       struct revindex_entry *revindex;
        num = pack_revindex_ix(p);
        if (num < 0)
                die("internal error: pack revindex uninitialized");
@@ -191,10 +204,10 @@ static unsigned long find_packed_object_size(struct packed_git *p,
        hi = num_packed_objects(p) + 1;
        do {
                int mi = (lo + hi) / 2;
-               if (revindex[mi] == ofs) {
-                       return revindex[mi+1] - ofs;
+               if (revindex[mi].offset == ofs) {
+                       return revindex + mi;
                }
-               else if (ofs < revindex[mi])
+               else if (ofs < revindex[mi].offset)
                        hi = mi;
                else
                        lo = mi + 1;
@@ -202,6 +215,20 @@ static unsigned long find_packed_object_size(struct packed_git *p,
        die("internal error: pack revindex corrupt");
 }
 
+static unsigned long find_packed_object_size(struct packed_git *p,
+                                            unsigned long ofs)
+{
+       struct revindex_entry *entry = find_packed_object(p, ofs);
+       return entry[1].offset - ofs;
+}
+
+static unsigned char *find_packed_object_name(struct packed_git *p,
+                                             unsigned long ofs)
+{
+       struct revindex_entry *entry = find_packed_object(p, ofs);
+       return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4;
+}
+
 static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
 {
        unsigned long othersize, delta_size;
@@ -232,7 +259,7 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
        int n = 1;
        unsigned char c;
 
-       if (type < OBJ_COMMIT || type > OBJ_DELTA)
+       if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
                die("bad type %d", type);
 
        c = (type << 4) | (size & 15);
@@ -247,6 +274,10 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
        return n;
 }
 
+/*
+ * we are going to reuse the existing object data as is.  make
+ * sure it is not corrupt.
+ */
 static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect)
 {
        z_stream stream;
@@ -278,32 +309,6 @@ static int check_inflate(unsigned char *data, unsigned long len, unsigned long e
        return st;
 }
 
-/*
- * we are going to reuse the existing pack entry data.  make
- * sure it is not corrupt.
- */
-static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len)
-{
-       enum object_type type;
-       unsigned long size, used;
-
-       if (pack_to_stdout)
-               return 0;
-
-       /* the caller has already called use_packed_git() for us,
-        * so it is safe to access the pack data from mmapped location.
-        * make sure the entry inflates correctly.
-        */
-       used = unpack_object_header_gently(data, len, &type, &size);
-       if (!used)
-               return -1;
-       if (type == OBJ_DELTA)
-               used += 20; /* skip base object name */
-       data += used;
-       len -= used;
-       return check_inflate(data, len, entry->size);
-}
-
 static int revalidate_loose_object(struct object_entry *entry,
                                   unsigned char *map,
                                   unsigned long mapsize)
@@ -334,13 +339,10 @@ static unsigned long write_object(struct sha1file *f,
        enum object_type obj_type;
        int to_reuse = 0;
 
-       if (entry->preferred_base)
-               return 0;
-
        obj_type = entry->type;
        if (! entry->in_pack)
                to_reuse = 0;   /* can't reuse what we don't have */
-       else if (obj_type == OBJ_DELTA)
+       else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
                to_reuse = 1;   /* check_object() decided it for us */
        else if (obj_type != entry->in_pack_type)
                to_reuse = 0;   /* pack has delta which is unusable */
@@ -380,18 +382,35 @@ static unsigned long write_object(struct sha1file *f,
                if (entry->delta) {
                        buf = delta_against(buf, size, entry);
                        size = entry->delta_size;
-                       obj_type = OBJ_DELTA;
+                       obj_type = (allow_ofs_delta && entry->delta->offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
                }
                /*
                 * The object header is a byte of 'type' followed by zero or
-                * more bytes of length.  For deltas, the 20 bytes of delta
-                * sha1 follows that.
+                * more bytes of length.
                 */
                hdrlen = encode_header(obj_type, size, header);
                sha1write(f, header, hdrlen);
 
-               if (entry->delta) {
-                       sha1write(f, entry->delta, 20);
+               if (obj_type == OBJ_OFS_DELTA) {
+                       /*
+                        * Deltas with relative base contain an additional
+                        * encoding of the relative offset for the delta
+                        * base from this object's position in the pack.
+                        */
+                       unsigned long ofs = entry->offset - entry->delta->offset;
+                       unsigned pos = sizeof(header) - 1;
+                       header[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               header[--pos] = 128 | (--ofs & 127);
+                       sha1write(f, header + pos, sizeof(header) - pos);
+                       hdrlen += sizeof(header) - pos;
+               } else if (obj_type == OBJ_REF_DELTA) {
+                       /*
+                        * Deltas with a base reference contain
+                        * an additional 20 bytes for the base sha1.
+                        */
+                       sha1write(f, entry->delta->sha1, 20);
                        hdrlen += 20;
                }
                datalen = sha1write_compressed(f, buf, size);
@@ -399,21 +418,40 @@ static unsigned long write_object(struct sha1file *f,
        }
        else {
                struct packed_git *p = entry->in_pack;
-               use_packed_git(p);
 
-               datalen = find_packed_object_size(p, entry->in_pack_offset);
-               buf = (char *) p->pack_base + entry->in_pack_offset;
+               if (entry->delta) {
+                       obj_type = (allow_ofs_delta && entry->delta->offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
+                       reused_delta++;
+               }
+               hdrlen = encode_header(obj_type, entry->size, header);
+               sha1write(f, header, hdrlen);
+               if (obj_type == OBJ_OFS_DELTA) {
+                       unsigned long ofs = entry->offset - entry->delta->offset;
+                       unsigned pos = sizeof(header) - 1;
+                       header[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               header[--pos] = 128 | (--ofs & 127);
+                       sha1write(f, header + pos, sizeof(header) - pos);
+                       hdrlen += sizeof(header) - pos;
+               } else if (obj_type == OBJ_REF_DELTA) {
+                       sha1write(f, entry->delta->sha1, 20);
+                       hdrlen += 20;
+               }
 
-               if (revalidate_pack_entry(entry, buf, datalen))
+               use_packed_git(p);
+               buf = (char *) p->pack_base
+                       + entry->in_pack_offset
+                       + entry->in_pack_header_size;
+               datalen = find_packed_object_size(p, entry->in_pack_offset)
+                               - entry->in_pack_header_size;
+               if (!pack_to_stdout && check_inflate(buf, datalen, entry->size))
                        die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
                sha1write(f, buf, datalen);
                unuse_packed_git(p);
-               hdrlen = 0; /* not really */
-               if (obj_type == OBJ_DELTA)
-                       reused_delta++;
                reused++;
        }
-       if (obj_type == OBJ_DELTA)
+       if (entry->delta)
                written_delta++;
        written++;
        return hdrlen + datalen;
@@ -423,17 +461,16 @@ static unsigned long write_one(struct sha1file *f,
                               struct object_entry *e,
                               unsigned long offset)
 {
-       if (e->offset)
+       if (e->offset || e->preferred_base)
                /* offset starts from header size and cannot be zero
                 * if it is written already.
                 */
                return offset;
-       e->offset = offset;
-       offset += write_object(f, e);
-       /* if we are deltified, write out its base object. */
+       /* if we are deltified, write out its base object first. */
        if (e->delta)
                offset = write_one(f, e->delta, offset);
-       return offset;
+       e->offset = offset;
+       return offset + write_object(f, e);
 }
 
 static void write_pack_file(void)
@@ -443,15 +480,15 @@ static void write_pack_file(void)
        unsigned long offset;
        struct pack_header hdr;
        unsigned last_percent = 999;
-       int do_progress = 0;
+       int do_progress = progress;
 
-       if (!base_name)
+       if (!base_name) {
                f = sha1fd(1, "<stdout>");
-       else {
+               do_progress >>= 1;
+       }
+       else
                f = sha1create("%s-%s.%s", base_name,
                               sha1_to_hex(object_list_sha1), "pack");
-               do_progress = progress;
-       }
        if (do_progress)
                fprintf(stderr, "Writing %d objects.\n", nr_result);
 
@@ -899,26 +936,64 @@ static void check_object(struct object_entry *entry)
        char type[20];
 
        if (entry->in_pack && !entry->preferred_base) {
-               unsigned char base[20];
-               unsigned long size;
-               struct object_entry *base_entry;
+               struct packed_git *p = entry->in_pack;
+               unsigned long left = p->pack_size - entry->in_pack_offset;
+               unsigned long size, used;
+               unsigned char *buf;
+               struct object_entry *base_entry = NULL;
+
+               use_packed_git(p);
+               buf = p->pack_base;
+               buf += entry->in_pack_offset;
 
                /* We want in_pack_type even if we do not reuse delta.
                 * There is no point not reusing non-delta representations.
                 */
-               check_reuse_pack_delta(entry->in_pack,
-                                      entry->in_pack_offset,
-                                      base, &size,
-                                      &entry->in_pack_type);
+               used = unpack_object_header_gently(buf, left,
+                                                  &entry->in_pack_type, &size);
+               if (!used || left - used <= 20)
+                       die("corrupt pack for %s", sha1_to_hex(entry->sha1));
 
                /* Check if it is delta, and the base is also an object
                 * we are going to pack.  If so we will reuse the existing
                 * delta.
                 */
-               if (!no_reuse_delta &&
-                   entry->in_pack_type == OBJ_DELTA &&
-                   (base_entry = locate_object_entry(base)) &&
-                   (!base_entry->preferred_base)) {
+               if (!no_reuse_delta) {
+                       unsigned char c, *base_name;
+                       unsigned long ofs;
+                       /* there is at least 20 bytes left in the pack */
+                       switch (entry->in_pack_type) {
+                       case OBJ_REF_DELTA:
+                               base_name = buf + used;
+                               used += 20;
+                               break;
+                       case OBJ_OFS_DELTA:
+                               c = buf[used++];
+                               ofs = c & 127;
+                               while (c & 128) {
+                                       ofs += 1;
+                                       if (!ofs || ofs & ~(~0UL >> 7))
+                                               die("delta base offset overflow in pack for %s",
+                                                   sha1_to_hex(entry->sha1));
+                                       c = buf[used++];
+                                       ofs = (ofs << 7) + (c & 127);
+                               }
+                               if (ofs >= entry->in_pack_offset)
+                                       die("delta base offset out of bound for %s",
+                                           sha1_to_hex(entry->sha1));
+                               ofs = entry->in_pack_offset - ofs;
+                               base_name = find_packed_object_name(p, ofs);
+                               break;
+                       default:
+                               base_name = NULL;
+                       }
+                       if (base_name)
+                               base_entry = locate_object_entry(base_name);
+               }
+               unuse_packed_git(p);
+               entry->in_pack_header_size = used;
+
+               if (base_entry) {
 
                        /* Depth value does not matter - find_deltas()
                         * will never consider reused delta as the
@@ -927,9 +1002,9 @@ static void check_object(struct object_entry *entry)
                         */
 
                        /* uncompressed size of the delta data */
-                       entry->size = entry->delta_size = size;
+                       entry->size = size;
                        entry->delta = base_entry;
-                       entry->type = OBJ_DELTA;
+                       entry->type = entry->in_pack_type;
 
                        entry->delta_sibling = base_entry->delta_child;
                        base_entry->delta_child = entry;
@@ -1450,10 +1525,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        local = 1;
                        continue;
                }
-               if (!strcmp("--progress", arg)) {
-                       progress = 1;
-                       continue;
-               }
                if (!strcmp("--incremental", arg)) {
                        incremental = 1;
                        continue;
@@ -1476,6 +1547,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        progress = 1;
                        continue;
                }
+               if (!strcmp("--all-progress", arg)) {
+                       progress = 2;
+                       continue;
+               }
                if (!strcmp("-q", arg)) {
                        progress = 0;
                        continue;
@@ -1484,6 +1559,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        no_reuse_delta = 1;
                        continue;
                }
+               if (!strcmp("--delta-base-offset", arg)) {
+                       allow_ofs_delta = 1;
+                       continue;
+               }
                if (!strcmp("--stdout", arg)) {
                        pack_to_stdout = 1;
                        continue;
@@ -1567,7 +1646,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        else {
                if (nr_result)
                        prepare_pack(window, depth);
-               if (progress && pack_to_stdout) {
+               if (progress == 1 && pack_to_stdout) {
                        /* the other end usually displays progress itself */
                        struct itimerval v = {{0,},};
                        setitimer(ITIMER_REAL, &v, NULL);
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
new file mode 100644 (file)
index 0000000..042d271
--- /dev/null
@@ -0,0 +1,107 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char builtin_pack_refs_usage[] =
+"git-pack-refs [--all] [--prune]";
+
+struct ref_to_prune {
+       struct ref_to_prune *next;
+       unsigned char sha1[20];
+       char name[FLEX_ARRAY];
+};
+
+struct pack_refs_cb_data {
+       int prune;
+       int all;
+       struct ref_to_prune *ref_to_prune;
+       FILE *refs_file;
+};
+
+static int do_not_prune(int flags)
+{
+       /* If it is already packed or if it is a symref,
+        * do not prune it.
+        */
+       return (flags & (REF_ISSYMREF|REF_ISPACKED));
+}
+
+static int handle_one_ref(const char *path, const unsigned char *sha1,
+                         int flags, void *cb_data)
+{
+       struct pack_refs_cb_data *cb = cb_data;
+
+       if (!cb->all && strncmp(path, "refs/tags/", 10))
+               return 0;
+       /* Do not pack the symbolic refs */
+       if (!(flags & REF_ISSYMREF))
+               fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+       if (cb->prune && !do_not_prune(flags)) {
+               int namelen = strlen(path) + 1;
+               struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
+               hashcpy(n->sha1, sha1);
+               strcpy(n->name, path);
+               n->next = cb->ref_to_prune;
+               cb->ref_to_prune = n;
+       }
+       return 0;
+}
+
+/* make sure nobody touched the ref, and unlink */
+static void prune_ref(struct ref_to_prune *r)
+{
+       struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+
+       if (lock) {
+               unlink(git_path("%s", r->name));
+               unlock_ref(lock);
+       }
+}
+
+static void prune_refs(struct ref_to_prune *r)
+{
+       while (r) {
+               prune_ref(r);
+               r = r->next;
+       }
+}
+
+static struct lock_file packed;
+
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+       int fd, i;
+       struct pack_refs_cb_data cbdata;
+
+       memset(&cbdata, 0, sizeof(cbdata));
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--prune")) {
+                       cbdata.prune = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--all")) {
+                       cbdata.all = 1;
+                       continue;
+               }
+               /* perhaps other parameters later... */
+               break;
+       }
+       if (i != argc)
+               usage(builtin_pack_refs_usage);
+
+       fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
+       cbdata.refs_file = fdopen(fd, "w");
+       if (!cbdata.refs_file)
+               die("unable to create ref-pack file structure (%s)",
+                   strerror(errno));
+       for_each_ref(handle_one_ref, &cbdata);
+       fflush(cbdata.refs_file);
+       fsync(fd);
+       fclose(cbdata.refs_file);
+       if (commit_lock_file(&packed) < 0)
+               die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+       if (cbdata.prune)
+               prune_refs(cbdata.ref_to_prune);
+       return 0;
+}
index 960db49859a286bf5f3515ee7c2bfe25fe44d09e..24e3b0a8c21b43e0e82e5e505353213ca79f01b0 100644 (file)
@@ -4,9 +4,7 @@
 static const char prune_packed_usage[] =
 "git-prune-packed [-n]";
 
-static int dryrun;
-
-static void prune_dir(int i, DIR *dir, char *pathname, int len)
+static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun)
 {
        struct dirent *de;
        char hex[40];
@@ -31,7 +29,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len)
        rmdir(pathname);
 }
 
-static void prune_packed_objects(void)
+void prune_packed_objects(int dryrun)
 {
        int i;
        static char pathname[PATH_MAX];
@@ -50,7 +48,7 @@ static void prune_packed_objects(void)
                d = opendir(pathname);
                if (!d)
                        continue;
-               prune_dir(i, d, pathname, len + 3);
+               prune_dir(i, d, pathname, len + 3, dryrun);
                closedir(d);
        }
 }
@@ -58,6 +56,7 @@ static void prune_packed_objects(void)
 int cmd_prune_packed(int argc, const char **argv, const char *prefix)
 {
        int i;
+       int dryrun = 0;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -73,6 +72,6 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix)
                usage(prune_packed_usage);
        }
        sync();
-       prune_packed_objects();
+       prune_packed_objects(dryrun);
        return 0;
 }
index 6228c7907b183fb686c9f4cc54347c3dc16f3ec4..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();
@@ -255,5 +255,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 
        prune_object_dir(get_object_directory());
 
+       sync();
+       prune_packed_objects(show_only);
        return 0;
 }
index f5150ed82d269dd825a45cc9c369c71e777bac05..d23974e708cb744f75efbb9f1acbf37606e92b05 100644 (file)
@@ -10,7 +10,7 @@
 
 static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]";
 
-static int all, tags, force, thin = 1;
+static int all, tags, force, thin = 1, verbose;
 static const char *execute;
 
 #define BUF_SIZE (2084)
@@ -27,7 +27,7 @@ static void add_refspec(const char *ref)
        refspec_nr = nr;
 }
 
-static int expand_one_ref(const char *ref, const unsigned char *sha1)
+static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
 {
        /* Ignore the "refs/" at the beginning of the refname */
        ref += 5;
@@ -51,7 +51,7 @@ static void expand_refspecs(void)
        }
        if (!tags)
                return;
-       for_each_ref(expand_one_ref);
+       for_each_ref(expand_one_ref, NULL);
 }
 
 static void set_refspecs(const char **refs, int nr)
@@ -248,6 +248,8 @@ static int do_push(const char *repo)
                while (dest_refspec_nr--)
                        argv[dest_argc++] = *dest_refspec++;
                argv[dest_argc] = NULL;
+               if (verbose)
+                       fprintf(stderr, "Pushing to %s\n", dest);
                err = run_command_v(argc, argv);
                if (!err)
                        continue;
@@ -281,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        i++;
                        break;
                }
+               if (!strcmp(arg, "-v")) {
+                       verbose=1;
+                       continue;
+               }
+               if (!strncmp(arg, "--repo=", 7)) {
+                       repo = arg+7;
+                       continue;
+               }
                if (!strcmp(arg, "--all")) {
                        all = 1;
                        continue;
index f60cee1dc54497d8ba00069a56e14b431f6a0852..7b6e5725ae6d33350a9648e57139b646ab49696e 100644 (file)
@@ -3,7 +3,7 @@
 #include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char *key;
 static regex_t *key_regexp;
@@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
                        type = T_BOOL;
                else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
                        return git_config(show_all_config);
-               else
+               else if (!strcmp(argv[1], "--global")) {
+                       char *home = getenv("HOME");
+                       if (home) {
+                               char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+                               setenv("GIT_CONFIG", user_config, 1);
+                               free(user_config);
+                       } else {
+                               die("$HOME not set");
+                       }
+               } else
                        break;
                argc--;
                argv++;
index 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..e6d75748444ef4fc263970d4fe87b4623d2790fc 100644 (file)
@@ -15,14 +15,14 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-fil
 
 /* We always read in 4kB chunks. */
 static unsigned char buffer[4096];
-static unsigned long offset, len;
+static unsigned long offset, len, consumed_bytes;
 static SHA_CTX ctx;
 
 /*
  * Make sure at least "min" bytes are available in the buffer, and
  * return the pointer to the buffer.
  */
-static void * fill(int min)
+static void *fill(int min)
 {
        if (min <= len)
                return buffer + offset;
@@ -30,7 +30,7 @@ static void * fill(int min)
                die("cannot fill %d bytes", min);
        if (offset) {
                SHA1_Update(&ctx, buffer, offset);
-               memcpy(buffer, buffer + offset, len);
+               memmove(buffer, buffer + offset, len);
                offset = 0;
        }
        do {
@@ -51,6 +51,7 @@ static void use(int bytes)
                die("used more bytes than were available");
        len -= bytes;
        offset += bytes;
+       consumed_bytes += bytes;
 }
 
 static void *get_data(unsigned long size)
@@ -89,35 +90,49 @@ static void *get_data(unsigned long size)
 
 struct delta_info {
        unsigned char base_sha1[20];
+       unsigned long base_offset;
        unsigned long size;
        void *delta;
+       unsigned nr;
        struct delta_info *next;
 };
 
 static struct delta_info *delta_list;
 
-static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+                             unsigned long base_offset,
+                             void *delta, unsigned long size)
 {
        struct delta_info *info = xmalloc(sizeof(*info));
 
        hashcpy(info->base_sha1, base_sha1);
+       info->base_offset = base_offset;
        info->size = size;
        info->delta = delta;
+       info->nr = nr;
        info->next = delta_list;
        delta_list = info;
 }
 
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+struct obj_info {
+       unsigned long offset;
+       unsigned char sha1[20];
+};
+
+static struct obj_info *obj_list;
+
+static void added_object(unsigned nr, const char *type, void *data,
+                        unsigned long size);
 
-static void write_object(void *buf, unsigned long size, const char *type)
+static void write_object(unsigned nr, void *buf, unsigned long size,
+                        const char *type)
 {
-       unsigned char sha1[20];
-       if (write_sha1_file(buf, size, type, sha1) < 0)
+       if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0)
                die("failed to write object");
-       added_object(sha1, type, buf, size);
+       added_object(nr, type, buf, size);
 }
 
-static void resolve_delta(const char *type,
+static void resolve_delta(unsigned nr, const char *type,
                          void *base, unsigned long base_size,
                          void *delta, unsigned long delta_size)
 {
@@ -130,20 +145,23 @@ static void resolve_delta(const char *type,
        if (!result)
                die("failed to apply delta");
        free(delta);
-       write_object(result, result_size, type);
+       write_object(nr, result, result_size, type);
        free(result);
 }
 
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+static void added_object(unsigned nr, const char *type, void *data,
+                        unsigned long size)
 {
        struct delta_info **p = &delta_list;
        struct delta_info *info;
 
        while ((info = *p) != NULL) {
-               if (!hashcmp(info->base_sha1, sha1)) {
+               if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+                   info->base_offset == obj_list[nr].offset) {
                        *p = info->next;
                        p = &delta_list;
-                       resolve_delta(type, data, size, info->delta, info->size);
+                       resolve_delta(info->nr, type, data, size,
+                                     info->delta, info->size);
                        free(info);
                        continue;
                }
@@ -151,7 +169,8 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
        }
 }
 
-static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
+static void unpack_non_delta_entry(enum object_type kind, unsigned long size,
+                                  unsigned nr)
 {
        void *buf = get_data(size);
        const char *type;
@@ -164,30 +183,80 @@ static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
        default: die("bad type %d", kind);
        }
        if (!dry_run && buf)
-               write_object(buf, size, type);
+               write_object(nr, buf, size, type);
        free(buf);
 }
 
-static void unpack_delta_entry(unsigned long delta_size)
+static void unpack_delta_entry(enum object_type kind, unsigned long delta_size,
+                              unsigned nr)
 {
        void *delta_data, *base;
        unsigned long base_size;
        char type[20];
        unsigned char base_sha1[20];
 
-       hashcpy(base_sha1, fill(20));
-       use(20);
+       if (kind == OBJ_REF_DELTA) {
+               hashcpy(base_sha1, fill(20));
+               use(20);
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               if (!has_sha1_file(base_sha1)) {
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+                       return;
+               }
+       } else {
+               unsigned base_found = 0;
+               unsigned char *pack, c;
+               unsigned long base_offset;
+               unsigned lo, mid, hi;
 
-       delta_data = get_data(delta_size);
-       if (dry_run || !delta_data) {
-               free(delta_data);
-               return;
-       }
+               pack = fill(1);
+               c = *pack;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                               die("offset value overflow for delta base object");
+                       pack = fill(1);
+                       c = *pack;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               base_offset = obj_list[nr].offset - base_offset;
 
-       if (!has_sha1_file(base_sha1)) {
-               add_delta_to_list(base_sha1, delta_data, delta_size);
-               return;
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               lo = 0;
+               hi = nr;
+               while (lo < hi) {
+                       mid = (lo + hi)/2;
+                       if (base_offset < obj_list[mid].offset) {
+                               hi = mid;
+                       } else if (base_offset > obj_list[mid].offset) {
+                               lo = mid + 1;
+                       } else {
+                               hashcpy(base_sha1, obj_list[mid].sha1);
+                               base_found = !is_null_sha1(base_sha1);
+                               break;
+                       }
+               }
+               if (!base_found) {
+                       /* The delta base object is itself a delta that
+                          has not been resolved yet. */
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+                       return;
+               }
        }
+
        base = read_sha1_file(base_sha1, type, &base_size);
        if (!base) {
                error("failed to read delta-pack base object %s",
@@ -197,7 +266,7 @@ static void unpack_delta_entry(unsigned long delta_size)
                has_errors = 1;
                return;
        }
-       resolve_delta(type, base, base_size, delta_data, delta_size);
+       resolve_delta(nr, type, base, base_size, delta_data, delta_size);
        free(base);
 }
 
@@ -208,6 +277,8 @@ static void unpack_one(unsigned nr, unsigned total)
        unsigned long size;
        enum object_type type;
 
+       obj_list[nr].offset = consumed_bytes;
+
        pack = fill(1);
        c = *pack;
        use(1);
@@ -216,7 +287,7 @@ static void unpack_one(unsigned nr, unsigned total)
        shift = 4;
        while (c & 0x80) {
                pack = fill(1);
-               c = *pack++;
+               c = *pack;
                use(1);
                size += (c & 0x7f) << shift;
                shift += 7;
@@ -225,13 +296,14 @@ static void unpack_one(unsigned nr, unsigned total)
                static unsigned long last_sec;
                static unsigned last_percent;
                struct timeval now;
-               unsigned percentage = (nr * 100) / total;
+               unsigned percentage = ((nr+1) * 100) / total;
 
                gettimeofday(&now, NULL);
                if (percentage != last_percent || now.tv_sec != last_sec) {
                        last_sec = now.tv_sec;
                        last_percent = percentage;
-                       fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+                       fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                       percentage, (nr+1), total);
                }
        }
        switch (type) {
@@ -239,10 +311,11 @@ static void unpack_one(unsigned nr, unsigned total)
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
-               unpack_non_delta_entry(type, size);
+               unpack_non_delta_entry(type, size, nr);
                return;
-       case OBJ_DELTA:
-               unpack_delta_entry(size);
+       case OBJ_REF_DELTA:
+       case OBJ_OFS_DELTA:
+               unpack_delta_entry(type, size, nr);
                return;
        default:
                error("bad object type %d", type);
@@ -265,9 +338,10 @@ static void unpack_all(void)
                die("unknown pack file version %d", ntohl(hdr->hdr_version));
        fprintf(stderr, "Unpacking %d objects\n", nr_objects);
 
+       obj_list = xmalloc(nr_objects * sizeof(*obj_list));
        use(sizeof(struct pack_header));
        for (i = 0; i < nr_objects; i++)
-               unpack_one(i+1, nr_objects);
+               unpack_one(i, nr_objects);
        if (delta_list)
                die("unresolved deltas left after unpacking");
 }
@@ -297,6 +371,21 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
                                recover = 1;
                                continue;
                        }
+                       if (!strncmp(arg, "--pack_header=", 14)) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               len = sizeof(*hdr);
+                               continue;
+                       }
                        usage(unpack_usage);
                }
 
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 7451ce64eb8ea50ccfa7d5bb07db39efafc79738..24803dfea778c42d748aeba1df74571ad4a66493 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -11,13 +11,17 @@ extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const cha
 extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip);
 extern void stripspace(FILE *in, FILE *out);
 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);
@@ -26,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 +54,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 +68,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
index d388848dd25db917e9bac5a8fd95cd89d214f5d8..a80326289dbea2c08acce577cf8eef26a0cfba68 100644 (file)
@@ -2,7 +2,9 @@
 #include "tree.h"
 #include "cache-tree.h"
 
+#ifndef DEBUG
 #define DEBUG 0
+#endif
 
 struct cache_tree *cache_tree(void)
 {
diff --git a/cache.h b/cache.h
index c35470107dde85b2179333b354aff0be1eed6df2..f2ec5c8c1416e3167d49813965212d414e6a5a7a 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);
@@ -375,6 +376,7 @@ extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
                                                char *idx_path);
 
 extern void prepare_packed_git(void);
+extern void reprepare_packed_git(void);
 extern void install_packed_git(struct packed_git *pack);
 
 extern struct packed_git *find_sha1_pack(const unsigned char *sha1, 
@@ -414,10 +416,6 @@ extern int copy_fd(int ifd, int ofd);
 extern void write_or_die(int fd, const void *buf, size_t count);
 extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
 
-/* Finish off pack transfer receiving end */
-extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int);
-extern int receive_keep_pack(int fd[2], const char *me, int quiet, int);
-
 /* pager.c */
 extern void setup_pager(void);
 extern int pager_in_use;
diff --git a/check-builtins.sh b/check-builtins.sh
new file mode 100755 (executable)
index 0000000..d6fe6cf
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+{
+       cat <<\EOF
+sayIt:
+       $(foreach b,$(BUILT_INS),echo XXX $b YYY;)
+EOF
+       cat Makefile
+} |
+make -f - sayIt 2>/dev/null |
+sed -n -e 's/.*XXX \(.*\) YYY.*/\1/p' |
+sort |
+{
+    bad=0
+    while read builtin
+    do
+       base=`expr "$builtin" : 'git-\(.*\)'`
+       x=`sed -ne 's/.*{ "'$base'", \(cmd_[^, ]*\).*/'$base'   \1/p' git.c`
+       if test -z "$x"
+       then
+               echo "$base is builtin but not listed in git.c command list"
+               bad=1
+       fi
+       for sfx in sh perl py
+       do
+               if test -f "$builtin.$sfx"
+               then
+                       echo "$base is builtin but $builtin.$sfx still exists"
+                       bad=1
+               fi
+       done
+    done
+    exit $bad
+}
index 65c786807b3cca8408d5119d03a8e4e268bd3e76..29d0c9cf953fdc13bd44bef451d242c762e1c611 100644 (file)
@@ -489,6 +489,16 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long
        printf(" -%lu,%lu", l0, l1-l0);
 }
 
+static int hunk_comment_line(const char *bol)
+{
+       int ch;
+
+       if (!bol)
+               return 0;
+       ch = *bol & 0xff;
+       return (isalpha(ch) || ch == '_' || ch == '$');
+}
+
 static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                       int use_color)
 {
@@ -508,8 +518,13 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                struct sline *sl = &sline[lno];
                unsigned long hunk_end;
                unsigned long rlines;
-               while (lno <= cnt && !(sline[lno].flag & mark))
+               const char *hunk_comment = NULL;
+
+               while (lno <= cnt && !(sline[lno].flag & mark)) {
+                       if (hunk_comment_line(sline[lno].bol))
+                               hunk_comment = sline[lno].bol;
                        lno++;
+               }
                if (cnt < lno)
                        break;
                else {
@@ -526,6 +541,22 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                        show_parent_lno(sline, lno, hunk_end, i);
                printf(" +%lu,%lu ", lno+1, rlines);
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+
+               if (hunk_comment) {
+                       int comment_end = 0;
+                       for (i = 0; i < 40; i++) {
+                               int ch = hunk_comment[i] & 0xff;
+                               if (!ch || ch == '\n')
+                                       break;
+                               if (!isspace(ch))
+                                   comment_end = i;
+                       }
+                       if (comment_end)
+                               putchar(' ');
+                       for (i = 0; i < comment_end; i++)
+                               putchar(hunk_comment[i]);
+               }
+
                printf("%s\n", c_reset);
                while (lno < hunk_end) {
                        struct lline *ll;
@@ -707,8 +738,10 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                int use_color = opt->color_diff;
                const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
                const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+               int added = 0;
+               int deleted = 0;
 
-               if (rev->loginfo)
+               if (rev->loginfo && !rev->no_commit_id)
                        show_log(rev, opt->msg_sep);
                dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
                                 elem->path, c_meta, c_reset);
@@ -722,7 +755,10 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                printf("..%s%s\n", abb, c_reset);
 
                if (mode_differs) {
-                       int added = !!elem->mode;
+                       deleted = !elem->mode;
+
+                       /* We say it was added if nobody had it */
+                       added = !deleted;
                        for (i = 0; added && i < num_parent; i++)
                                if (elem->parent[i].status !=
                                    DIFF_STATUS_ADDED)
@@ -731,7 +767,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                                printf("%snew file mode %06o",
                                       c_meta, elem->mode);
                        else {
-                               if (!elem->mode)
+                               if (deleted)
                                        printf("%sdeleted file ", c_meta);
                                printf("mode ");
                                for (i = 0; i < num_parent; i++) {
@@ -743,8 +779,14 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        }
                        printf("%s\n", c_reset);
                }
-               dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
-               dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
+               if (added)
+                       dump_quoted_path("--- /dev/", "null", c_meta, c_reset);
+               else
+                       dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
+               if (deleted)
+                       dump_quoted_path("+++ /dev/", "null", c_meta, c_reset);
+               else
+                       dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
                dump_sline(sline, cnt, num_parent, opt->color_diff);
        }
        free(result);
@@ -777,7 +819,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
        if (!line_termination)
                inter_name_termination = 0;
 
-       if (rev->loginfo)
+       if (rev->loginfo && !rev->no_commit_id)
                show_log(rev, opt->msg_sep);
 
        if (opt->output_format & DIFF_FORMAT_RAW) {
@@ -849,7 +891,7 @@ void diff_tree_combined(const unsigned char *sha1,
        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diffopts.recursive = 1;
 
-       show_log_first = !!rev->loginfo;
+       show_log_first = !!rev->loginfo && !rev->no_commit_id;
        needsep = 0;
        /* find set of paths that everybody touches */
        for (i = 0; i < num_parent; i++) {
index e8f0caf7cf674ff44b23e0182420988e9672cc69..3cae3901aa9b50e8d04a8ce2633a1a1ef8ed0f6c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -103,6 +103,11 @@ static char *parse_value(void)
        }
 }
 
+static inline int iskeychar(int c)
+{
+       return isalnum(c) || c == '-';
+}
+
 static int get_value(config_fn_t fn, char *name, unsigned int len)
 {
        int c;
@@ -113,7 +118,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
                c = get_next_char();
                if (c == EOF)
                        break;
-               if (!isalnum(c))
+               if (!iskeychar(c))
                        break;
                name[len++] = tolower(c);
                if (len >= MAXNAME)
@@ -181,7 +186,7 @@ static int get_base_var(char *name)
                        return baselen;
                if (isspace(c))
                        return get_extended_base_var(name, baselen, c);
-               if (!isalnum(c) && c != '.')
+               if (!iskeychar(c) && c != '.')
                        return -1;
                if (baselen > MAXNAME / 2)
                        return -1;
@@ -573,7 +578,7 @@ int git_config_set_multivar(const char* key, const char* value,
                        dot = 1;
                /* Leave the extended basename untouched.. */
                if (!dot || i > store.baselen) {
-                       if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+                       if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
                                fprintf(stderr, "invalid key: %s\n", key);
                                free(store.key);
                                ret = 1;
index d9cb17d0b2f21b6cabd78c245f4deaf23cc6b6a0..a43a177160b3b2952e4025bebc47c820ff4003f7 100755 (executable)
 #        source ~/.git-completion.sh
 #
 
+__gitdir ()
+{
+       echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}"
+}
+
 __git_refs ()
 {
-       local cmd i is_hash=y
-       if [ -d "$1" ]; then
+       local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+       if [ -d "$dir" ]; then
                cmd=git-peek-remote
        else
                cmd=git-ls-remote
        fi
-       for i in $($cmd "$1" 2>/dev/null); do
+       for i in $($cmd "$dir" 2>/dev/null); do
                case "$is_hash,$i" in
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
@@ -40,13 +45,13 @@ __git_refs ()
 
 __git_refs2 ()
 {
-       local cmd i is_hash=y
-       if [ -d "$1" ]; then
+       local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+       if [ -d "$dir" ]; then
                cmd=git-peek-remote
        else
                cmd=git-ls-remote
        fi
-       for i in $($cmd "$1" 2>/dev/null); do
+       for i in $($cmd "$dir" 2>/dev/null); do
                case "$is_hash,$i" in
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
@@ -59,25 +64,34 @@ __git_refs2 ()
 
 __git_remotes ()
 {
-       local i REVERTGLOB=$(shopt -p nullglob)
+       local i ngoff IFS=$'\n' d="$(__gitdir)"
+       shopt -q nullglob || ngoff=1
        shopt -s nullglob
-       for i in .git/remotes/*; do
-               echo ${i#.git/remotes/}
+       for i in "$d/remotes"/*; do
+               echo ${i#$d/remotes/}
+       done
+       [ "$ngoff" ] && shopt -u nullglob
+       for i in $(git --git-dir="$d" repo-config --list); do
+               case "$i" in
+               remote.*.url=*)
+                       i="${i#remote.}"
+                       echo "${i/.url=*/}"
+                       ;;
+               esac
        done
-       $REVERTGLOB
 }
 
 __git_complete_file ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        ?*:*)
-               local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')"
-               cur="$(echo "$cur" | sed 's,^.*:,,')"
+               ref="${cur%%:*}"
+               cur="${cur#*:}"
                case "$cur" in
                ?*/*)
-                       pfx="$(echo "$cur" | sed 's,/[^/]*$,,')"
-                       cur="$(echo "$cur" | sed 's,^.*/,,')"
+                       pfx="${cur%/*}"
+                       cur="${cur##*/}"
                        ls="$ref:$pfx"
                        pfx="$pfx/"
                        ;;
@@ -86,7 +100,7 @@ __git_complete_file ()
                        ;;
            esac
                COMPREPLY=($(compgen -P "$pfx" \
-                       -W "$(git-ls-tree "$ls" \
+                       -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
                                | sed '/^100... blob /s,^.*     ,,
                                       /^040000 tree /{
                                           s,^.*        ,,
@@ -96,15 +110,40 @@ __git_complete_file ()
                        -- "$cur"))
                ;;
        *)
-               COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
                ;;
        esac
 }
 
+__git_aliases ()
+{
+       local i IFS=$'\n'
+       for i in $(git --git-dir="$(__gitdir)" repo-config --list); do
+               case "$i" in
+               alias.*)
+                       i="${i#alias.}"
+                       echo "${i/=*/}"
+                       ;;
+               esac
+       done
+}
+
+__git_aliased_command ()
+{
+       local word cmdline=$(git --git-dir="$(__gitdir)" \
+               repo-config --get "alias.$1")
+       for word in $cmdline; do
+               if [ "${word##-*}" ]; then
+                       echo $word
+                       return
+               fi
+       done
+}
+
 _git_branch ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur"))
+       COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur"))
 }
 
 _git_cat_file ()
@@ -126,7 +165,7 @@ _git_cat_file ()
 _git_checkout ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur"))
+       COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur"))
 }
 
 _git_diff ()
@@ -137,7 +176,7 @@ _git_diff ()
 _git_diff_tree ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur"))
+       COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur"))
 }
 
 _git_fetch ()
@@ -154,8 +193,8 @@ _git_fetch ()
        *)
                case "$cur" in
                *:*)
-               cur=$(echo "$cur" | sed 's/^.*://')
-                       COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+                       cur="${cur#*:}"
+                       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
                        ;;
                *)
                        local remote
@@ -183,15 +222,20 @@ _git_ls_tree ()
 
 _git_log ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local pfx cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
+       *...*)
+               pfx="${cur%...*}..."
+               cur="${cur#*...}"
+               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+               ;;
        *..*)
-               local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
-               cur=$(echo "$cur" | sed 's/^.*\.\.//')
-               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur"))
+               pfx="${cur%..*}.."
+               cur="${cur#*..}"
+               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
                ;;
        *)
-               COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
                ;;
        esac
 }
@@ -199,7 +243,7 @@ _git_log ()
 _git_merge_base ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
 }
 
 _git_pull ()
@@ -243,54 +287,82 @@ _git_push ()
                        git-push)  remote="${COMP_WORDS[1]}" ;;
                        git)       remote="${COMP_WORDS[2]}" ;;
                        esac
-               cur=$(echo "$cur" | sed 's/^.*://')
+                       cur="${cur#*:}"
                        COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
                        ;;
                *)
-                       COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur"))
+                       COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur"))
                        ;;
                esac
                ;;
        esac
 }
 
+_git_reset ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local opt="--mixed --hard --soft"
+       COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur"))
+}
+
 _git_show ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
 }
 
 _git ()
 {
-       if [ $COMP_CWORD = 1 ]; then
+       local i c=1 command __git_dir
+
+       while [ $c -lt $COMP_CWORD ]; do
+               i="${COMP_WORDS[c]}"
+               case "$i" in
+               --git-dir=*) __git_dir="${i#--git-dir=}" ;;
+               --bare)      __git_dir="." ;;
+               --version|--help|-p|--paginate) ;;
+               *) command="$i"; break ;;
+               esac
+               c=$((++c))
+       done
+
+       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
                COMPREPLY=($(compgen \
-                       -W "--version $(git help -a|egrep '^ ')" \
+                       -W "--git-dir= --version \
+                               $(git help -a|egrep '^ ') \
+                           $(__git_aliases)" \
                        -- "${COMP_WORDS[COMP_CWORD]}"))
-       else
-               case "${COMP_WORDS[1]}" in
-               branch)      _git_branch ;;
-               cat-file)    _git_cat_file ;;
-               checkout)    _git_checkout ;;
-               diff)        _git_diff ;;
-               diff-tree)   _git_diff_tree ;;
-               fetch)       _git_fetch ;;
-               log)         _git_log ;;
-               ls-remote)   _git_ls_remote ;;
-               ls-tree)     _git_ls_tree ;;
-               pull)        _git_pull ;;
-               push)        _git_push ;;
-               show)        _git_show ;;
-               show-branch) _git_log ;;
-               whatchanged) _git_log ;;
-               *)           COMPREPLY=() ;;
-               esac
+               return;
        fi
+
+       local expansion=$(__git_aliased_command "$command")
+       [ "$expansion" ] && command="$expansion"
+
+       case "$command" in
+       branch)      _git_branch ;;
+       cat-file)    _git_cat_file ;;
+       checkout)    _git_checkout ;;
+       diff)        _git_diff ;;
+       diff-tree)   _git_diff_tree ;;
+       fetch)       _git_fetch ;;
+       log)         _git_log ;;
+       ls-remote)   _git_ls_remote ;;
+       ls-tree)     _git_ls_tree ;;
+       merge-base)  _git_merge_base ;;
+       pull)        _git_pull ;;
+       push)        _git_push ;;
+       reset)       _git_reset ;;
+       show)        _git_show ;;
+       show-branch) _git_log ;;
+       whatchanged) _git_log ;;
+       *)           COMPREPLY=() ;;
+       esac
 }
 
 _gitk ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur"))
+       COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur"))
 }
 
 complete -o default -o nospace -F _git git
@@ -307,13 +379,18 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree
 complete -o default            -F _git_merge_base git-merge-base
 complete -o default -o nospace -F _git_pull git-pull
 complete -o default -o nospace -F _git_push git-push
+complete -o default            -F _git_reset git-reset
 complete -o default            -F _git_show git-show
+complete -o default -o nospace -F _git_log git-show-branch
 complete -o default -o nospace -F _git_log git-whatchanged
 
 # The following are necessary only for Cygwin, and only are needed
 # when the user has tab-completed the executable name and consequently
 # included the '.exe' suffix.
 #
+if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
+complete -o default -o nospace -F _git git.exe
+complete -o default            -F _git_branch git-branch.exe
 complete -o default -o nospace -F _git_cat_file git-cat-file.exe
 complete -o default -o nospace -F _git_diff git-diff.exe
 complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
@@ -321,4 +398,6 @@ complete -o default -o nospace -F _git_log git-log.exe
 complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
 complete -o default            -F _git_merge_base git-merge-base.exe
 complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_log git-show-branch.exe
 complete -o default -o nospace -F _git_log git-whatchanged.exe
+fi
index 5354cd67b3dfa05d0018c03f15d0dcaf4e3df4a7..972c402ea0e79405ef7123dcdf748eccf9e6f3f7 100644 (file)
@@ -589,6 +589,7 @@ and returns the process output as a string."
                           (let ((commit (git-commit-tree buffer tree head)))
                             (git-update-ref "HEAD" commit head)
                             (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+                            (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
                             (with-current-buffer buffer (erase-buffer))
                             (git-set-files-state files 'uptodate)
                             (when (file-directory-p ".git/rr-cache")
@@ -670,6 +671,32 @@ and returns the process output as a string."
   (unless git-status (error "Not in git-status buffer."))
   (ewoc-goto-prev git-status n))
 
+(defun git-next-unmerged-file (&optional n)
+  "Move the selection down N unmerged files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((last (ewoc-locate git-status))
+         (node (ewoc-next git-status last)))
+    (while (and node (> n 0))
+      (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+        (setq n (1- n))
+        (setq last node))
+      (setq node (ewoc-next git-status node)))
+    (ewoc-goto-node git-status last)))
+
+(defun git-prev-unmerged-file (&optional n)
+  "Move the selection up N unmerged files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((last (ewoc-locate git-status))
+         (node (ewoc-prev git-status last)))
+    (while (and node (> n 0))
+      (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+        (setq n (1- n))
+        (setq last node))
+      (setq node (ewoc-prev git-status node)))
+    (ewoc-goto-node git-status last)))
+
 (defun git-add-file ()
   "Add marked file(s) to the index cache."
   (interactive)
@@ -862,7 +889,7 @@ and returns the process output as a string."
           'face 'git-header-face)
          (propertize git-log-msg-separator 'face 'git-separator-face)
          "\n")
-        (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+        (cond ((file-readable-p ".git/MERGE_MSG")
                (insert-file-contents ".git/MERGE_MSG"))
               (sign-off
                (insert (format "\n\nSigned-off-by: %s <%s>\n"
@@ -873,7 +900,8 @@ and returns the process output as a string."
               (2 font-lock-function-name-face))
              (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
               (1 font-lock-comment-face)))))
-      (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+      (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."
@@ -884,6 +912,15 @@ and returns the process output as a string."
     (when (eq 'unmerged (git-fileinfo->state info))
       (smerge-mode))))
 
+(defun git-find-file-other-window ()
+  "Visit the current file in its own buffer in another window."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (find-file-other-window (git-fileinfo->name info))
+    (when (eq 'unmerged (git-fileinfo->state info))
+      (smerge-mode))))
+
 (defun git-find-file-imerge ()
   "Visit the current file in interactive merge mode."
   (interactive)
@@ -967,7 +1004,10 @@ and returns the process output as a string."
     (define-key map "m"   'git-mark-file)
     (define-key map "M"   'git-mark-all)
     (define-key map "n"   'git-next-file)
+    (define-key map "N"   'git-next-unmerged-file)
+    (define-key map "o"   'git-find-file-other-window)
     (define-key map "p"   'git-prev-file)
+    (define-key map "P"   'git-prev-unmerged-file)
     (define-key map "q"   'git-status-quit)
     (define-key map "r"   'git-remove-file)
     (define-key map "R"   'git-resolve-file)
index 4189c4ced047a89782cd1de035e76a930a707b38..8b6361922fd6e6a2fcd9acb20fd54f5b645b36f0 100644 (file)
 ;; system.
 ;;
 ;; To install: put this file on the load-path and add GIT to the list
-;; of supported backends in `vc-handled-backends'.
+;; of supported backends in `vc-handled-backends'; the following line,
+;; placed in your ~/.emacs, will accomplish this:
+;;
+;;     (add-to-list 'vc-handled-backends 'GIT)
 ;;
 ;; TODO
 ;;  - changelog generation
 ;;  - 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 ad8492873ea1db63d56c24bec6174c4682105433..e66bb802da9faed6e01229fffbe56e3d7cca60c6 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -450,6 +450,8 @@ void fill_in_extra_table_entries(struct interp *itable)
         * Replace literal host with lowercase-ized hostname.
         */
        hp = interp_table[INTERP_SLOT_HOST].value;
+       if (!hp)
+               return;
        for ( ; *hp; hp++)
                *hp = tolower(*hp);
 
@@ -544,8 +546,10 @@ static int execute(struct sockaddr *addr)
                loginfo("Extended attributes (%d bytes) exist <%.*s>",
                        (int) pktlen - len,
                        (int) pktlen - len, line + len + 1);
-       if (len && line[len-1] == '\n')
+       if (len && line[len-1] == '\n') {
                line[--len] = 0;
+               pktlen--;
+       }
 
        /*
         * Initialize the path interpolation table for this connection.
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.h b/diff.h
index 6fda92a7b5e69cbedfe5b13e9c4e7fba54a7ed7f..101b2b505dcace41754687a427ad0523daaa50a0 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -103,6 +103,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;
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;
diff --git a/fetch-clone.c b/fetch-clone.c
deleted file mode 100644 (file)
index 76b99af..0000000
+++ /dev/null
@@ -1,276 +0,0 @@
-#include "cache.h"
-#include "exec_cmd.h"
-#include "pkt-line.h"
-#include "sideband.h"
-#include <sys/wait.h>
-#include <sys/time.h>
-
-static int finish_pack(const char *pack_tmp_name, const char *me)
-{
-       int pipe_fd[2];
-       pid_t pid;
-       char idx[PATH_MAX];
-       char final[PATH_MAX];
-       char hash[41];
-       unsigned char sha1[20];
-       char *cp;
-       int err = 0;
-
-       if (pipe(pipe_fd) < 0)
-               die("%s: unable to set up pipe", me);
-
-       strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
-       cp = strrchr(idx, '/');
-       memcpy(cp, "/pidx", 5);
-
-       pid = fork();
-       if (pid < 0)
-               die("%s: unable to fork off git-index-pack", me);
-       if (!pid) {
-               close(0);
-               dup2(pipe_fd[1], 1);
-               close(pipe_fd[0]);
-               close(pipe_fd[1]);
-               execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL);
-               error("cannot exec git-index-pack <%s> <%s>",
-                     idx, pack_tmp_name);
-               exit(1);
-       }
-       close(pipe_fd[1]);
-       if (read(pipe_fd[0], hash, 40) != 40) {
-               error("%s: unable to read from git-index-pack", me);
-               err = 1;
-       }
-       close(pipe_fd[0]);
-
-       for (;;) {
-               int status, code;
-
-               if (waitpid(pid, &status, 0) < 0) {
-                       if (errno == EINTR)
-                               continue;
-                       error("waitpid failed (%s)", strerror(errno));
-                       goto error_die;
-               }
-               if (WIFSIGNALED(status)) {
-                       int sig = WTERMSIG(status);
-                       error("git-index-pack died of signal %d", sig);
-                       goto error_die;
-               }
-               if (!WIFEXITED(status)) {
-                       error("git-index-pack died of unnatural causes %d",
-                             status);
-                       goto error_die;
-               }
-               code = WEXITSTATUS(status);
-               if (code) {
-                       error("git-index-pack died with error code %d", code);
-                       goto error_die;
-               }
-               if (err)
-                       goto error_die;
-               break;
-       }
-       hash[40] = 0;
-       if (get_sha1_hex(hash, sha1)) {
-               error("git-index-pack reported nonsense '%s'", hash);
-               goto error_die;
-       }
-       /* Now we have pack in pack_tmp_name[], and
-        * idx in idx[]; rename them to their final names.
-        */
-       snprintf(final, sizeof(final),
-                "%s/pack/pack-%s.pack", get_object_directory(), hash);
-       move_temp_to_file(pack_tmp_name, final);
-       chmod(final, 0444);
-       snprintf(final, sizeof(final),
-                "%s/pack/pack-%s.idx", get_object_directory(), hash);
-       move_temp_to_file(idx, final);
-       chmod(final, 0444);
-       return 0;
-
- error_die:
-       unlink(idx);
-       unlink(pack_tmp_name);
-       exit(1);
-}
-
-static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2])
-{
-       pid_t side_pid;
-
-       if (!sideband) {
-               fd[0] = xd[0];
-               fd[1] = xd[1];
-               return 0;
-       }
-       /* xd[] is talking with upload-pack; subprocess reads from
-        * xd[0], spits out band#2 to stderr, and feeds us band#1
-        * through our fd[0].
-        */
-       if (pipe(fd) < 0)
-               die("%s: unable to set up pipe", me);
-       side_pid = fork();
-       if (side_pid < 0)
-               die("%s: unable to fork off sideband demultiplexer", me);
-       if (!side_pid) {
-               /* subprocess */
-               close(fd[0]);
-               if (xd[0] != xd[1])
-                       close(xd[1]);
-               if (recv_sideband(me, xd[0], fd[1], 2))
-                       exit(1);
-               exit(0);
-       }
-       close(xd[0]);
-       close(fd[1]);
-       fd[1] = xd[1];
-       return side_pid;
-}
-
-int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband)
-{
-       int status;
-       pid_t pid, side_pid;
-       int fd[2];
-
-       side_pid = setup_sideband(sideband, me, fd, xd);
-       pid = fork();
-       if (pid < 0)
-               die("%s: unable to fork off git-unpack-objects", me);
-       if (!pid) {
-               dup2(fd[0], 0);
-               close(fd[0]);
-               close(fd[1]);
-               execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL);
-               die("git-unpack-objects exec failed");
-       }
-       close(fd[0]);
-       close(fd[1]);
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno != EINTR)
-                       die("waiting for git-unpack-objects: %s",
-                           strerror(errno));
-       }
-       if (WIFEXITED(status)) {
-               int code = WEXITSTATUS(status);
-               if (code)
-                       die("git-unpack-objects died with error code %d",
-                           code);
-               return 0;
-       }
-       if (WIFSIGNALED(status)) {
-               int sig = WTERMSIG(status);
-               die("git-unpack-objects died of signal %d", sig);
-       }
-       die("git-unpack-objects died of unnatural causes %d", status);
-}
-
-/*
- * We average out the download speed over this many "events", where
- * an event is a minimum of about half a second. That way, we get
- * a reasonably stable number.
- */
-#define NR_AVERAGE (4)
-
-/*
- * A "binary msec" is a power-of-two-msec, aka 1/1024th of a second.
- * Keeping the time in that format means that "bytes / msecs" means
- * the same as kB/s (modulo rounding).
- *
- * 1000512 is a magic number (usecs in a second, rounded up by half
- * of 1024, to make "rounding" come out right ;)
- */
-#define usec_to_binarymsec(x) ((int)(x) / (1000512 >> 10))
-
-int receive_keep_pack(int xd[2], const char *me, int quiet, int sideband)
-{
-       char tmpfile[PATH_MAX];
-       int ofd, ifd, fd[2];
-       unsigned long total;
-       static struct timeval prev_tv;
-       struct average {
-               unsigned long bytes;
-               unsigned long time;
-       } download[NR_AVERAGE] = { {0, 0}, };
-       unsigned long avg_bytes, avg_time;
-       int idx = 0;
-
-       setup_sideband(sideband, me, fd, xd);
-
-       ifd = fd[0];
-       snprintf(tmpfile, sizeof(tmpfile),
-                "%s/pack/tmp-XXXXXX", get_object_directory());
-       ofd = mkstemp(tmpfile);
-       if (ofd < 0)
-               return error("unable to create temporary file %s", tmpfile);
-
-       gettimeofday(&prev_tv, NULL);
-       total = 0;
-       avg_bytes = 0;
-       avg_time = 0;
-       while (1) {
-               char buf[8192];
-               ssize_t sz, wsz, pos;
-               sz = read(ifd, buf, sizeof(buf));
-               if (sz == 0)
-                       break;
-               if (sz < 0) {
-                       if (errno != EINTR && errno != EAGAIN) {
-                               error("error reading pack (%s)", strerror(errno));
-                               close(ofd);
-                               unlink(tmpfile);
-                               return -1;
-                       }
-                       sz = 0;
-               }
-               pos = 0;
-               while (pos < sz) {
-                       wsz = write(ofd, buf + pos, sz - pos);
-                       if (wsz < 0) {
-                               error("error writing pack (%s)",
-                                     strerror(errno));
-                               close(ofd);
-                               unlink(tmpfile);
-                               return -1;
-                       }
-                       pos += wsz;
-               }
-               total += sz;
-               if (!quiet) {
-                       static unsigned long last;
-                       struct timeval tv;
-                       unsigned long diff = total - last;
-                       /* not really "msecs", but a power-of-two millisec (1/1024th of a sec) */
-                       unsigned long msecs;
-
-                       gettimeofday(&tv, NULL);
-                       msecs = tv.tv_sec - prev_tv.tv_sec;
-                       msecs <<= 10;
-                       msecs += usec_to_binarymsec(tv.tv_usec - prev_tv.tv_usec);
-
-                       if (msecs > 500) {
-                               prev_tv = tv;
-                               last = total;
-
-                               /* Update averages ..*/
-                               avg_bytes += diff;
-                               avg_time += msecs;
-                               avg_bytes -= download[idx].bytes;
-                               avg_time -= download[idx].time;
-                               download[idx].bytes = diff;
-                               download[idx].time = msecs;
-                               idx++;
-                               if (idx >= NR_AVERAGE)
-                                       idx = 0;
-
-                               fprintf(stderr, "%4lu.%03luMB  (%lu kB/s)      \r",
-                                       total >> 20,
-                                       1000*((total >> 10) & 1023)>>10,
-                                       avg_bytes / avg_time );
-                       }
-               }
-       }
-       close(ofd);
-       return finish_pack(tmpfile, me);
-}
index e8708aa802b8e09d8044bb99dbccb0fecdb14481..0a169dce8574102a03c978bb24f8d0664f1fc9b6 100644 (file)
@@ -3,6 +3,9 @@
 #include "pkt-line.h"
 #include "commit.h"
 #include "tag.h"
+#include "exec_cmd.h"
+#include "sideband.h"
+#include <sys/wait.h>
 
 static int keep_pack;
 static int quiet;
@@ -42,7 +45,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 +146,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 +169,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 +257,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 +369,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);
 
@@ -415,6 +419,103 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
        return retval;
 }
 
+static pid_t setup_sideband(int fd[2], int xd[2])
+{
+       pid_t side_pid;
+
+       if (!use_sideband) {
+               fd[0] = xd[0];
+               fd[1] = xd[1];
+               return 0;
+       }
+       /* xd[] is talking with upload-pack; subprocess reads from
+        * xd[0], spits out band#2 to stderr, and feeds us band#1
+        * through our fd[0].
+        */
+       if (pipe(fd) < 0)
+               die("fetch-pack: unable to set up pipe");
+       side_pid = fork();
+       if (side_pid < 0)
+               die("fetch-pack: unable to fork off sideband demultiplexer");
+       if (!side_pid) {
+               /* subprocess */
+               close(fd[0]);
+               if (xd[0] != xd[1])
+                       close(xd[1]);
+               if (recv_sideband("fetch-pack", xd[0], fd[1], 2))
+                       exit(1);
+               exit(0);
+       }
+       close(xd[0]);
+       close(fd[1]);
+       fd[1] = xd[1];
+       return side_pid;
+}
+
+static int get_pack(int xd[2], const char **argv)
+{
+       int status;
+       pid_t pid, side_pid;
+       int fd[2];
+
+       side_pid = setup_sideband(fd, xd);
+       pid = fork();
+       if (pid < 0)
+               die("fetch-pack: unable to fork off %s", argv[0]);
+       if (!pid) {
+               dup2(fd[0], 0);
+               close(fd[0]);
+               close(fd[1]);
+               execv_git_cmd(argv);
+               die("%s exec failed", argv[0]);
+       }
+       close(fd[0]);
+       close(fd[1]);
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno != EINTR)
+                       die("waiting for %s: %s", argv[0], strerror(errno));
+       }
+       if (WIFEXITED(status)) {
+               int code = WEXITSTATUS(status);
+               if (code)
+                       die("%s died with error code %d", argv[0], code);
+               return 0;
+       }
+       if (WIFSIGNALED(status)) {
+               int sig = WTERMSIG(status);
+               die("%s died of signal %d", argv[0], sig);
+       }
+       die("%s died of unnatural causes %d", argv[0], status);
+}
+
+static int explode_rx_pack(int xd[2])
+{
+       const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL };
+       return get_pack(xd, argv);
+}
+
+static int keep_rx_pack(int xd[2])
+{
+       const char *argv[6];
+       char keep_arg[256];
+       int n = 0;
+
+       argv[n++] = "index-pack";
+       argv[n++] = "--stdin";
+       if (!quiet)
+               argv[n++] = "-v";
+       if (use_thin_pack)
+               argv[n++] = "--fix-thin";
+       if (keep_pack > 1) {
+               int s = sprintf(keep_arg, "--keep=fetch-pack %i on ", getpid());
+               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+                       strcpy(keep_arg + s, "localhost");
+               argv[n++] = keep_arg;
+       }
+       argv[n] = NULL;
+       return get_pack(xd, argv);
+}
+
 static int fetch_pack(int fd[2], int nr_match, char **match)
 {
        struct ref *ref;
@@ -446,17 +547,13 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
                goto all_done;
        }
        if (find_common(fd, sha1, ref) < 0)
-               if (!keep_pack)
+               if (keep_pack != 1)
                        /* When cloning, it is not unusual to have
                         * no common commit.
                         */
                        fprintf(stderr, "warning: no common commits\n");
 
-       if (keep_pack)
-               status = receive_keep_pack(fd, "git-fetch-pack", quiet, use_sideband);
-       else
-               status = receive_unpack_pack(fd, "git-fetch-pack", quiet, use_sideband);
-
+       status = (keep_pack) ? keep_rx_pack(fd) : explode_rx_pack(fd);
        if (status)
                die("git-fetch-pack: fetch failed.");
 
@@ -493,7 +590,7 @@ int main(int argc, char **argv)
                                continue;
                        }
                        if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
-                               keep_pack = 1;
+                               keep_pack++;
                                continue;
                        }
                        if (!strcmp("--thin", arg)) {
@@ -517,8 +614,6 @@ int main(int argc, char **argv)
        }
        if (!dest)
                usage(fetch_pack_usage);
-       if (keep_pack)
-               use_thin_pack = 0;
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;
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}});
-}
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755 (executable)
index 4f31903..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/bin/sh
-
-USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -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)
diff --git a/git-cherry.sh b/git-cherry.sh
deleted file mode 100755 (executable)
index 8832573..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano.
-#
-
-USAGE='[-v] <upstream> [<head>] [<limit>]'
-LONG_USAGE='             __*__*__*__*__> <upstream>
-            /
-  fork-point
-            \__+__+__+__+__+__+__+__> <head>
-
-Each commit between the fork-point (or <limit> if given) and <head> is
-examined, and compared against the change each commit between the
-fork-point and <upstream> introduces.  If the change seems to be in
-the upstream, it is shown on the standard output with prefix "+".
-Otherwise it is shown with prefix "-".'
-. git-sh-setup
-
-case "$1" in -v) verbose=t; shift ;; esac 
-
-case "$#,$1" in
-1,*..*)
-    upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$')
-    set x "$upstream" "$ours"
-    shift ;;
-esac
-
-case "$#" in
-1) upstream=`git-rev-parse --verify "$1"` &&
-   ours=`git-rev-parse --verify HEAD` || exit
-   limit="$upstream"
-   ;;
-2) upstream=`git-rev-parse --verify "$1"` &&
-   ours=`git-rev-parse --verify "$2"` || exit
-   limit="$upstream"
-   ;;
-3) upstream=`git-rev-parse --verify "$1"` &&
-   ours=`git-rev-parse --verify "$2"` &&
-   limit=`git-rev-parse --verify "$3"` || exit
-   ;;
-*) usage ;;
-esac
-
-# Note that these list commits in reverse order;
-# not that the order in inup matters...
-inup=`git-rev-list ^$ours $upstream` &&
-ours=`git-rev-list $ours ^$limit` || exit
-
-tmp=.cherry-tmp$$
-patch=$tmp-patch
-mkdir $patch
-trap "rm -rf $tmp-*" 0 1 2 3 15
-
-for c in $inup
-do
-       git-diff-tree -p $c
-done | git-patch-id |
-while read id name
-do
-       echo $name >>$patch/$id
-done
-
-LF='
-'
-
-O=
-for c in $ours
-do
-       set x `git-diff-tree -p $c | git-patch-id`
-       if test "$2" != ""
-       then
-               if test -f "$patch/$2"
-               then
-                       sign=-
-               else
-                       sign=+
-               fi
-               case "$verbose" in
-               t)
-                       c=$(git-rev-list --pretty=oneline --max-count=1 $c)
-               esac
-               case "$O" in
-               '')     O="$sign $c" ;;
-               *)      O="$sign $c$LF$O" ;;
-               esac
-       fi
-done
-case "$O" in
-'') ;;
-*)  echo "$O" ;;
-esac
index bf54a11508b0d35340b6a2c4a677a38a38e37aed..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
        ;;
@@ -414,7 +401,8 @@ Pull: refs/heads/$head_points_at:$origin_track" &&
 
        case "$no_checkout" in
        '')
-               git-read-tree -m -u -v HEAD HEAD
+               test "z$quiet" = z && v=-v || v=
+               git-read-tree -m -u $v HEAD HEAD
        esac
 fi
 rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
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 b15fc2b389f35e2b30b17eada4bcc711a12d2a11..7442dd2ca54ffc3744b260ee10309d2495afd5fd 100755 (executable)
@@ -20,7 +20,7 @@ verbose=
 update_head_ok=
 exec=
 upload_pack=
-keep=--thin
+keep=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -51,7 +51,7 @@ do
                verbose=Yes
                ;;
        -k|--k|--ke|--kee|--keep)
-               keep=--keep
+               keep='-k -k'
                ;;
        --reflog-action=*)
                rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
@@ -147,15 +147,15 @@ update_local_ref () {
        [ "$verbose" ] && echo >&2 "  $label_: $newshort_"
        return 0
     fi
-    oldshort_=$(git-rev-parse --short "$1" 2>/dev/null)
-    mkdir -p "$(dirname "$GIT_DIR/$1")"
+    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 "  $label_: $newshort_" ||:
@@ -296,6 +296,7 @@ fetch_main () {
       # There are transports that can fetch only one head at a time...
       case "$remote" in
       http://* | https://* | ftp://*)
+         proto=`expr "$remote" : '\([^:]*\):'`
          if [ -n "$GIT_SSL_NO_VERIFY" ]; then
              curl_extra_args="-k"
          fi
@@ -319,7 +320,7 @@ fetch_main () {
          done
          expr "z$head" : "z$_x40\$" >/dev/null ||
              die "Failed to fetch $remote_name from $remote"
-         echo >&2 Fetching "$remote_name from $remote" using http
+         echo >&2 "Fetching $remote_name from $remote using $proto"
          git-http-fetch -v -a "$head" "$remote/" || exit
          ;;
       rsync://*)
@@ -367,9 +368,10 @@ fetch_main () {
       ;; # we are already done.
   *)
     ( : subshell because we muck with IFS
+      pack_lockfile=
       IFS="    $LF"
       (
-         git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
+         git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote"
       ) |
       while read sha1 remote_name
       do
@@ -377,6 +379,12 @@ fetch_main () {
          failed)
                  echo >&2 "Fetch failure: $remote"
                  exit 1 ;;
+         # special line coming from index-pack with the pack name
+         pack)
+                 continue ;;
+         keep)
+                 pack_lockfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep"
+                 continue ;;
          esac
          found=
          single_force=
@@ -407,6 +415,7 @@ fetch_main () {
          append_fetch_head "$sha1" "$remote" \
                  "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
       done
+      if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
     ) || exit ;;
   esac
 
@@ -426,7 +435,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 fba4b0cb5fffbb1ad3f39c670c6364975c52186a..c49e4c65af606496c7e65b5147e36fee105d9ae7 100755 (executable)
@@ -23,6 +23,12 @@ case "${1:-.}${2:-.}${3:-.}" in
 "$1.." | "$1.$1" | "$1$1.")
        if [ "$2" ]; then
                echo "Removing $4"
+       else
+               # read-tree checked that index matches HEAD already,
+               # so we know we do not have this path tracked.
+               # there may be an unrelated working tree file here,
+               # which we should just leave unmolested.
+               exit 0
        fi
        if test -f "$4"; then
                rm -f -- "$4" &&
@@ -34,8 +40,16 @@ case "${1:-.}${2:-.}${3:-.}" in
 #
 # Added in one.
 #
-".$2." | "..$3" )
+".$2.")
+       # the other side did not add and we added so there is nothing
+       # to be done.
+       ;;
+"..$3")
        echo "Adding $4"
+       test -f "$4" || {
+               echo "ERROR: untracked $4 is overwritten by the merge."
+               exit 1
+       }
        git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
                exec git-checkout-index -u -f -- "$4"
        ;;
index 49c46d55df3491460864b3afd751258ec8ec97c7..cb094388bb5c830d37a790f5e7bbf13e8c61352a 100755 (executable)
@@ -93,6 +93,8 @@ finish () {
        esac
 }
 
+case "$#" in 0) usage ;; esac
+
 rloga=
 while case "$#" in 0) break ;; esac
 do
index f2c9071d1109e014832f0efd8a1fd67dca44c8af..f150a558ca7a7a659f4eb88be3a275e1a4e87337 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"-*
@@ -36,11 +45,19 @@ case ",$all_into_one," in
        args='--unpacked --incremental'
        ;;
 ,t,)
-       args=
-
-       # Redundancy check in all-into-one case is trivial.
-       existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \
-           find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
+       if [ -d "$PACKDIR" ]; then
+               for e in `cd "$PACKDIR" && find . -type f -name '*.pack' \
+                       | sed -e 's/^\.\///' -e 's/\.pack$//'`
+               do
+                       if [ -e "$PACKDIR/$e.keep" ]; then
+                               : keep
+                       else
+                               args="$args --unpacked=$e.pack"
+                               existing="$existing $e"
+                       fi
+               done
+       fi
+       [ -z "$args" ] && args='--unpacked --incremental'
        ;;
 esac
 
@@ -77,17 +94,16 @@ fi
 
 if test "$remove_redundant" = t
 then
-       # We know $existing are all redundant only when
-       # all-into-one is used.
-       if test "$all_into_one" != '' && test "$existing" != ''
+       # We know $existing are all redundant.
+       if [ -n "$existing" ]
        then
                sync
                ( cd "$PACKDIR" &&
                  for e in $existing
                  do
                        case "$e" in
-                       ./pack-$name.pack | ./pack-$name.idx) ;;
-                       *)      rm -f $e ;;
+                       pack-$name) ;;
+                       *)      rm -f "$e.pack" "$e.idx" "$e.keep" ;;
                        esac
                  done
                )
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 1c6d2cc7872ab82020d9dc32ce0086b1d3139e22..4c87c20c158fe3edfd0b770f1855e67e20038e3b 100755 (executable)
@@ -230,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 $_) {
@@ -514,7 +517,7 @@ sub send_message
                                                $2, $_) unless $quiet;
                                        push @cc, $2;
                                }
-                               elsif (/^[-A-Za-z]+:\s+\S/) {
+                               elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
                                        push @xh, $_;
                                }
 
index 54d23569337f680f7936390c8a83d2e7f2868c38..4a56f1871a6e185c4504d2464f264a8d8adc124b 100755 (executable)
@@ -1501,10 +1501,13 @@ sub svn_checkout_tree {
                        apply_mod_line_blob($m);
                        svn_check_prop_executable($m);
                } elsif ($m->{chg} eq 'T') {
-                       sys(qw(svn rm --force),$m->{file_b});
-                       apply_mod_line_blob($m);
-                       sys(qw(svn add), $m->{file_b});
                        svn_check_prop_executable($m);
+                       apply_mod_line_blob($m);
+                       if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+                               sys(qw(svn propdel svn:special), $m->{file_b});
+                       } else {
+                               sys(qw(svn propset svn:special *),$m->{file_b});
+                       }
                } elsif ($m->{chg} eq 'A') {
                        svn_ensure_parent_path( $m->{file_b} );
                        apply_mod_line_blob($m);
@@ -2659,11 +2662,12 @@ sub libsvn_connect {
 }
 
 sub libsvn_get_file {
-       my ($gui, $f, $rev) = @_;
+       my ($gui, $f, $rev, $chg) = @_;
        my $p = $f;
        if (length $SVN_PATH > 0) {
                return unless ($p =~ s#^\Q$SVN_PATH\E/##);
        }
+       print "\t$chg\t$f\n" unless $_q;
 
        my ($hash, $pid, $in, $out);
        my $pool = SVN::Pool->new;
@@ -2766,8 +2770,7 @@ sub libsvn_fetch {
                $pool->clear;
        }
        foreach (@amr) {
-               print "\t$_->[0]\t$_->[1]\n" unless $_q;
-               libsvn_get_file($gui, $_->[1], $rev)
+               libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
        }
        close $gui or croak $?;
        return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
@@ -2845,8 +2848,7 @@ sub libsvn_traverse {
                        if (defined $files) {
                                push @$files, $file;
                        } else {
-                               print "\tA\t$file\n" unless $_q;
-                               libsvn_get_file($gui, $file, $rev);
+                               libsvn_get_file($gui, $file, $rev, 'A');
                        }
                }
        }
@@ -3137,7 +3139,7 @@ sub copy_remote_ref {
        my $ref = "refs/remotes/$GIT_SVN";
        if (safe_qx('git-ls-remote', $origin, $ref)) {
                sys(qw/git fetch/, $origin, "$ref:$ref");
-       } else {
+       } elsif ($_cp_remote && !$_upgrade) {
                die "Unable to find remote reference: ",
                                "refs/remotes/$GIT_SVN on $origin\n";
        }
index f6eff8e32adc92a072b33de9040e2154efd13a8a..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();
 
@@ -427,6 +429,20 @@ ($$$$$)
        }
 }
 
+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;
@@ -446,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);
 }
 
@@ -898,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 b944c3740122aa2ecb204bd8a3690c14cd225a44..9a9addf71d3f2f7d712ff3cd2e0fa6dfec31dd28 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 },
@@ -268,6 +272,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 9b1217ac3f2a39f352e0b4046ceac9d438e4ac95..83268fc9d9e3ab63920b5ec4a5ffd71e8dd11b0d 100644 (file)
@@ -96,10 +96,10 @@ find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
 (find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
 %if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
 %else
 rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
@@ -126,10 +126,10 @@ rm -rf $RPM_BUILD_ROOT
 
 %files arch
 %defattr(-,root,root)
-%doc Documentation/*arch*.txt
-%{_bindir}/*arch*
-%{!?_without_docs: %{_mandir}/man1/*arch*.1*}
-%{!?_without_docs: %doc Documentation/*arch*.html }
+%doc Documentation/git-archimport.txt
+%{_bindir}/git-archimport
+%{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
+%{!?_without_docs: %doc Documentation/git-archimport.html }
 
 %files email
 %defattr(-,root,root)
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 035e85e6e9c25623783ef36e2e91964b306de3b5..3c6fd7ca47551eb808a40ca2cc7ed1690f19cc06 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
@@ -69,7 +76,7 @@
 
 # list of git base URLs used for URL to where fetch project from,
 # i.e. full URL is "$git_base_url/$project"
-our @git_base_url_list = ("++GITWEB_BASE_URL++");
+our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
 
 # default blob_plain mimetype and default charset for text/plain blob
 our $default_blob_plain_mimetype = 'text/plain';
        'pathinfo' => {
                'override' => 0,
                'default' => [0]},
+
+       # Make gitweb consider projects in project root subdirectories
+       # to be forks of existing projects. Given project $projname.git,
+       # projects matching $projname/*.git will not be shown in the main
+       # projects list, instead a '+' mark will be added to $projname
+       # there and a 'forks' view will be enabled for the project, listing
+       # all the forks. This feature is supported only if project list
+       # is taken from a directory, not file.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'forks'}{'default'} = [1];
+       # Project specific override is not supported.
+       'forks' => {
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_check_feature {
@@ -217,6 +239,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).
@@ -249,7 +287,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;
@@ -316,6 +354,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;
@@ -326,7 +371,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
@@ -375,11 +420,13 @@ sub evaluate_path_info {
        "commitdiff" => \&git_commitdiff,
        "commitdiff_plain" => \&git_commitdiff_plain,
        "commit" => \&git_commit,
+       "forks" => \&git_forks,
        "heads" => \&git_heads,
        "history" => \&git_history,
        "log" => \&git_log,
        "rss" => \&git_rss,
        "search" => \&git_search,
+       "search_help" => \&git_search_help,
        "shortlog" => \&git_shortlog,
        "summary" => \&git_summary,
        "tag" => \&git_tag,
@@ -429,6 +476,7 @@ (%)
                page => "pg",
                order => "o",
                searchtext => "s",
+               searchtype => "st",
        );
        my %mapping = @mapping;
 
@@ -522,12 +570,17 @@ sub esc_url {
 }
 
 # replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html {
+sub esc_html ($;%) {
        my $str = shift;
+       my %opts = @_;
+
        $str = to_utf8($str);
        $str = escapeHTML($str);
        $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
        $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1)
+       if ($opts{'-nbsp'}) {
+               $str =~ s/ /&nbsp;/g;
+       }
        return $str;
 }
 
@@ -752,7 +805,7 @@ sub format_diff_line {
                $diff_class = " incomplete";
        }
        $line = untabify($line);
-       return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
+       return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 }
 
 ## ----------------------------------------------------------------------
@@ -828,7 +881,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;
@@ -860,13 +913,21 @@ sub git_get_project_url_list {
 }
 
 sub git_get_projects_list {
+       my ($filter) = @_;
        my @list;
 
+       $filter ||= '';
+       $filter =~ s/\.git$//;
+
        if (-d $projects_list) {
                # search in directory
-               my $dir = $projects_list;
+               my $dir = $projects_list . ($filter ? "/$filter" : '');
+               # remove the trailing "/"
+               $dir =~ s!/+$!!;
                my $pfxlen = length("$dir");
 
+               my $check_forks = gitweb_check_feature('forks');
+
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
@@ -878,9 +939,10 @@ 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")) {
-                                       push @list, { path => $subdir };
+                               if ($check_forks and $subdir =~ m#/.#) {
+                                       $File::Find::prune = 1;
+                               } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
+                                       push @list, { path => ($filter ? "$filter/" : '') . $subdir };
                                        $File::Find::prune = 1;
                                }
                        },
@@ -900,8 +962,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),
@@ -946,6 +1007,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;
@@ -1011,6 +1090,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;
 }
 
@@ -1059,12 +1141,13 @@ sub parse_commit {
        if (defined $commit_text) {
                @commit_lines = @$commit_text;
        } else {
-               $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id
+               local $/ = "\0";
+               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;
-               $/ = "\n";
                pop @commit_lines;
        }
        my $header = shift @commit_lines;
@@ -1225,7 +1308,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;
@@ -1242,47 +1325,88 @@ ($;%)
 ## ......................................................................
 ## parse to array of hashes functions
 
-sub git_get_refs_list {
-       my $type = shift || "";
-       my %refs;
-       my @reflist;
+sub git_get_heads_list {
+       my $limit = shift;
+       my @headslist;
 
-       my @refs;
-       open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+       open my $fd, '-|', git_cmd(), 'for-each-ref',
+               ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+               '--format=%(objectname) %(refname) %(subject)%00%(committer)',
+               'refs/heads'
                or return;
        while (my $line = <$fd>) {
-               chomp $line;
-               if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) {
-                       if (defined $refs{$1}) {
-                               push @{$refs{$1}}, $2;
-                       } else {
-                               $refs{$1} = [ $2 ];
-                       }
+               my %ref_item;
 
-                       if (! $4) { # unpeeled, direct reference
-                               push @refs, { hash => $1, name => $3 }; # without type
-                       } elsif ($3 eq $refs[-1]{'name'}) {
-                               # most likely a tag is followed by its peeled
-                               # (deref) one, and when that happens we know the
-                               # previous one was of type 'tag'.
-                               $refs[-1]{'type'} = "tag";
-                       }
+               chomp $line;
+               my ($refinfo, $committerinfo) = split(/\0/, $line);
+               my ($hash, $name, $title) = split(' ', $refinfo, 3);
+               my ($committer, $epoch, $tz) =
+                       ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+               $name =~ s!^refs/heads/!!;
+
+               $ref_item{'name'}  = $name;
+               $ref_item{'id'}    = $hash;
+               $ref_item{'title'} = $title || '(no commit message)';
+               $ref_item{'epoch'} = $epoch;
+               if ($epoch) {
+                       $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+               } else {
+                       $ref_item{'age'} = "unknown";
                }
+
+               push @headslist, \%ref_item;
        }
        close $fd;
 
-       foreach my $ref (@refs) {
-               my $ref_file = $ref->{'name'};
-               my $ref_id   = $ref->{'hash'};
+       return wantarray ? @headslist : \@headslist;
+}
 
-               my $type = $ref->{'type'} || git_get_type($ref_id) || next;
-               my %ref_item = parse_ref($ref_file, $ref_id, $type);
+sub git_get_tags_list {
+       my $limit = shift;
+       my @tagslist;
 
-               push @reflist, \%ref_item;
+       open my $fd, '-|', git_cmd(), 'for-each-ref',
+               ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
+               '--format=%(objectname) %(objecttype) %(refname) '.
+               '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
+               'refs/tags'
+               or return;
+       while (my $line = <$fd>) {
+               my %ref_item;
+
+               chomp $line;
+               my ($refinfo, $creatorinfo) = split(/\0/, $line);
+               my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
+               my ($creator, $epoch, $tz) =
+                       ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+               $name =~ s!^refs/tags/!!;
+
+               $ref_item{'type'} = $type;
+               $ref_item{'id'} = $id;
+               $ref_item{'name'} = $name;
+               if ($type eq "tag") {
+                       $ref_item{'subject'} = $title;
+                       $ref_item{'reftype'} = $reftype;
+                       $ref_item{'refid'}   = $refid;
+               } else {
+                       $ref_item{'reftype'} = $type;
+                       $ref_item{'refid'}   = $id;
+               }
+
+               if ($type eq "tag" || $type eq "commit") {
+                       $ref_item{'epoch'} = $epoch;
+                       if ($epoch) {
+                               $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+                       } else {
+                               $ref_item{'age'} = "unknown";
+                       }
+               }
+
+               push @tagslist, \%ref_item;
        }
-       # sort refs by age
-       @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
-       return (\@reflist, \%refs);
+       close $fd;
+
+       return wantarray ? @tagslist : \@tagslist;
 }
 
 ## ----------------------------------------------------------------------
@@ -1379,7 +1503,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) {
@@ -1417,8 +1541,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",
@@ -1436,8 +1569,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"/>));
@@ -1467,6 +1607,10 @@ sub git_header_html {
                      $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";
@@ -1489,8 +1633,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>";
 }
 
@@ -1615,17 +1766,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,
@@ -1641,11 +1791,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 (\@;%) {
@@ -1698,15 +1849,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) = @_;
@@ -1722,26 +1864,28 @@ sub git_print_tree_entry {
        if ($t->{'type'} eq "blob") {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-                                              file_name=>"$basedir$t->{'name'}", %base_key),
-                                -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
+                                              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'},
-                                                    file_name=>"$basedir$t->{'name'}", %base_key)},
-                                     "blame");
+                       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");
                }
                print " | " .
                        $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
-                                              file_name=>"$basedir$t->{'name'}")},
-                               "raw");
+                                              file_name=>"$basedir$t->{'name'}")},
+                               "raw");
                print "</td>\n";
 
        } elsif ($t->{'type'} eq "tree") {
@@ -1751,8 +1895,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");
                }
@@ -1809,7 +1957,7 @@ sub git_difftree_body {
                        print "<td>";
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
                                                     hash_base=>$hash, file_name=>$diff{'file'}),
-                                      -class => "list"}, esc_html($diff{'file'}));
+                                     -class => "list"}, esc_html($diff{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chng</td>\n";
                        print "<td class=\"link\">";
@@ -1835,12 +1983,15 @@ 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") . " | ";
+                                                    file_name=>$diff{'file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                    file_name=>$diff{'file'})},
-                                     "history");
+                                                    file_name=>$diff{'file'})},
+                                     "history");
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
@@ -1861,31 +2012,34 @@ sub git_difftree_body {
                        }
                        print "<td>";
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'}),
-                                      -class => "list"}, esc_html($diff{'file'}));
+                                                    hash_base=>$hash, file_name=>$diff{'file'}),
+                                     -class => "list"}, esc_html($diff{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chnge</td>\n";
                        print "<td class=\"link\">";
-                       if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
-                               if ($action eq 'commitdiff') {
-                                       # link to patch
-                                       $patchno++;
-                                       print $cgi->a({-href => "#patch$patchno"}, "patch");
-                               } else {
-                                       print $cgi->a({-href => href(action=>"blobdiff",
-                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                    hash_base=>$hash, hash_parent_base=>$parent,
-                                                                    file_name=>$diff{'file'})},
-                                                     "diff");
-                               }
-                               print " | ";
+                       if ($action eq 'commitdiff') {
+                               # link to patch
+                               $patchno++;
+                               print $cgi->a({-href => "#patch$patchno"}, "patch") .
+                                     " | ";
+                       } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+                               # "commit" view and modified file (not onlu mode changed)
+                               print $cgi->a({-href => href(action=>"blobdiff",
+                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                            hash_base=>$hash, hash_parent_base=>$parent,
+                                                            file_name=>$diff{'file'})},
+                                             "diff") .
+                                     " | ";
                        }
+                       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") . " | ";
+                                                    file_name=>$diff{'file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
-                                                    file_name=>$diff{'file'})},
-                                     "history");
+                                                    file_name=>$diff{'file'})},
+                                     "history");
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
@@ -1906,26 +2060,29 @@ sub git_difftree_body {
                                      -class => "list"}, esc_html($diff{'from_file'})) .
                              " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
                              "<td class=\"link\">";
-                       if ($diff{'to_id'} ne $diff{'from_id'}) {
-                               if ($action eq 'commitdiff') {
-                                       # link to patch
-                                       $patchno++;
-                                       print $cgi->a({-href => "#patch$patchno"}, "patch");
-                               } else {
-                                       print $cgi->a({-href => href(action=>"blobdiff",
-                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                    hash_base=>$hash, hash_parent_base=>$parent,
-                                                                    file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
-                                                     "diff");
-                               }
-                               print " | ";
+                       if ($action eq 'commitdiff') {
+                               # link to patch
+                               $patchno++;
+                               print $cgi->a({-href => "#patch$patchno"}, "patch") .
+                                     " | ";
+                       } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+                               # "commit" view and modified file (not only pure rename or copy)
+                               print $cgi->a({-href => href(action=>"blobdiff",
+                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                            hash_base=>$hash, hash_parent_base=>$parent,
+                                                            file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+                                             "diff") .
+                                     " | ";
                        }
+                       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") . " | ";
+                                                    file_name=>$diff{'from_file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                    file_name=>$diff{'from_file'})},
-                                     "history");
+                                                   file_name=>$diff{'from_file'})},
+                                     "history");
                        print "</td>\n";
 
                } # we should not encounter Unmerged (U) or Unknown (X) status
@@ -1966,13 +2123,6 @@ sub git_patchset_body {
                        }
                        $patch_idx++;
 
-                       # for now, no extended header, hence we skip empty patches
-                       # companion to  next LINE if $in_header;
-                       if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change
-                               $in_header = 1;
-                               next LINE;
-                       }
-
                        if ($diffinfo->{'status'} eq "A") { # added
                                print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
                                      $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
@@ -2059,6 +2209,124 @@ sub git_patchset_body {
 
 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
+sub git_project_list_body {
+       my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+
+       my $check_forks = gitweb_check_feature('forks');
+
+       my @projects;
+       foreach my $pr (@$projlist) {
+               my (@aa) = git_get_last_activity($pr->{'path'});
+               unless (@aa) {
+                       next;
+               }
+               ($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);
+               }
+               if (!defined $pr->{'owner'}) {
+                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+               }
+               if ($check_forks) {
+                       my $pname = $pr->{'path'};
+                       $pname =~ s/\.git$//;
+                       $pr->{'forks'} = -d "$projectroot/$pname";
+               }
+               push @projects, $pr;
+       }
+
+       $order ||= "project";
+       $from = 0 unless defined $from;
+       $to = $#projects if (!defined $to || $#projects < $to);
+
+       print "<table class=\"project_list\">\n";
+       unless ($no_header) {
+               print "<tr>\n";
+               if ($check_forks) {
+                       print "<th></th>\n";
+               }
+               if ($order eq "project") {
+                       @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+                       print "<th>Project</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'project'),
+                                      -class => "header"}, "Project") .
+                             "</th>\n";
+               }
+               if ($order eq "descr") {
+                       @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+                       print "<th>Description</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'descr'),
+                                      -class => "header"}, "Description") .
+                             "</th>\n";
+               }
+               if ($order eq "owner") {
+                       @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+                       print "<th>Owner</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'owner'),
+                                      -class => "header"}, "Owner") .
+                             "</th>\n";
+               }
+               if ($order eq "age") {
+                       @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
+                       print "<th>Last Change</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'age'),
+                                      -class => "header"}, "Last Change") .
+                             "</th>\n";
+               }
+               print "<th></th>\n" .
+                     "</tr>\n";
+       }
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my $pr = $projects[$i];
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               if ($check_forks) {
+                       print "<td>";
+                       if ($pr->{'forks'}) {
+                               print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+                       }
+                       print "</td>\n";
+               }
+               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -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->{'age'}) . "\">" .
+                     $pr->{'age_string'} . "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
+                     $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+                     ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+                     "</td>\n" .
+                     "</tr>\n";
+       }
+       if (defined $extra) {
+               print "<tr>\n";
+               if ($check_forks) {
+                       print "<td></td>\n";
+               }
+               print "<td colspan=\"5\">$extra</td>\n" .
+                     "</tr>\n";
+       }
+       print "</table>\n";
+}
+
 sub git_shortlog_body {
        # uses global variable $project
        my ($revlist, $from, $to, $refs, $extra) = @_;
@@ -2087,6 +2355,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()) {
@@ -2178,8 +2447,7 @@ sub git_tags_body {
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $taglist->[$i];
                my %tag = %$entry;
-               my $comment_lines = $tag{'comment'};
-               my $comment = shift @$comment_lines;
+               my $comment = $tag{'subject'};
                my $comment_short;
                if (defined $comment) {
                        $comment_short = chop_str($comment, 30, 5);
@@ -2212,7 +2480,7 @@ sub git_tags_body {
                      $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
                if ($tag{'reftype'} eq "commit") {
                        print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") .
-                             " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log");
+                             " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log");
                } elsif ($tag{'reftype'} eq "blob") {
                        print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
                }
@@ -2237,23 +2505,23 @@ sub git_heads_body {
        my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $headlist->[$i];
-               my %tag = %$entry;
-               my $curr = $tag{'id'} eq $head;
+               my %ref = %$entry;
+               my $curr = $ref{'id'} eq $head;
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td><i>$tag{'age'}</i></td>\n" .
-                     ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
-                     $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}),
-                              -class => "list name"},esc_html($tag{'name'})) .
+               print "<td><i>$ref{'age'}</i></td>\n" .
+                     ($curr ? "<td class=\"current_head\">" : "<td>") .
+                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}),
+                              -class => "list name"},esc_html($ref{'name'})) .
                      "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " .
-                     $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") .
+                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " .
+                     $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") .
                      "</td>\n" .
                      "</tr>";
        }
@@ -2276,30 +2544,9 @@ sub git_project_list {
        }
 
        my @list = git_get_projects_list();
-       my @projects;
        if (!@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) {
-                       next;
-               }
-               $pr->{'commit'} = \%co;
-               if (!defined $pr->{'descr'}) {
-                       my $descr = git_get_project_description($pr->{'path'}) || "";
-                       $pr->{'descr'} = chop_str($descr, 25, 5);
-               }
-               if (!defined $pr->{'owner'}) {
-                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
-               }
-               push @projects, $pr;
-       }
 
        git_header_html();
        if (-f $home_text) {
@@ -2309,75 +2556,30 @@ sub git_project_list {
                close $fd;
                print "</div>\n";
        }
-       print "<table class=\"project_list\">\n" .
-             "<tr>\n";
-       $order ||= "project";
-       if ($order eq "project") {
-               @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
-               print "<th>Project</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'project'),
-                              -class => "header"}, "Project") .
-                     "</th>\n";
-       }
-       if ($order eq "descr") {
-               @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
-               print "<th>Description</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'descr'),
-                              -class => "header"}, "Description") .
-                     "</th>\n";
-       }
-       if ($order eq "owner") {
-               @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
-               print "<th>Owner</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'owner'),
-                              -class => "header"}, "Owner") .
-                     "</th>\n";
-       }
-       if ($order eq "age") {
-               @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
-               print "<th>Last Change</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'age'),
-                              -class => "header"}, "Last Change") .
-                     "</th>\n";
+       git_project_list_body(\@list, $order);
+       git_footer_html();
+}
+
+sub git_forks {
+       my $order = $cgi->param('o');
+       if (defined $order && $order !~ m/project|descr|owner|age/) {
+               die_error(undef, "Unknown order parameter");
        }
-       print "<th></th>\n" .
-             "</tr>\n";
-       my $alternate = 1;
-       foreach my $pr (@projects) {
-               if ($alternate) {
-                       print "<tr class=\"dark\">\n";
-               } else {
-                       print "<tr class=\"light\">\n";
-               }
-               $alternate ^= 1;
-               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-                                       -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" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
-                     "</td>\n" .
-                     "</tr>\n";
+
+       my @list = git_get_projects_list($project);
+       if (!@list) {
+               die_error(undef, "No forks found");
        }
-       print "</table>\n";
+
+       git_header_html();
+       git_print_page_nav('','');
+       git_print_header_div('summary', "$project forks");
+       git_project_list_body(\@list, $order);
        git_footer_html();
 }
 
 sub git_project_index {
-       my @projects = git_get_projects_list();
+       my @projects = git_get_projects_list($project);
 
        print $cgi->header(
                -type => 'text/plain',
@@ -2408,17 +2610,12 @@ sub git_summary {
 
        my $owner = git_get_project_owner($project);
 
-       my ($reflist, $refs) = git_get_refs_list();
-
-       my @taglist;
-       my @headlist;
-       foreach my $ref (@$reflist) {
-               if ($ref->{'name'} =~ s!^heads/!!) {
-                       push @headlist, $ref;
-               } else {
-                       $ref->{'name'} =~ s!^tags/!!;
-                       push @taglist, $ref;
-               }
+       my $refs = git_get_references();
+       my @taglist  = git_get_tags_list(15);
+       my @headlist = git_get_heads_list(15);
+       my @forklist;
+       if (gitweb_check_feature('forks')) {
+               @forklist = git_get_projects_list($project);
        }
 
        git_header_html();
@@ -2441,8 +2638,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;
@@ -2462,6 +2667,13 @@ sub git_summary {
                               $cgi->a({-href => href(action=>"heads")}, "..."));
        }
 
+       if (@forklist) {
+               git_print_header_div('forks');
+               git_project_list_body(\@forklist, undef, 0, 15,
+                                     $cgi->a({-href => href(action=>"forks")}, "..."),
+                                     'noheader');
+       }
+
        git_footer_html();
 }
 
@@ -2519,7 +2731,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 =
@@ -2543,25 +2756,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";
        }
@@ -2675,9 +2915,9 @@ sub git_tags {
        git_print_page_nav('','', $head,undef,$head);
        git_print_header_div('summary', $project);
 
-       my ($taglist) = git_get_refs_list("tags");
-       if (@$taglist) {
-               git_tags_body($taglist);
+       my @tagslist = git_get_tags_list();
+       if (@tagslist) {
+               git_tags_body(\@tagslist);
        }
        git_footer_html();
 }
@@ -2688,9 +2928,9 @@ sub git_heads {
        git_print_page_nav('','', $head,undef,$head);
        git_print_header_div('summary', $project);
 
-       my ($headlist) = git_get_refs_list("heads");
-       if (@$headlist) {
-               git_heads_body($headlist, $head);
+       my @headslist = git_get_heads_list();
+       if (@headslist) {
+               git_heads_body(\@headslist, $head);
        }
        git_footer_html();
 }
@@ -2803,7 +3043,7 @@ sub git_blob {
                $nr++;
                $line = untabify($line);
                printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                      $nr, $nr, $nr, esc_html($line);
+                      $nr, $nr, $nr, esc_html($line, -nbsp=>1);
        }
        close $fd
                or print "Reading blob failed.\n";
@@ -2834,7 +3074,7 @@ sub git_tree {
        my $refs = git_get_references();
        my $ref = format_ref_marker($refs, $hash_base);
        git_header_html();
-       my $base = "";
+       my $basedir = '';
        my ($have_blame) = gitweb_check_feature('blame');
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                my @views_nav = ();
@@ -2851,7 +3091,7 @@ sub git_tree {
                        # FIXME: Should be available when we have no hash base as well.
                        push @views_nav,
                                $cgi->a({-href => href(action=>"snapshot", hash=>$hash)},
-                                       "snapshot");
+                                       "snapshot");
                }
                git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
@@ -2862,12 +3102,39 @@ sub git_tree {
                print "<div class=\"title\">$hash</div>\n";
        }
        if (defined $file_name) {
-               $base = esc_html("$file_name/");
+               $basedir = $file_name;
+               if ($basedir ne '' && substr($basedir, -1) ne '/') {
+                       $basedir .= '/';
+               }
        }
        git_print_page_path($file_name, 'tree', $hash_base);
        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);
 
@@ -2878,7 +3145,7 @@ sub git_tree {
                }
                $alternate ^= 1;
 
-               git_print_tree_entry(\%t, $base, $hash_base, $have_blame);
+               git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
 
                print "</tr>\n";
        }
@@ -2930,7 +3197,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;
@@ -2969,7 +3236,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();
@@ -2987,7 +3254,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");
@@ -3092,7 +3360,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>;
@@ -3106,7 +3375,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'
@@ -3176,7 +3446,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")
@@ -3253,6 +3524,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';
        }
@@ -3262,7 +3578,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>)) {
@@ -3273,7 +3590,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 {
@@ -3290,19 +3607,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");
@@ -3434,18 +3749,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');
@@ -3453,23 +3758,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;
@@ -3511,7 +3819,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 | " .
@@ -3571,6 +3879,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) {
@@ -3582,7 +3915,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;
@@ -3610,7 +3943,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");
@@ -3634,7 +3968,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
@@ -3679,7 +4013,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">
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 16804ab286a42c0dc2733dccf816ab2173e465af..a6a65680ee6daf062180d2580e83616cfff4cda4 100644 (file)
@@ -272,7 +272,7 @@ buffer_gets( buffer_t * b, char **s )
                                n = b->bytes - start;
 
                                if (n)
-                                       memcpy( b->buf, b->buf + start, n );
+                                       memmove(b->buf, b->buf + start, n);
                                b->offset -= start;
                                b->bytes = n;
                                start = 0;
index 80bc6cb45b1a91c31dbdf18b48399b7a7a6b2737..042aea8842b531254e9a2d0ec67cbaeb40990575 100644 (file)
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
+#include <sys/time.h>
+#include <signal.h>
 
 static const char index_pack_usage[] =
-"git-index-pack [-o index-file] pack-file";
+"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
 
 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;
+       int obj_no;
 };
 
-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 int nr_resolved_deltas;
+
+static int from_stdin;
+static int verbose;
+
+static volatile sig_atomic_t progress_update;
 
-static void open_pack_file(void)
+static void progress_interval(int signum)
 {
-       int fd;
-       struct stat st;
+       progress_update = 1;
+}
 
-       fd = open(pack_name, O_RDONLY);
-       if (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));
+static void setup_progress_signal(void)
+{
+       struct sigaction sa;
+       struct itimerval v;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = progress_interval;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGALRM, &sa, NULL);
+
+       v.it_interval.tv_sec = 1;
+       v.it_interval.tv_usec = 0;
+       v.it_value = v.it_interval;
+       setitimer(ITIMER_REAL, &v, NULL);
+
+}
+
+static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
+{
+       unsigned percent = n * 100 / total;
+       if (percent != last_pc || progress_update) {
+               fprintf(stderr, "%4u%% (%u/%u) done\r", percent, n, total);
+               progress_update = 0;
        }
-       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));
+       return percent;
+}
+
+/* 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, output_fd, mmap_fd;
+
+/* Discard current buffer used content. */
+static void flush()
+{
+       if (input_offset) {
+               if (output_fd >= 0)
+                       write_or_die(output_fd, input_buffer, input_offset);
+               SHA1_Update(&input_ctx, input_buffer, input_offset);
+               memcpy(input_buffer, input_buffer + input_offset, input_len);
+               input_offset = 0;
        }
-       close(fd);
 }
 
-static void parse_pack_header(void)
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
 {
-       const struct pack_header *hdr;
-       unsigned char sha1[20];
-       SHA_CTX ctx;
+       if (min <= input_len)
+               return input_buffer + input_offset;
+       if (min > sizeof(input_buffer))
+               die("cannot fill %d bytes", min);
+       flush();
+       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;
+}
+
+static const char *open_pack_file(const char *pack_name)
+{
+       if (from_stdin) {
+               input_fd = 0;
+               if (!pack_name) {
+                       static char tmpfile[PATH_MAX];
+                       snprintf(tmpfile, sizeof(tmpfile),
+                                "%s/pack_XXXXXX", get_object_directory());
+                       output_fd = mkstemp(tmpfile);
+                       pack_name = xstrdup(tmpfile);
+               } else
+                       output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
+               if (output_fd < 0)
+                       die("unable to create %s: %s\n", pack_name, strerror(errno));
+               mmap_fd = output_fd;
+       } else {
+               input_fd = open(pack_name, O_RDONLY);
+               if (input_fd < 0)
+                       die("cannot open packfile '%s': %s",
+                           pack_name, strerror(errno));
+               output_fd = -1;
+               mmap_fd = input_fd;
+       }
+       SHA1_Init(&input_ctx);
+       return pack_name;
+}
 
-       /* 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);
+static void parse_pack_header(void)
+{
+       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);
+               die("pack signature mismatch");
        if (!pack_version_ok(hdr->hdr_version))
-               die("packfile '%s' version %d unsupported",
-                   pack_name, ntohl(hdr->hdr_version));
+               die("pack version %d unsupported", 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));
 }
 
 static void bad_object(unsigned long offset, const char *format,
@@ -97,90 +186,124 @@ static void bad_object(unsigned long offset, const char *format, ...)
        va_start(params, format);
        vsnprintf(buf, sizeof(buf), format, params);
        va_end(params);
-       die("packfile '%s': bad object at offset %lu: %s",
-           pack_name, offset, buf);
+       die("pack has bad object at offset %lu: %s", 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,
+                  mmap_fd, from - pg_offset);
+       if (map == MAP_FAILED)
+               die("cannot mmap pack file: %s", 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 +312,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 +324,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_children(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;
@@ -244,33 +367,46 @@ static void sha1_object(const void *data, unsigned long size,
        SHA1_Final(sha1, &ctx);
 }
 
-static void resolve_delta(struct delta_entry *delta, void *base_data,
+static void resolve_delta(struct object_entry *delta_obj, void *base_data,
                          unsigned long base_size, enum object_type type)
 {
-       struct object_entry *obj = delta->obj;
        void *delta_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_obj->real_type = type;
+       delta_data = get_data_from_pack(delta_obj);
+       delta_size = delta_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)) {
-               for (j = first; j <= last; j++)
-                       resolve_delta(&deltas[j], result, result_size, type);
+               bad_object(delta_obj->offset, "failed to apply delta");
+       sha1_object(result, result_size, type, delta_obj->sha1);
+       nr_resolved_deltas++;
+
+       hashcpy(delta_base.sha1, delta_obj->sha1);
+       if (!find_delta_children(&delta_base, &first, &last)) {
+               for (j = first; j <= last; j++) {
+                       struct object_entry *child = objects + deltas[j].obj_no;
+                       if (child->real_type == OBJ_REF_DELTA)
+                               resolve_delta(child, result, result_size, type);
+               }
+       }
+
+       memset(&delta_base, 0, sizeof(delta_base));
+       delta_base.offset = delta_obj->offset;
+       if (!find_delta_children(&delta_base, &first, &last)) {
+               for (j = first; j <= last; j++) {
+                       struct object_entry *child = objects + deltas[j].obj_no;
+                       if (child->real_type == OBJ_OFS_DELTA)
+                               resolve_delta(child, result, result_size, type);
+               }
        }
+
        free(result);
 }
 
@@ -278,41 +414,60 @@ 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];
+       int i, percent = -1;
+       struct delta_entry *delta = deltas;
        void *data;
-       unsigned long data_size;
+       struct stat st;
 
        /*
         * First pass:
         * - find locations of all objects;
         * - calculate SHA1 of all non-delta objects;
-        * - remember base SHA1 for all deltas.
+        * - remember base (SHA1 or offset) for all deltas.
         */
+       if (verbose)
+               fprintf(stderr, "Indexing %d objects.\n", nr_objects);
        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++];
-                       delta->obj = obj;
-                       hashcpy(delta->base_sha1, base_sha1);
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       nr_deltas++;
+                       delta->obj_no = i;
+                       delta++;
                } else
-                       sha1_object(data, data_size, obj->type, obj->sha1);
+                       sha1_object(data, obj->size, obj->type, obj->sha1);
                free(data);
+               if (verbose)
+                       percent = display_progress(i+1, nr_objects, percent);
        }
-       if (offset != pack_size - 20)
-               die("packfile '%s' has junk at the end", pack_name);
-
-       /* Sort deltas by base SHA1 for fast searching */
+       objects[i].offset = consumed_bytes;
+       if (verbose)
+               fputc('\n', stderr);
+
+       /* Check pack integrity */
+       flush();
+       SHA1_Final(sha1, &input_ctx);
+       if (hashcmp(fill(20), sha1))
+               die("pack is corrupted (SHA1 mismatch)");
+       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", strerror(errno));
+       if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
+               die("pack has junk at the end");
+
+       if (!nr_deltas)
+               return;
+
+       /* Sort deltas by base SHA1/offset for fast searching */
        qsort(deltas, nr_deltas, sizeof(struct delta_entry),
              compare_delta_entry);
 
@@ -324,26 +479,189 @@ static void parse_pack_objects(void)
         *   recursively checking if the resulting object is used as a base
         *   for some more deltas.
         */
+       if (verbose)
+               fprintf(stderr, "Resolving %d deltas.\n", nr_deltas);
        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_children(&base, &ref_first, &ref_last);
+               memset(&base, 0, sizeof(base));
+               base.offset = obj->offset;
+               ofs = !find_delta_children(&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++) {
+                               struct object_entry *child = objects + deltas[j].obj_no;
+                               if (child->real_type == OBJ_REF_DELTA)
+                                       resolve_delta(child, data,
+                                                     obj->size, obj->type);
+                       }
+               if (ofs)
+                       for (j = ofs_first; j <= ofs_last; j++) {
+                               struct object_entry *child = objects + deltas[j].obj_no;
+                               if (child->real_type == OBJ_OFS_DELTA)
+                                       resolve_delta(child, data,
+                                                     obj->size, obj->type);
+                       }
                free(data);
+               if (verbose)
+                       percent = display_progress(nr_resolved_deltas,
+                                                  nr_deltas, percent);
        }
+       if (verbose && nr_resolved_deltas == nr_deltas)
+               fputc('\n', stderr);
+}
+
+static int write_compressed(int fd, void *in, unsigned int size)
+{
+       z_stream stream;
+       unsigned long maxsize;
+       void *out;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, zlib_compression_level);
+       maxsize = deflateBound(&stream, size);
+       out = xmalloc(maxsize);
+
+       /* Compress it */
+       stream.next_in = in;
+       stream.avail_in = size;
+       stream.next_out = out;
+       stream.avail_out = maxsize;
+       while (deflate(&stream, Z_FINISH) == Z_OK);
+       deflateEnd(&stream);
+
+       size = stream.total_out;
+       write_or_die(fd, out, size);
+       free(out);
+       return size;
+}
+
+static void append_obj_to_pack(void *buf,
+                              unsigned long size, enum object_type type)
+{
+       struct object_entry *obj = &objects[nr_objects++];
+       unsigned char header[10];
+       unsigned long s = size;
+       int n = 0;
+       unsigned char c = (type << 4) | (s & 15);
+       s >>= 4;
+       while (s) {
+               header[n++] = c | 0x80;
+               c = s & 0x7f;
+               s >>= 7;
+       }
+       header[n++] = c;
+       write_or_die(output_fd, header, n);
+       obj[1].offset = obj[0].offset + n;
+       obj[1].offset += write_compressed(output_fd, buf, size);
+       sha1_object(buf, size, type, obj->sha1);
+}
+
+static int delta_pos_compare(const void *_a, const void *_b)
+{
+       struct delta_entry *a = *(struct delta_entry **)_a;
+       struct delta_entry *b = *(struct delta_entry **)_b;
+       return a->obj_no - b->obj_no;
+}
+
+static void fix_unresolved_deltas(int nr_unresolved)
+{
+       struct delta_entry **sorted_by_pos;
+       int i, n = 0, percent = -1;
 
-       /* Check for unresolved deltas */
+       /*
+        * Since many unresolved deltas may well be themselves base objects
+        * for more unresolved deltas, we really want to include the
+        * smallest number of base objects that would cover as much delta
+        * as possible by picking the
+        * trunc deltas first, allowing for other deltas to resolve without
+        * additional base objects.  Since most base objects are to be found
+        * before deltas depending on them, a good heuristic is to start
+        * resolving deltas in the same order as their position in the pack.
+        */
+       sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
        for (i = 0; i < nr_deltas; i++) {
-               if (deltas[i].obj->real_type == OBJ_DELTA)
-                       die("packfile '%s' has unresolved deltas",  pack_name);
+               if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
+                       continue;
+               sorted_by_pos[n++] = &deltas[i];
+       }
+       qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+
+       for (i = 0; i < n; i++) {
+               struct delta_entry *d = sorted_by_pos[i];
+               void *data;
+               unsigned long size;
+               char type[10];
+               enum object_type obj_type;
+               int j, first, last;
+
+               if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
+                       continue;
+               data = read_sha1_file(d->base.sha1, type, &size);
+               if (!data)
+                       continue;
+               if      (!strcmp(type, blob_type))   obj_type = OBJ_BLOB;
+               else if (!strcmp(type, tree_type))   obj_type = OBJ_TREE;
+               else if (!strcmp(type, commit_type)) obj_type = OBJ_COMMIT;
+               else if (!strcmp(type, tag_type))    obj_type = OBJ_TAG;
+               else die("base object %s is of type '%s'",
+                        sha1_to_hex(d->base.sha1), type);
+
+               find_delta_children(&d->base, &first, &last);
+               for (j = first; j <= last; j++) {
+                       struct object_entry *child = objects + deltas[j].obj_no;
+                       if (child->real_type == OBJ_REF_DELTA)
+                               resolve_delta(child, data, size, obj_type);
+               }
+
+               append_obj_to_pack(data, size, obj_type);
+               free(data);
+               if (verbose)
+                       percent = display_progress(nr_resolved_deltas,
+                                                  nr_deltas, percent);
        }
+       free(sorted_by_pos);
+       if (verbose)
+               fputc('\n', stderr);
+}
+
+static void readjust_pack_header_and_sha1(unsigned char *sha1)
+{
+       struct pack_header hdr;
+       SHA_CTX ctx;
+       int size;
+
+       /* Rewrite pack header with updated object number */
+       if (lseek(output_fd, 0, SEEK_SET) != 0)
+               die("cannot seek back: %s", strerror(errno));
+       if (xread(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+               die("cannot read pack header back: %s", strerror(errno));
+       hdr.hdr_entries = htonl(nr_objects);
+       if (lseek(output_fd, 0, SEEK_SET) != 0)
+               die("cannot seek back: %s", strerror(errno));
+       write_or_die(output_fd, &hdr, sizeof(hdr));
+       if (lseek(output_fd, 0, SEEK_SET) != 0)
+               die("cannot seek back: %s", strerror(errno));
+
+       /* Recompute and store the new pack's SHA1 */
+       SHA1_Init(&ctx);
+       do {
+               unsigned char *buf[4096];
+               size = xread(output_fd, buf, sizeof(buf));
+               if (size < 0)
+                       die("cannot read pack data back: %s", strerror(errno));
+               SHA1_Update(&ctx, buf, size);
+       } while (size > 0);
+       SHA1_Final(sha1, &ctx);
+       write_or_die(output_fd, sha1, 20);
 }
 
 static int sha1_compare(const void *_a, const void *_b)
@@ -353,12 +671,16 @@ static int sha1_compare(const void *_a, const void *_b)
        return hashcmp(a->sha1, b->sha1);
 }
 
-static void write_index_file(const char *index_name, unsigned char *sha1)
+/*
+ * On entry *sha1 contains the pack content SHA1 hash, on exit it is
+ * the SHA1 hash of sorted object names.
+ */
+static const char *write_index_file(const char *index_name, unsigned char *sha1)
 {
        struct sha1file *f;
        struct object_entry **sorted_by_sha, **list, **last;
        unsigned int array[256];
-       int i;
+       int i, fd;
        SHA_CTX ctx;
 
        if (nr_objects) {
@@ -375,8 +697,19 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
        else
                sorted_by_sha = list = last = NULL;
 
-       unlink(index_name);
-       f = sha1create("%s", index_name);
+       if (!index_name) {
+               static char tmpfile[PATH_MAX];
+               snprintf(tmpfile, sizeof(tmpfile),
+                        "%s/index_XXXXXX", get_object_directory());
+               fd = mkstemp(tmpfile);
+               index_name = xstrdup(tmpfile);
+       } else {
+               unlink(index_name);
+               fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+       }
+       if (fd < 0)
+               die("unable to create %s: %s", index_name, strerror(errno));
+       f = sha1fd(fd, index_name);
 
        /*
         * Write the first-level table (the list is sorted,
@@ -412,24 +745,132 @@ 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);
+       return index_name;
+}
+
+static void final(const char *final_pack_name, const char *curr_pack_name,
+                 const char *final_index_name, const char *curr_index_name,
+                 const char *keep_name, const char *keep_msg,
+                 unsigned char *sha1)
+{
+       char *report = "pack";
+       char name[PATH_MAX];
+       int err;
+
+       if (!from_stdin) {
+               close(input_fd);
+       } else {
+               err = close(output_fd);
+               if (err)
+                       die("error while closing pack file: %s", strerror(errno));
+               chmod(curr_pack_name, 0444);
+       }
+
+       if (keep_msg) {
+               int keep_fd, keep_msg_len = strlen(keep_msg);
+               if (!keep_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       keep_name = name;
+               }
+               keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+               if (keep_fd < 0) {
+                       if (errno != EEXIST)
+                               die("cannot write keep file");
+               } else {
+                       if (keep_msg_len > 0) {
+                               write_or_die(keep_fd, keep_msg, keep_msg_len);
+                               write_or_die(keep_fd, "\n", 1);
+                       }
+                       close(keep_fd);
+                       report = "keep";
+               }
+       }
+
+       if (final_pack_name != curr_pack_name) {
+               if (!final_pack_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       final_pack_name = name;
+               }
+               if (move_temp_to_file(curr_pack_name, final_pack_name))
+                       die("cannot store pack file");
+       }
+
+       chmod(curr_index_name, 0444);
+       if (final_index_name != curr_index_name) {
+               if (!final_index_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       final_index_name = name;
+               }
+               if (move_temp_to_file(curr_index_name, final_index_name))
+                       die("cannot store index file");
+       }
+
+       if (!from_stdin) {
+               printf("%s\n", sha1_to_hex(sha1));
+       } else {
+               char buf[48];
+               int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+                                  report, sha1_to_hex(sha1));
+               xwrite(1, buf, len);
+
+               /*
+                * Let's just mimic git-unpack-objects here and write
+                * the last part of the input buffer to stdout.
+                */
+               while (input_len) {
+                       err = xwrite(1, input_buffer + input_offset, input_len);
+                       if (err <= 0)
+                               break;
+                       input_len -= err;
+                       input_offset += err;
+               }
+       }
 }
 
 int main(int argc, char **argv)
 {
-       int i;
-       char *index_name = NULL;
-       char *index_name_buf = NULL;
+       int i, fix_thin_pack = 0;
+       const char *curr_pack, *pack_name = NULL;
+       const char *curr_index, *index_name = NULL;
+       const char *keep_name = NULL, *keep_msg = NULL;
+       char *index_name_buf = NULL, *keep_name_buf = NULL;
        unsigned char sha1[20];
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
                if (*arg == '-') {
-                       if (!strcmp(arg, "-o")) {
+                       if (!strcmp(arg, "--stdin")) {
+                               from_stdin = 1;
+                       } else if (!strcmp(arg, "--fix-thin")) {
+                               fix_thin_pack = 1;
+                       } else if (!strcmp(arg, "--keep")) {
+                               keep_msg = "";
+                       } else if (!strncmp(arg, "--keep=", 7)) {
+                               keep_msg = arg + 7;
+                       } else if (!strncmp(arg, "--pack_header=", 14)) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)input_buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               input_len = sizeof(*hdr);
+                       } else if (!strcmp(arg, "-v")) {
+                               verbose = 1;
+                       } else if (!strcmp(arg, "-o")) {
                                if (index_name || (i+1) >= argc)
                                        usage(index_pack_usage);
                                index_name = argv[++i];
@@ -443,9 +884,11 @@ int main(int argc, char **argv)
                pack_name = arg;
        }
 
-       if (!pack_name)
+       if (!pack_name && !from_stdin)
                usage(index_pack_usage);
-       if (!index_name) {
+       if (fix_thin_pack && !from_stdin)
+               die("--fix-thin cannot be used without --stdin");
+       if (!index_name && pack_name) {
                int len = strlen(pack_name);
                if (!has_extension(pack_name, ".pack"))
                        die("packfile name '%s' does not end with '.pack'",
@@ -455,18 +898,55 @@ int main(int argc, char **argv)
                strcpy(index_name_buf + len - 5, ".idx");
                index_name = index_name_buf;
        }
+       if (keep_msg && !keep_name && pack_name) {
+               int len = strlen(pack_name);
+               if (!has_extension(pack_name, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               keep_name_buf = xmalloc(len);
+               memcpy(keep_name_buf, pack_name, len - 5);
+               strcpy(keep_name_buf + len - 5, ".keep");
+               keep_name = keep_name_buf;
+       }
 
-       open_pack_file();
+       curr_pack = open_pack_file(pack_name);
        parse_pack_header();
-       objects = xcalloc(nr_objects, sizeof(struct object_entry));
-       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
-       parse_pack_objects();
+       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
+       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+       if (verbose)
+               setup_progress_signal();
+       parse_pack_objects(sha1);
+       if (nr_deltas != nr_resolved_deltas) {
+               if (fix_thin_pack) {
+                       int nr_unresolved = nr_deltas - nr_resolved_deltas;
+                       int nr_objects_initial = nr_objects;
+                       if (nr_unresolved <= 0)
+                               die("confusion beyond insanity");
+                       objects = xrealloc(objects,
+                                          (nr_objects + nr_unresolved + 1)
+                                          * sizeof(*objects));
+                       fix_unresolved_deltas(nr_unresolved);
+                       if (verbose)
+                               fprintf(stderr, "%d objects were added to complete this thin pack.\n",
+                                       nr_objects - nr_objects_initial);
+                       readjust_pack_header_and_sha1(sha1);
+               }
+               if (nr_deltas != nr_resolved_deltas)
+                       die("pack has %d unresolved deltas",
+                           nr_deltas - nr_resolved_deltas);
+       } else {
+               /* Flush remaining pack final 20-byte SHA1. */
+               flush();
+       }
        free(deltas);
-       write_index_file(index_name, sha1);
+       curr_index = write_index_file(index_name, sha1);
+       final(pack_name, curr_pack,
+               index_name, curr_index,
+               keep_name, keep_msg,
+               sha1);
        free(objects);
        free(index_name_buf);
-
-       printf("%s\n", sha1_to_hex(sha1));
+       free(keep_name_buf);
 
        return 0;
 }
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 2ba43ae84b20f993ba175f728297cf5360066872..cd2cc77bf47025a9dfc88f594961521c3ec8f22c 100644 (file)
@@ -98,7 +98,7 @@ static void output_commit_title(struct commit *commit)
        if (commit->util)
                printf("virtual %s\n", (char *)commit->util);
        else {
-               printf("%s ", sha1_to_hex(commit->object.sha1));
+               printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
@@ -427,8 +427,9 @@ static struct path_list *get_renames(struct tree *tree,
        return renames;
 }
 
-int update_stages(const char *path, struct diff_filespec *o,
-               struct diff_filespec *a, struct diff_filespec *b, int clear)
+static int update_stages(const char *path, struct diff_filespec *o,
+                        struct diff_filespec *a, struct diff_filespec *b,
+                        int clear)
 {
        int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
        if (clear)
@@ -468,10 +469,10 @@ static int remove_path(const char *name)
        return ret;
 }
 
-int remove_file(int clean, const char *path)
+static int remove_file(int clean, const char *path, int no_wd)
 {
        int update_cache = index_only || clean;
-       int update_working_directory = !index_only;
+       int update_working_directory = !index_only && !no_wd;
 
        if (update_cache) {
                if (!cache_dirty)
@@ -480,8 +481,7 @@ int remove_file(int clean, const char *path)
                if (remove_file_from_cache(path))
                        return -1;
        }
-       if (update_working_directory)
-       {
+       if (update_working_directory) {
                unlink(path);
                if (errno != ENOENT || errno != EISDIR)
                        return -1;
@@ -537,11 +537,11 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
-void update_file_flags(const unsigned char *sha,
-                      unsigned mode,
-                      const char *path,
-                      int update_cache,
-                      int update_wd)
+static void update_file_flags(const unsigned char *sha,
+                             unsigned mode,
+                             const char *path,
+                             int update_cache,
+                             int update_wd)
 {
        if (index_only)
                update_wd = 0;
@@ -586,10 +586,10 @@ void update_file_flags(const unsigned char *sha,
                add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 }
 
-void update_file(int clean,
-               const unsigned char *sha,
-               unsigned mode,
-               const char *path)
+static void update_file(int clean,
+                       const unsigned char *sha,
+                       unsigned mode,
+                       const char *path)
 {
        update_file_flags(sha, mode, path, index_only || clean, !index_only);
 }
@@ -724,13 +724,13 @@ static void conflict_rename_rename(struct rename *ren1,
                dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
                output("%s is a directory in %s adding as %s instead",
                       ren1_dst, branch2, dst_name1);
-               remove_file(0, ren1_dst);
+               remove_file(0, ren1_dst, 0);
        }
        if (path_list_has_path(&current_directory_set, ren2_dst)) {
                dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
                output("%s is a directory in %s adding as %s instead",
                       ren2_dst, branch1, dst_name2);
-               remove_file(0, ren2_dst);
+               remove_file(0, ren2_dst, 0);
        }
        update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
        update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
@@ -743,7 +743,7 @@ static void conflict_rename_dir(struct rename *ren1,
 {
        char *new_path = unique_path(ren1->pair->two->path, branch1);
        output("Renaming %s to %s instead", ren1->pair->one->path, new_path);
-       remove_file(0, ren1->pair->two->path);
+       remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
        free(new_path);
 }
@@ -758,7 +758,7 @@ static void conflict_rename_rename_2(struct rename *ren1,
        output("Renaming %s to %s and %s to %s instead",
               ren1->pair->one->path, new_path1,
               ren2->pair->one->path, new_path2);
-       remove_file(0, ren1->pair->two->path);
+       remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
        update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
        free(new_path2);
@@ -856,7 +856,7 @@ static int process_renames(struct path_list *a_renames,
                                conflict_rename_rename(ren1, branch1, ren2, branch2);
                        } else {
                                struct merge_file_info mfi;
-                               remove_file(1, ren1_src);
+                               remove_file(1, ren1_src, 1);
                                mfi = merge_file(ren1->pair->one,
                                                 ren1->pair->two,
                                                 ren2->pair->two,
@@ -889,7 +889,7 @@ static int process_renames(struct path_list *a_renames,
                        struct diff_filespec src_other, dst_other;
                        int try_merge, stage = a_renames == renames1 ? 3: 2;
 
-                       remove_file(1, ren1_src);
+                       remove_file(1, ren1_src, 1);
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
                        src_other.mode = ren1->src_entry->stages[stage].mode;
@@ -1007,7 +1007,8 @@ static int process_entry(const char *path, struct stage_data *entry,
                         * unchanged in the other */
                        if (a_sha)
                                output("Removing %s", path);
-                       remove_file(1, path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(1, path, !a_sha);
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
@@ -1054,7 +1055,7 @@ static int process_entry(const char *path, struct stage_data *entry,
                        output("CONFLICT (%s): There is a directory with name %s in %s. "
                               "Adding %s as %s",
                               conf, path, other_branch, path, new_path);
-                       remove_file(0, path);
+                       remove_file(0, path, 0);
                        update_file(0, sha, mode, new_path);
                } else {
                        output("Adding %s", path);
@@ -1082,7 +1083,7 @@ static int process_entry(const char *path, struct stage_data *entry,
                        output("CONFLICT (add/add): File %s added non-identically "
                               "in both branches. Adding as %s and %s instead.",
                               path, new_path1, new_path2);
-                       remove_file(0, path);
+                       remove_file(0, path, 0);
                        update_file(0, a_sha, a_mode, new_path1);
                        update_file(0, b_sha, b_mode, new_path2);
                }
@@ -1204,14 +1205,13 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)
  * Merge the commits h1 and h2, return the resulting virtual
  * commit object and a flag indicating the cleaness of the merge.
  */
-static
-int merge(struct commit *h1,
-                         struct commit *h2,
-                         const char *branch1,
-                         const char *branch2,
-                         int call_depth /* =0 */,
-                         struct commit *ancestor /* =None */,
-                         struct commit **result)
+static int merge(struct commit *h1,
+                struct commit *h2,
+                const char *branch1,
+                const char *branch2,
+                int call_depth /* =0 */,
+                struct commit *ancestor /* =None */,
+                struct commit **result)
 {
        struct commit_list *ca = NULL, *iter;
        struct commit *merged_common_ancestors;
@@ -1308,6 +1308,7 @@ int main(int argc, char *argv[])
        const char *branch1, *branch2;
        struct commit *result, *h1, *h2;
 
+       git_config(git_default_config); /* core.filemode */
        original_index_file = getenv("GIT_INDEX_FILE");
 
        if (!original_index_file)
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/pager.c b/pager.c
index dcb398da8e703de2999badb976dee7322eff470b..4587fbbdb51e50c1e3e6e4c3476cdccd4ac59cee 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -50,7 +50,7 @@ void setup_pager(void)
        close(fd[0]);
        close(fd[1]);
 
-       setenv("LESS", "-RS", 0);
+       setenv("LESS", "FRSX", 0);
        run_pager(pager);
        die("unable to execute pager '%s'", pager);
        exit(255);
diff --git a/path.c b/path.c
index bb89fb02dc9a8a1a09492fb32d8708f952afe47e..d2c076d7cbad3a16a002897d926cc13633be4f77 100644 (file)
--- a/path.c
+++ b/path.c
@@ -279,7 +279,7 @@ int adjust_shared_perm(const char *path)
                            : 0));
        if (S_ISDIR(mode))
                mode |= S_ISGID;
-       if (chmod(path, mode) < 0)
+       if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
                return -2;
        return 0;
 }
diff --git a/quote.c b/quote.c
index e3a4d4aef3478148ff89aebeba23e1cbf4202ba1..a418a0f803f91d218b66ae89a1e8615a94ff27bc 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -209,7 +209,7 @@ static int quote_c_style_counted(const char *name, int namelen,
                if (!ch)
                        break;
                if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
-                   (ch == 0177)) {
+                   (ch >= 0177)) {
                        needquote = 1;
                        switch (ch) {
                        case '\a': EMITQ(); ch = 'a'; break;
@@ -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..d56898c9b29d3e5ea01fd5a211790141d0beac21 100644 (file)
@@ -1,20 +1,42 @@
 #include "cache.h"
+#include "pack.h"
 #include "refs.h"
 #include "pkt-line.h"
 #include "run-command.h"
+#include "exec_cmd.h"
 #include "commit.h"
 #include "object.h"
+#include <sys/wait.h>
 
 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 unpack_limit = 5000;
 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;
+       }
+
+       if (strcmp(var, "receive.unpacklimit") == 0)
+       {
+               unpack_limit = git_config_int(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 +49,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 +65,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 +101,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 +111,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 +133,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";
@@ -273,29 +235,127 @@ static void read_head_info(void)
        }
 }
 
-static const char *unpack(int *error_code)
+static const char *parse_pack_header(struct pack_header *hdr)
 {
-       int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+       char *c = (char*)hdr;
+       ssize_t remaining = sizeof(struct pack_header);
+       do {
+               ssize_t r = xread(0, c, remaining);
+               if (r <= 0)
+                       return "eof before pack header was fully read";
+               remaining -= r;
+               c += r;
+       } while (remaining > 0);
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               return "protocol error (pack signature mismatch detected)";
+       if (!pack_version_ok(hdr->hdr_version))
+               return "protocol error (pack version unsupported)";
+       return NULL;
+}
 
-       *error_code = 0;
-       switch (code) {
-       case 0:
-               return NULL;
-       case -ERR_RUN_COMMAND_FORK:
-               return "unpack fork failed";
-       case -ERR_RUN_COMMAND_EXEC:
-               return "unpack execute failed";
-       case -ERR_RUN_COMMAND_WAITPID:
-               return "waitpid failed";
-       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-               return "waitpid is confused";
-       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-               return "unpacker died of signal";
-       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-               return "unpacker died strangely";
-       default:
-               *error_code = -code;
-               return "unpacker exited with error code";
+static const char *pack_lockfile;
+
+static const char *unpack(void)
+{
+       struct pack_header hdr;
+       const char *hdr_err;
+       char hdr_arg[38];
+
+       hdr_err = parse_pack_header(&hdr);
+       if (hdr_err)
+               return hdr_err;
+       snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u",
+                       ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+
+       if (ntohl(hdr.hdr_entries) < unpack_limit) {
+               int code;
+               const char *unpacker[3];
+               unpacker[0] = "unpack-objects";
+               unpacker[1] = hdr_arg;
+               unpacker[2] = NULL;
+               code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+               switch (code) {
+               case 0:
+                       return NULL;
+               case -ERR_RUN_COMMAND_FORK:
+                       return "unpack fork failed";
+               case -ERR_RUN_COMMAND_EXEC:
+                       return "unpack execute failed";
+               case -ERR_RUN_COMMAND_WAITPID:
+                       return "waitpid failed";
+               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+                       return "waitpid is confused";
+               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+                       return "unpacker died of signal";
+               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+                       return "unpacker died strangely";
+               default:
+                       return "unpacker exited with error code";
+               }
+       } else {
+               const char *keeper[6];
+               int fd[2], s, len, status;
+               pid_t pid;
+               char keep_arg[256];
+               char packname[46];
+
+               s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
+               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+                       strcpy(keep_arg + s, "localhost");
+
+               keeper[0] = "index-pack";
+               keeper[1] = "--stdin";
+               keeper[2] = "--fix-thin";
+               keeper[3] = hdr_arg;
+               keeper[4] = keep_arg;
+               keeper[5] = NULL;
+
+               if (pipe(fd) < 0)
+                       return "index-pack pipe failed";
+               pid = fork();
+               if (pid < 0)
+                       return "index-pack fork failed";
+               if (!pid) {
+                       dup2(fd[1], 1);
+                       close(fd[1]);
+                       close(fd[0]);
+                       execv_git_cmd(keeper);
+                       die("execv of index-pack failed");
+               }
+               close(fd[1]);
+
+               /*
+                * The first thing we expects from index-pack's output
+                * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
+                * %40s is the newly created pack SHA1 name.  In the "keep"
+                * case, we need it to remove the corresponding .keep file
+                * later on.  If we don't get that then tough luck with it.
+                */
+               for (len = 0;
+                    len < 46 && (s = xread(fd[0], packname+len, 46-len)) > 0;
+                    len += s);
+               close(fd[0]);
+               if (len == 46 && packname[45] == '\n' &&
+                   memcmp(packname, "keep\t", 5) == 0) {
+                       char path[PATH_MAX];
+                       packname[45] = 0;
+                       snprintf(path, sizeof(path), "%s/pack/pack-%s.keep",
+                                get_object_directory(), packname + 5);
+                       pack_lockfile = xstrdup(path);
+               }
+
+               /* Then wrap our index-pack process. */
+               while (waitpid(pid, &status, 0) < 0)
+                       if (errno != EINTR)
+                               return "waitpid failed";
+               if (WIFEXITED(status)) {
+                       int code = WEXITSTATUS(status);
+                       if (code)
+                               return "index-pack exited with error code";
+                       reprepare_packed_git();
+                       return NULL;
+               }
+               return "index-pack abnormal exit";
        }
 }
 
@@ -335,9 +395,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 */
@@ -345,10 +408,11 @@ int main(int argc, char **argv)
 
        read_head_info();
        if (commands) {
-               int code;
-               const char *unpack_status = unpack(&code);
+               const char *unpack_status = unpack();
                if (!unpack_status)
                        execute_commands();
+               if (pack_lockfile)
+                       unlink(pack_lockfile);
                if (report_status)
                        report(unpack_status);
        }
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 b021d3354e8b0f2f1efb6dbda0aa1289914eb112..993bb668a205a9fb3e2f5ddd2f378f10ac7905b4 100644 (file)
@@ -418,9 +418,6 @@ static void limit_list(struct rev_info *revs)
 
                if (revs->max_age != -1 && (commit->date < revs->max_age))
                        obj->flags |= UNINTERESTING;
-               if (revs->unpacked &&
-                   has_sha1_pack(obj->sha1, revs->ignore_packed))
-                       obj->flags |= UNINTERESTING;
                add_parents_to_list(revs, commit, &list);
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
@@ -468,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, "");
@@ -479,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)
@@ -1022,7 +1019,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                add_pending_object(revs, object, def);
        }
 
-       if (revs->topo_order || revs->unpacked)
+       if (revs->topo_order)
                revs->limited = 1;
 
        if (revs->prune_data) {
@@ -1156,17 +1153,18 @@ struct commit *get_revision(struct rev_info *revs)
                 * that we'd otherwise have done in limit_list().
                 */
                if (!revs->limited) {
-                       if ((revs->unpacked &&
-                            has_sha1_pack(commit->object.sha1,
-                                          revs->ignore_packed)) ||
-                           (revs->max_age != -1 &&
-                            (commit->date < revs->max_age)))
+                       if (revs->max_age != -1 &&
+                           (commit->date < revs->max_age))
                                continue;
                        add_parents_to_list(revs, commit, &revs->commits);
                }
                if (commit->object.flags & SHOWN)
                        continue;
 
+               if (revs->unpacked && has_sha1_pack(commit->object.sha1,
+                                                   revs->ignore_packed))
+                   continue;
+
                /* We want to show boundary commits only when their
                 * children are shown.  When path-limiter is in effect,
                 * rewrite_parents() drops some commits from getting shown,
index 5bb123a37696384c5413dac128529d1c1f679940..447666665bc43090849a66681edec663fe75000c 100644 (file)
@@ -29,6 +29,7 @@ static void exec_pack_objects(void)
 {
        static const char *args[] = {
                "pack-objects",
+               "--all-progress",
                "--stdout",
                NULL
        };
@@ -215,7 +216,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 +231,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 47e2a29abd6d2ae067d72c0f73bcf4e6afe0a938..6ea59b55887480fb0ebe3c6116fe9f68821d4527 100644 (file)
@@ -663,7 +663,7 @@ void prepare_packed_git(void)
        prepare_packed_git_run_once = 1;
 }
 
-static void reprepare_packed_git(void)
+void reprepare_packed_git(void)
 {
        prepare_packed_git_run_once = 0;
        prepare_packed_git();
@@ -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:
@@ -1186,6 +1203,24 @@ unsigned long find_pack_entry_one(const unsigned char *sha1,
        return 0;
 }
 
+static int matches_pack_name(struct packed_git *p, const char *ig)
+{
+       const char *last_c, *c;
+
+       if (!strcmp(p->pack_name, ig))
+               return 0;
+
+       for (c = p->pack_name, last_c = c; *c;)
+               if (*c == '/')
+                       last_c = ++c;
+               else
+                       ++c;
+       if (!strcmp(last_c, ig))
+               return 0;
+
+       return 1;
+}
+
 static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
 {
        struct packed_git *p;
@@ -1197,7 +1232,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons
                if (ignore_packed) {
                        const char **ig;
                        for (ig = ignore_packed; *ig; ig++)
-                               if (!strcmp(p->pack_name, *ig))
+                               if (!matches_pack_name(p, *ig))
                                        break;
                        if (*ig)
                                continue;
@@ -1382,9 +1417,10 @@ static int link_temp_to_file(const char *tmpfile, const char *filename)
        dir = strrchr(filename, '/');
        if (dir) {
                *dir = 0;
-               mkdir(filename, 0777);
-               if (adjust_shared_perm(filename))
+               if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) {
+                       *dir = '/';
                        return -2;
+               }
                *dir = '/';
                if (!link(tmpfile, filename))
                        return 0;
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 c21d660b628a5b6d67b0f56b41fc76a921df340f..a30a2de5d13931c590169cf30bd7004e23f2df1b 100644 (file)
@@ -8,7 +8,7 @@ int main(int argc, char **argv)
        static unsigned int top_index[256];
 
        if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
-               die("unable to read idex");
+               die("unable to read index");
        nr = 0;
        for (i = 0; i < 256; i++) {
                unsigned n = ntohl(top_index[i]);
diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh
new file mode 100755 (executable)
index 0000000..018fbea
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='read-tree -m -u checks working tree files'
+
+. ./test-lib.sh
+
+# two-tree test
+
+test_expect_success 'two-way setup' '
+
+       echo >file1 file one &&
+       echo >file2 file two &&
+       git update-index --add file1 file2 &&
+       git commit -m initial &&
+
+       git branch side &&
+       git tag -f branch-point &&
+
+       echo file2 is not tracked on the master anymore &&
+       rm -f file2 &&
+       git update-index --remove file2 &&
+       git commit -a -m "master removes file2"
+'
+
+test_expect_success 'two-way not clobbering' '
+
+       echo >file2 master creates untracked file2 &&
+       if err=`git read-tree -m -u master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+# three-tree test
+
+test_expect_success 'three-way not complaining' '
+
+       rm -f file2 &&
+       git checkout side &&
+       echo >file3 file three &&
+       git update-index --add file3 &&
+       git commit -a -m "side adds file3" &&
+
+       git checkout master &&
+       echo >file2 file two is untracked on the master side &&
+
+       git-read-tree -m -u branch-point master side
+'
+
+test_done
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 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..b608e202c18fd545868898fcd0528c3501459b6f 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 &&
@@ -48,15 +42,20 @@ O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
 EOF
 
 git add A M &&
-git commit -m initial &&
+git commit -m "initial has A and M" &&
 git branch white &&
 git branch red &&
 git branch blue &&
+git branch yellow &&
 
 sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
 mv A+ A &&
 git commit -a -m "master updates A" &&
 
+git checkout yellow &&
+rm -f M &&
+git commit -a -m "yellow removes M" &&
+
 git checkout white &&
 sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
 sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
@@ -85,27 +84,27 @@ test_expect_success 'pull renaming branch into unrenaming one' \
        git show-branch
        git pull . white && {
                echo "BAD: should have conflicted"
-               exit 1
+               return 1
        }
        git ls-files -s
        test "$(git ls-files -u B | wc -l)" -eq 3 || {
                echo "BAD: should have left stages for B"
-               exit 1  
+               return 1
        }
        test "$(git ls-files -s N | wc -l)" -eq 1 || {
                echo "BAD: should have merged N"
-               exit 1  
+               return 1
        }
        sed -ne "/^g/{
        p
        q
        }" B | grep master || {
                echo "BAD: should have listed our change first"
-               exit 1
+               return 1
        }
        test "$(git diff white N | wc -l)" -eq 0 || {
                echo "BAD: should have taken colored branch"
-               exit 1
+               return 1
        }
 '
 
@@ -116,26 +115,26 @@ test_expect_success 'pull renaming branch into another renaming one' \
        git checkout red
        git pull . white && {
                echo "BAD: should have conflicted"
-               exit 1
+               return 1
        }
        test "$(git ls-files -u B | wc -l)" -eq 3 || {
                echo "BAD: should have left stages"
-               exit 1  
+               return 1
        }
        test "$(git ls-files -s N | wc -l)" -eq 1 || {
                echo "BAD: should have merged N"
-               exit 1  
+               return 1
        }
        sed -ne "/^g/{
        p
        q
        }" B | grep red || {
                echo "BAD: should have listed our change first"
-               exit 1
+               return 1
        }
        test "$(git diff white N | wc -l)" -eq 0 || {
                echo "BAD: should have taken colored branch"
-               exit 1
+               return 1
        }
 '
 
@@ -145,26 +144,26 @@ test_expect_success 'pull unrenaming branch into renaming one' \
        git show-branch
        git pull . master && {
                echo "BAD: should have conflicted"
-               exit 1
+               return 1
        }
        test "$(git ls-files -u B | wc -l)" -eq 3 || {
                echo "BAD: should have left stages"
-               exit 1  
+               return 1
        }
        test "$(git ls-files -s N | wc -l)" -eq 1 || {
                echo "BAD: should have merged N"
-               exit 1  
+               return 1
        }
        sed -ne "/^g/{
        p
        q
        }" B | grep red || {
                echo "BAD: should have listed our change first"
-               exit 1
+               return 1
        }
        test "$(git diff white N | wc -l)" -eq 0 || {
                echo "BAD: should have taken colored branch"
-               exit 1
+               return 1
        }
 '
 
@@ -174,35 +173,149 @@ test_expect_success 'pull conflicting renames' \
        git show-branch
        git pull . blue && {
                echo "BAD: should have conflicted"
-               exit 1
+               return 1
        }
        test "$(git ls-files -u A | wc -l)" -eq 1 || {
                echo "BAD: should have left a stage"
-               exit 1  
+               return 1
        }
        test "$(git ls-files -u B | wc -l)" -eq 1 || {
                echo "BAD: should have left a stage"
-               exit 1  
+               return 1
        }
        test "$(git ls-files -u C | wc -l)" -eq 1 || {
                echo "BAD: should have left a stage"
-               exit 1  
+               return 1
        }
        test "$(git ls-files -s N | wc -l)" -eq 1 || {
                echo "BAD: should have merged N"
-               exit 1  
+               return 1
        }
        sed -ne "/^g/{
        p
        q
        }" B | grep red || {
                echo "BAD: should have listed our change first"
-               exit 1
+               return 1
        }
        test "$(git diff white N | wc -l)" -eq 0 || {
                echo "BAD: should have taken colored branch"
-               exit 1
+               return 1
+       }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+       git reset --hard
+       git show-branch
+       echo >A this file should not matter
+       git pull . white && {
+               echo "BAD: should have conflicted"
+               return 1
+       }
+       test -f A || {
+               echo "BAD: should have left A intact"
+               return 1
+       }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+       git reset --hard
+       git checkout white
+       git show-branch
+       rm -f A
+       echo >A this file should not matter
+       git pull . red && {
+               echo "BAD: should have conflicted"
+               return 1
+       }
+       test -f A || {
+               echo "BAD: should have left A intact"
+               return 1
+       }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+       git reset --hard
+       rm -f A M
+       git checkout -f master
+       git tag -f anchor
+       git show-branch
+       git pull . yellow || {
+               echo "BAD: should have cleanly merged"
+               return 1
+       }
+       test -f M && {
+               echo "BAD: should have removed M"
+               return 1
+       }
+       git reset --hard anchor
+'
+
+test_expect_success 'updated working tree file should prevent the merge' '
+
+       git reset --hard
+       rm -f A M
+       git checkout -f master
+       git tag -f anchor
+       git show-branch
+       echo >>M one line addition
+       cat M >M.saved
+       git pull . yellow && {
+               echo "BAD: should have complained"
+               return 1
+       }
+       diff M M.saved || {
+               echo "BAD: should have left M intact"
+               return 1
+       }
+       rm -f M.saved
+'
+
+test_expect_success 'updated working tree file should prevent the merge' '
+
+       git reset --hard
+       rm -f A M
+       git checkout -f master
+       git tag -f anchor
+       git show-branch
+       echo >>M one line addition
+       cat M >M.saved
+       git update-index M
+       git pull . yellow && {
+               echo "BAD: should have complained"
+               return 1
+       }
+       diff M M.saved || {
+               echo "BAD: should have left M intact"
+               return 1
+       }
+       rm -f M.saved
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+       git reset --hard
+       rm -f A M
+       git checkout -f yellow
+       git tag -f anchor
+       git show-branch
+       echo >M this file should not matter
+       git pull . master || {
+               echo "BAD: should have cleanly merged"
+               return 1
+       }
+       test -f M || {
+               echo "BAD: should have left M intact"
+               return 1
+       }
+       git ls-files -s | grep M && {
+               echo "BAD: M must be untracked in the result"
+               return 1
        }
+       git reset --hard anchor
 '
 
 test_done
index 2488e6eae1f515baa3adcaceca75000d15976612..07cb706fa8a241bf707b91d033ea0c5c876d9307 100755 (executable)
@@ -207,7 +207,8 @@ test_done () {
 # t/ subdirectory and are run in trash subdirectory.
 PATH=$(pwd)/..:$PATH
 GIT_EXEC_PATH=$(pwd)/..
-export PATH GIT_EXEC_PATH
+HOME=$(pwd)/trash
+export PATH GIT_EXEC_PATH HOME
 
 # Similarly use ../compat/subprocess.py if our python does not
 # have subprocess.py on its own.
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 3ac0289b3a3309fca9ade4e271dbd8b0d2f148ea..7cfd628d8e1c576115cb934e72138535fb7d2db3 100644 (file)
@@ -642,7 +642,7 @@ int threeway_merge(struct cache_entry **stages,
                    (remote_deleted && head && head_match)) {
                        if (index)
                                return deleted_entry(index, index, o);
-                       else if (path)
+                       else if (path && !head_deleted)
                                verify_absent(path, "removed", o);
                        return 0;
                }
@@ -661,8 +661,6 @@ int threeway_merge(struct cache_entry **stages,
        if (index) {
                verify_uptodate(index, o);
        }
-       else if (path)
-               verify_absent(path, "overwritten", o);
 
        o->nontrivial_merge = 1;
 
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..9692dfa325b6153571c83d0f25dd0281d8795b43 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);
        }
@@ -156,10 +154,8 @@ void wt_status_print_initial(struct wt_status *s)
 static void wt_status_print_updated(struct wt_status *s)
 {
        struct rev_info rev;
-       const char *argv[] = { NULL, NULL, NULL };
-       argv[1] = s->reference;
        init_revisions(&rev, NULL);
-       setup_revisions(2, argv, &rev, NULL);
+       setup_revisions(0, NULL, &rev, s->reference);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_print_updated_cb;
        rev.diffopt.format_callback_data = s;
@@ -170,9 +166,8 @@ static void wt_status_print_updated(struct wt_status *s)
 static void wt_status_print_changed(struct wt_status *s)
 {
        struct rev_info rev;
-       const char *argv[] = { NULL, NULL };
        init_revisions(&rev, "");
-       setup_revisions(1, argv, &rev, NULL);
+       setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_print_changed_cb;
        rev.diffopt.format_callback_data = s;
@@ -227,10 +222,8 @@ static void wt_status_print_untracked(const struct wt_status *s)
 static void wt_status_print_verbose(struct wt_status *s)
 {
        struct rev_info rev;
-       const char *argv[] = { NULL, NULL, NULL };
-       argv[1] = s->reference;
        init_revisions(&rev, NULL);
-       setup_revisions(2, argv, &rev, NULL);
+       setup_revisions(0, NULL, &rev, s->reference);
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
        rev.diffopt.detect_rename = 1;
        run_diff_index(&rev, 1);
index 714c563547462d962b4a6b69b524092e1bc2a036..07995ec33e9079cbbb579947e55fe46c1521d28f 100644 (file)
@@ -86,11 +86,10 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
                if (len > 0 &&
                    (isalpha((unsigned char)*rec) || /* identifier? */
                     *rec == '_' ||     /* also identifier? */
-                    *rec == '(' ||     /* lisp defun? */
-                    *rec == '#')) {    /* #define? */
+                    *rec == '$')) {    /* mysterious GNU diff's invention */
                        if (len > sz)
                                len = sz;
-                       if (len && rec[len - 1] == '\n')
+                       while (0 < len && isspace((unsigned char)rec[len - 1]))
                                len--;
                        memcpy(buf, rec, len);
                        *ll = len;