Merge branch 'jc/read-tree'
authorJunio C Hamano <junkio@cox.net>
Tue, 7 Nov 2006 23:41:34 +0000 (15:41 -0800)
committerJunio C Hamano <junkio@cox.net>
Tue, 7 Nov 2006 23:41:34 +0000 (15:41 -0800)
* jc/read-tree:
t6022: ignoring untracked files by merge-recursive when they do not matter
merge-recursive: adjust to loosened "working file clobbered" check
merge-recursive: make a few functions static.
merge-recursive: use abbreviated commit object name.
merge: loosen overcautious "working file will be lost" check.

88 files changed:
.gitignore
Documentation/config.txt
Documentation/git-clone.txt
Documentation/git-diff-index.txt
Documentation/git-fetch-pack.txt
Documentation/git-for-each-ref.txt
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-repo-config.txt
Documentation/git-send-email.txt
Documentation/git-show-ref.txt [new file with mode: 0644]
Documentation/git-update-ref.txt
Documentation/git.txt
Makefile
archive.h
blame.c
builtin-apply.c
builtin-archive.c
builtin-branch.c [new file with mode: 0644]
builtin-fmt-merge-msg.c
builtin-for-each-ref.c
builtin-init-db.c
builtin-log.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-pack-refs.c [new file with mode: 0644]
builtin-prune.c
builtin-push.c
builtin-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]
config.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
describe.c
diff.h
environment.c
fetch-clone.c [deleted file]
fetch-pack.c
fetch.c
fsck-objects.c
git-branch.sh [deleted file]
git-checkout.sh
git-cherry.sh [deleted file]
git-commit.sh
git-fetch.sh
git-repack.sh
git-revert.sh
git-send-email.perl
git-svn.perl
git-svnimport.perl
git-tag.sh
git.c
gitweb/gitweb.perl
http-push.c
imap-send.c
index-pack.c
log-tree.c
merge-recursive.c
path.c
quote.c
receive-pack.c
refs.c
refs.h
revision.c
send-pack.c
server-info.c
setup.c
sha1_file.c
sha1_name.c
t/t1400-update-ref.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh [new file with mode: 0755]
tree-diff.c
upload-pack.c
wt-status.c
index b670877510ca89bad91bed82e7527d26f2fa9248..4c8c8e411500fe2ed0108c46838d50576ed15185 100644 (file)
@@ -75,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
@@ -106,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 026d4cf9ade90cd108d351e372ed862378cde100..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
@@ -297,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 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 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
index d5fdcef8d9e60d061ed83a8388ad1d3d0a47e6a5..2bf6aef73522e3ba7e5212d3eb1bce02a257e069 100644 (file)
@@ -7,14 +7,14 @@ git-for-each-ref - Output information on each ref
 
 SYNOPSIS
 --------
-'git-for-each-ref' [--count=<count>]* [--shell|--perl|--python] [--sort=<key>]* [--format=<format>] [<pattern>]
+'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>`s.  If `<max>` is given, stop after
+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.
@@ -38,7 +38,11 @@ OPTIONS
        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
-       `%(refname)`.
+       `%(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
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 a1e55054bd544848605eec8bfaa2cc29ac94f3d0..5ebe34eebf3d5e36cf67b20d4fd3247508bd8c5a 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
-       [--local] [--incremental] [--window=N] [--depth=N]
+       [--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::
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 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 481b3f50e3d690f5cd47ffa2c2d8bb645114e315..4c8d907bd595a4f31b3f53cfff25b84cea059ff7 100644 (file)
@@ -66,8 +66,13 @@ The options available are:
        all that is output.
 
 --smtp-server::
-       If set, specifies the outgoing SMTP server to use.  Defaults to
-       localhost.
+       If set, specifies the outgoing SMTP server to use.  A full
+       pathname of a sendmail-like program can be specified instead;
+       the program must support the `-i` option.  Default value can
+       be specified by the 'sendemail.smtpserver' configuration
+       option; the built-in default is `/usr/sbin/sendmail` or
+       `/usr/lib/sendmail` if such program is available, or
+       `localhost` otherwise.
 
 --subject::
        Specify the initial subject of the email thread.
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
new file mode 100644 (file)
index 0000000..5973a82
--- /dev/null
@@ -0,0 +1,156 @@
+git-show-ref(1)
+===============
+
+NAME
+----
+git-show-ref - List references in a local repository
+
+SYNOPSIS
+--------
+[verse]
+'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
+            [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>...
+
+DESCRIPTION
+-----------
+
+Displays references available in a local repository along with the associated
+commit IDs. Results can be filtered using a pattern and tags can be
+dereferenced into object IDs. Additionally, it can be used to test whether a
+particular ref exists.
+
+Use of this utility is encouraged in favor of directly accessing files under
+in the `.git` directory.
+
+OPTIONS
+-------
+
+-h, --head::
+
+       Show the HEAD reference.
+
+--tags, --heads::
+
+       Limit to only "refs/heads" and "refs/tags", respectively.  These
+       options are not mutually exclusive; when given both, references stored
+       in "refs/heads" and "refs/tags" are displayed.
+
+-d, --dereference::
+
+       Dereference tags into object IDs as well. They will be shown with "^{}"
+       appended.
+
+-s, --hash::
+
+       Only show the SHA1 hash, not the reference name. When also using
+       --dereference the dereferenced tag will still be shown after the SHA1.
+
+--verify::
+
+       Enable stricter reference checking by requiring an exact ref path.
+       Aside from returning an error code of 1, it will also print an error
+       message if '--quiet' was not specified.
+
+--abbrev, --abbrev=len::
+
+       Abbreviate the object name.  When using `--hash`, you do
+       not have to say `--hash --abbrev`; `--hash=len` would do.
+
+-q, --quiet::
+
+       Do not print any results to stdout. When combined with '--verify' this
+       can be used to silently check if a reference exists.
+
+<pattern>::
+
+       Show references matching one or more patterns.
+
+OUTPUT
+------
+
+The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'.
+
+-----------------------------------------------------------------------------
+$ git show-ref --head --dereference
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin
+3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c
+6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{}
+055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4
+423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{}
+...
+-----------------------------------------------------------------------------
+
+When using --hash (and not --dereference) the output format is: '<SHA-1 ID>'
+
+-----------------------------------------------------------------------------
+$ git show-ref --heads --hash
+2e3ba0114a1f52b47df29743d6915d056be13278
+185008ae97960c8d551adcd9e23565194651b5d1
+03adf42c988195b50e1a1935ba5fcbc39b2b029b
+...
+-----------------------------------------------------------------------------
+
+EXAMPLE
+-------
+
+To show all references called "master", whether tags or heads or anything
+else, and regardless of how deep in the reference naming hierarchy they are,
+use:
+
+-----------------------------------------------------------------------------
+       git show-ref master
+-----------------------------------------------------------------------------
+
+This will show "refs/heads/master" but also "refs/remote/other-repo/master",
+if such references exists.
+
+When using the '--verify' flag, the command requires an exact path:
+
+-----------------------------------------------------------------------------
+       git show-ref --verify refs/heads/master
+-----------------------------------------------------------------------------
+
+will only match the exact branch called "master".
+
+If nothing matches, gitlink:git-show-ref[1] will return an error code of 1,
+and in the case of verification, it will show an error message.
+
+For scripting, you can ask it to be quiet with the "--quiet" flag, which
+allows you to do things like
+
+-----------------------------------------------------------------------------
+       git-show-ref --quiet --verify -- "refs/heads/$headname" ||
+               echo "$headname is not a valid branch"
+-----------------------------------------------------------------------------
+
+to check whether a particular branch exists or not (notice how we don't
+actually want to show any results, and we want to use the full refname for it
+in order to not trigger the problem with ambiguous partial matches).
+
+To show only tags, or only proper branch heads, use "--tags" and/or "--heads"
+respectively (using both means that it shows tags and heads, but not other
+random references under the refs/ subdirectory).
+
+To do automatic tag object dereferencing, use the "-d" or "--dereference"
+flag, so you can do
+
+-----------------------------------------------------------------------------
+       git show-ref --tags --dereference
+-----------------------------------------------------------------------------
+
+to get a listing of all tags together with what they dereference.
+
+SEE ALSO
+--------
+gitlink:git-ls-remote[1], gitlink:git-peek-remote[1]
+
+AUTHORS
+-------
+Written by Linus Torvalds <torvalds@osdl.org>.
+Man page by Jonas Fonseca <fonseca@diku.dk>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index e062030e91ae2b2a25c2ef7bafc4c026015e1f5e..71bcb7954f0c53a995ba56de6c99d9aebc6aa52a 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
 
 DESCRIPTION
 -----------
@@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that
 the current value of the <ref> matches <oldvalue>.
 E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
 updates the master branch head to <newvalue> only if its current
-value is <oldvalue>.
+value is <oldvalue>.  You can specify 40 "0" or an empty string
+as <oldvalue> to make sure that the ref you are creating does
+not exist.
 
 It also allows a "ref" file to be a symbolic pointer to another
 ref file by starting with the four-byte header sequence of
@@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a
 ref symlink to some other tree, if you have copied a whole
 archive by creating a symlink tree).
 
+With `-d` flag, it deletes the named <ref> after verifying it
+still contains <oldvalue>.
+
+
 Logging Updates
 ---------------
 If config parameter "core.logAllRefUpdates" is true or the file
index b00607ee1b836e2cdcb033e55ca18214d91cb3b9..4ce4f8d1c6efd18088c046a682ef66d6ae067bdc 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.
@@ -488,6 +312,191 @@ gitlink:git-stripspace[1]::
        Filter out empty lines.
 
 
+Low-level commands (plumbing)
+-----------------------------
+
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+development of alternative porcelains.  Developers of such porcelains
+might start by reading about gitlink:git-update-index[1] and
+gitlink:git-read-tree[1].
+
+We divide the low-level commands into commands that manipulate objects (in
+the repository, index, and working tree), commands that interrogate and
+compare objects, and commands that move objects and references between
+repositories.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+gitlink:git-apply[1]::
+       Reads a "diff -up1" or git generated patch file and
+       applies it to the working tree.
+
+gitlink:git-checkout-index[1]::
+       Copy files from the index to the working tree.
+
+gitlink:git-commit-tree[1]::
+       Creates a new commit object.
+
+gitlink:git-hash-object[1]::
+       Computes the object ID from a file.
+
+gitlink:git-index-pack[1]::
+       Build pack idx file for an existing packed archive.
+
+gitlink:git-init-db[1]::
+       Creates an empty git object database, or reinitialize an
+       existing one.
+
+gitlink:git-merge-index[1]::
+       Runs a merge for files needing merging.
+
+gitlink:git-mktag[1]::
+       Creates a tag object.
+
+gitlink:git-mktree[1]::
+       Build a tree-object from ls-tree formatted text.
+
+gitlink:git-pack-objects[1]::
+       Creates a packed archive of objects.
+
+gitlink:git-prune-packed[1]::
+       Remove extra objects that are already in pack files.
+
+gitlink:git-read-tree[1]::
+       Reads tree information into the index.
+
+gitlink:git-repo-config[1]::
+       Get and set options in .git/config.
+
+gitlink:git-unpack-objects[1]::
+       Unpacks objects out of a packed archive.
+
+gitlink:git-update-index[1]::
+       Registers files in the working tree to the index.
+
+gitlink:git-write-tree[1]::
+       Creates a tree from the index.
+
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-cat-file[1]::
+       Provide content or type/size information for repository objects.
+
+gitlink:git-describe[1]::
+       Show the most recent tag that is reachable from a commit.
+
+gitlink:git-diff-index[1]::
+       Compares content and mode of blobs between the index and repository.
+
+gitlink:git-diff-files[1]::
+       Compares files in the working tree and the index.
+
+gitlink:git-diff-stages[1]::
+       Compares two "merge stages" in the index.
+
+gitlink:git-diff-tree[1]::
+       Compares the content and mode of blobs found via two tree objects.
+
+gitlink:git-for-each-ref[1]::
+       Output information on each ref.
+
+gitlink:git-fsck-objects[1]::
+       Verifies the connectivity and validity of the objects in the database.
+
+gitlink:git-ls-files[1]::
+       Information about files in the index and the working tree.
+
+gitlink:git-ls-tree[1]::
+       Displays a tree object in human readable form.
+
+gitlink:git-merge-base[1]::
+       Finds as good common ancestors as possible for a merge.
+
+gitlink:git-name-rev[1]::
+       Find symbolic names for given revs.
+
+gitlink:git-pack-redundant[1]::
+       Find redundant pack files.
+
+gitlink:git-rev-list[1]::
+       Lists commit objects in reverse chronological order.
+
+gitlink:git-show-index[1]::
+       Displays contents of a pack idx file.
+
+gitlink:git-show-ref[1]::
+       List references in a local repository.
+
+gitlink:git-tar-tree[1]::
+       Creates a tar archive of the files in the named tree object.
+
+gitlink:git-unpack-file[1]::
+       Creates a temporary file with a blob's contents.
+
+gitlink:git-var[1]::
+       Displays a git logical variable.
+
+gitlink:git-verify-pack[1]::
+       Validates packed git archive files.
+
+In general, the interrogate commands do not touch the files in
+the working tree.
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-fetch-pack[1]::
+       Updates from a remote repository (engine for ssh and
+       local transport).
+
+gitlink:git-http-fetch[1]::
+       Downloads a remote git repository via HTTP by walking
+       commit chain.
+
+gitlink:git-local-fetch[1]::
+       Duplicates another git repository on a local system by
+       walking commit chain.
+
+gitlink:git-peek-remote[1]::
+       Lists references on a remote repository using
+       upload-pack protocol (engine for ssh and local
+       transport).
+
+gitlink:git-receive-pack[1]::
+       Invoked by 'git-send-pack' to receive what is pushed to it.
+
+gitlink:git-send-pack[1]::
+       Pushes to a remote repository, intelligently.
+
+gitlink:git-http-push[1]::
+       Push missing objects using HTTP/DAV.
+
+gitlink:git-shell[1]::
+       Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
+       Pulls from a remote repository over ssh connection by
+       walking commit chain.
+
+gitlink:git-ssh-upload[1]::
+       Helper "server-side" program used by git-ssh-fetch.
+
+gitlink:git-update-server-info[1]::
+       Updates auxiliary information on a dumb server to help
+       clients discover references and packs on it.
+
+gitlink:git-upload-archive[1]::
+       Invoked by 'git-archive' to send a generated archive.
+
+gitlink:git-upload-pack[1]::
+       Invoked by 'git-fetch-pack' to push
+       what are asked for.
+
+
 Configuration Mechanism
 -----------------------
 
index 2d62efb5c4eb0a84c440e2e75b521b536b790bf9..6d324fb56e9feaaf0780b11a6eb190dbd9bf1675 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -158,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 \
@@ -187,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 \
@@ -212,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
@@ -260,7 +257,7 @@ 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
@@ -270,6 +267,7 @@ BUILTIN_OBJS = \
        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 \
@@ -310,7 +308,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
@@ -483,11 +483,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
@@ -734,11 +732,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
@@ -939,3 +932,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 e664813cb48f555fa8968995eda4170a02a99f42..3ec1c8f1b287654cccb0f2427810d0afead5c44b 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -19,7 +19,9 @@
 #include "xdiff-interface.h"
 #include "quote.h"
 
+#ifndef DEBUG
 #define DEBUG 0
+#endif
 
 static const char blame_usage[] =
 "git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] file [commit]\n"
@@ -232,6 +234,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;
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;
 
index 698618b798184ad24eed43917b5d609f9abf5886..173bf38735315a1c299f1f2e3db76a7795a2305c 100644 (file)
@@ -59,6 +59,8 @@ static struct {
        { "taggername" },
        { "taggeremail" },
        { "taggerdate", FIELD_TIME },
+       { "creator" },
+       { "creatordate", FIELD_TIME },
        { "subject" },
        { "body" },
        { "contents" },
@@ -401,6 +403,29 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
                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)
@@ -585,24 +610,27 @@ static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
        *v = &ref->value[atom];
 }
 
-static struct refinfo **grab_array;
-static const char **grab_pattern;
-static int *grab_cnt;
+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)
+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 (*grab_pattern) {
+       if (*cb->grab_pattern) {
                const char **pattern;
                int namelen = strlen(refname);
-               for (pattern = grab_pattern; *pattern; pattern++) {
+               for (pattern = cb->grab_pattern; *pattern; pattern++) {
                        const char *p = *pattern;
                        int plen = strlen(p);
 
@@ -626,25 +654,14 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1)
        ref->refname = xstrdup(refname);
        hashcpy(ref->objectname, sha1);
 
-       cnt = *grab_cnt;
-       grab_array = xrealloc(grab_array, sizeof(*grab_array) * (cnt + 1));
-       grab_array[cnt++] = ref;
-       *grab_cnt = cnt;
+       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 struct refinfo **grab_refs(const char **pattern, int *cnt)
-{
-       /* Sheesh, we really should make for-each-ref to take
-        * callback data.
-        */
-       *cnt = 0;
-       grab_pattern = pattern;
-       grab_cnt = cnt;
-       for_each_ref(grab_single_ref);
-       return grab_array;
-}
-
 static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
 {
        struct atom_value *va, *vb;
@@ -784,6 +801,7 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix)
        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];
@@ -855,7 +873,11 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix)
 
        verify_format(format);
 
-       refs = grab_refs(av + i, &num_refs);
+       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] == '*') {
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 41e1e74533d64e999ec1e564e15b8dea29e4c9c6..270bcbded68551d6992b039d48d019e4012a2ab9 100644 (file)
@@ -15,7 +15,7 @@
 #include <sys/time.h>
 #include <signal.h>
 
-static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
+static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--all-progress] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -475,15 +475,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);
 
@@ -1524,6 +1524,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("--incremental", arg)) {
                        incremental = 1;
                        continue;
@@ -1641,7 +1645,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 7290e6d9aa9e26cc8256a34ed22028e80936c010..d853902c51e78800b76cf4714dffaa79600d3e53 100644 (file)
@@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs)
        }
 }
 
-static int add_one_ref(const char *path, const unsigned char *sha1)
+static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *object = parse_object(sha1);
        if (!object)
@@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
        revs.tree_objects = 1;
 
        /* Add all external refs */
-       for_each_ref(add_one_ref);
+       for_each_ref(add_one_ref, NULL);
 
        /* Add all refs from the index file */
        add_cache_refs();
index f5150ed82d269dd825a45cc9c369c71e777bac05..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 e70a71163d18dff1dd182c0d0b02967044693884..e6d75748444ef4fc263970d4fe87b4623d2790fc 100644 (file)
@@ -22,7 +22,7 @@ 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 {
@@ -371,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 708a2f22e8b14f337a0829c53c47e57393c4ee75..a9b28ed8996ffc255f82ebc35f32386865b7f227 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -17,9 +17,11 @@ 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);
@@ -65,5 +67,7 @@ extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
 extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
 
 #endif
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 d0a1657292f5b47b7e345a87877d9b8894a80860..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;
 
@@ -289,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);
@@ -376,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, 
@@ -415,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 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 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 ce3058e437d5f0142be0746a3e50a3c32045eecb..ac7b21c46bdf958df5d7c8162c7aa248af54dbdb 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -102,6 +102,8 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
                     const char *base, struct diff_options *opt);
 extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
                          const char *base, struct diff_options *opt);
+extern int diff_root_tree_sha1(const unsigned char *new, const char *base,
+                               struct diff_options *opt);
 
 struct combine_diff_path {
        struct combine_diff_path *next;
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 474d54520eae356a8987349b1e36186a30914966..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) {
@@ -254,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);
 
@@ -366,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);
 
@@ -416,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;
@@ -447,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.");
 
@@ -494,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)) {
@@ -518,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-branch.sh b/git-branch.sh
deleted file mode 100755 (executable)
index f823c78..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/bin/sh
-
-USAGE='[-l] [-f] <branchname> [<start-point>] | (-d | -D) <branchname> | [-r]'
-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
-If one argument, create a new branch <branchname> based off of current HEAD.
-If two arguments, create a new branch <branchname> based off of <start-point>.'
-
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-
-delete_branch () {
-    option="$1"
-    shift
-    for branch_name
-    do
-       case ",$headref," in
-       ",$branch_name,")
-           die "Cannot delete the branch you are on." ;;
-       ,,)
-           die "What branch are you on anyway?" ;;
-       esac
-       branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
-           branch=$(git-rev-parse --verify "$branch^0") ||
-               die "Seriously, what branch are you talking about?"
-       case "$option" in
-       -D)
-           ;;
-       *)
-           mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
-           case " $mbs " in
-           *' '$branch' '*)
-               # the merge base of branch and HEAD contains branch --
-               # which means that the HEAD contains everything in both.
-               ;;
-           *)
-               echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
-               exit 1
-               ;;
-           esac
-           ;;
-       esac
-       rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
-       rm -f "$GIT_DIR/refs/heads/$branch_name"
-       echo "Deleted branch $branch_name."
-    done
-    exit 0
-}
-
-ls_remote_branches () {
-    git-rev-parse --symbolic --all |
-    sed -ne 's|^refs/\(remotes/\)|\1|p' |
-    sort
-}
-
-force=
-create_log=
-while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
-do
-       case "$1" in
-       -d | -D)
-               delete_branch "$@"
-               exit
-               ;;
-       -r)
-               ls_remote_branches
-               exit
-               ;;
-       -f)
-               force="$1"
-               ;;
-       -l)
-               create_log="yes"
-               ;;
-       --)
-               shift
-               break
-               ;;
-       -*)
-               usage
-               ;;
-       esac
-       shift
-done
-
-case "$#" in
-0)
-       git-rev-parse --symbolic --branches |
-       sort |
-       while read ref
-       do
-               if test "$headref" = "$ref"
-               then
-                       pfx='*'
-               else
-                       pfx=' '
-               fi
-               echo "$pfx $ref"
-       done
-       exit 0 ;;
-1)
-       head=HEAD ;;
-2)
-       head="$2^0" ;;
-esac
-branchname="$1"
-
-rev=$(git-rev-parse --verify "$head") || exit
-
-git-check-ref-format "heads/$branchname" ||
-       die "we do not like '$branchname' as a branch name."
-
-if [ -d "$GIT_DIR/refs/heads/$branchname" ]
-then
-       for refdir in `cd "$GIT_DIR" && \
-               find "refs/heads/$branchname" -type d | sort -r`
-       do
-               rmdir "$GIT_DIR/$refdir" || \
-                   die "Could not delete '$refdir', there may still be a ref there."
-       done
-fi
-
-if [ -e "$GIT_DIR/refs/heads/$branchname" ]
-then
-       if test '' = "$force"
-       then
-               die "$branchname already exists."
-       elif test "$branchname" = "$headref"
-       then
-               die "cannot force-update the current branch."
-       fi
-fi
-if test "$create_log" = 'yes'
-then
-       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
-       touch "$GIT_DIR/logs/refs/heads/$branchname"
-fi
-git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
index dd477245fb3703402b950cbc1378b35ce379d631..119bca1ffbfb8fb0dae404a8e7389f577aebc42c 100755 (executable)
@@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do
                shift
                [ -z "$newbranch" ] &&
                        die "git checkout: -b needs a branch name"
-               [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+               git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
                        die "git checkout: branch $newbranch already exists"
                git-check-ref-format "heads/$newbranch" ||
                        die "git checkout: we do not like '$newbranch' as a branch name."
@@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do
                        fi
                        new="$rev"
                        new_name="$arg^0"
-                       if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+                       if git-show-ref --verify --quiet -- "refs/heads/$arg"
+                       then
                                branch="$arg"
                        fi
                elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
diff --git a/git-cherry.sh b/git-cherry.sh
deleted file mode 100755 (executable)
index cf7af55..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 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 7dc1f33687279812605c7b6f9ddc13478e143058..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_" ||:
@@ -368,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
@@ -378,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=
@@ -408,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
 
@@ -427,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 17e24526c279467891389295a55f8f257ca0d01b..f150a558ca7a7a659f4eb88be3a275e1a4e87337 100755 (executable)
@@ -45,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
 
@@ -86,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 c42dc3bc943edb2e8f5d9e11a309f6e6d86848ab..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 $_) {
index 37ecc517879aa2c18cc909c60d3bf6ceed82fb48..4a56f1871a6e185c4504d2464f264a8d8adc124b 100755 (executable)
@@ -2662,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;
@@ -2769,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]);
@@ -2848,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');
                        }
                }
        }
@@ -3140,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 6475847b7a06750f8095c363dbc7414213042cca..d2460c861885a50df9ff8b3acb11c1b45bcc366e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -222,9 +222,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "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 },
@@ -269,6 +271,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
                { "write-tree", cmd_write_tree, RUN_SETUP },
                { "verify-pack", cmd_verify_pack },
+               { "show-ref", cmd_show_ref, RUN_SETUP },
+               { "pack-refs", cmd_pack_refs, RUN_SETUP },
        };
        int i;
 
index 7b2499af1ecaf11fa4bd775d2bfcc2abc5f477c0..3c6fd7ca47551eb808a40ca2cc7ed1690f19cc06 100755 (executable)
        '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 {
@@ -405,6 +420,7 @@ 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,
@@ -554,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;
 }
 
@@ -784,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";
 }
 
 ## ----------------------------------------------------------------------
@@ -860,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;
@@ -892,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
@@ -910,8 +939,10 @@ sub git_get_projects_list {
 
                                my $subdir = substr($File::Find::name, $pfxlen + 1);
                                # we check related file in $projectroot
-                               if (check_export_ok("$projectroot/$subdir")) {
-                                       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;
                                }
                        },
@@ -976,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;
@@ -1082,24 +1131,6 @@ sub parse_tag {
        return %tag
 }
 
-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 parse_commit {
        my $commit_id = shift;
        my $commit_text = shift;
@@ -1111,7 +1142,9 @@ sub parse_commit {
                @commit_lines = @$commit_text;
        } else {
                local $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id
+               open my $fd, "-|", git_cmd(), "rev-list",
+                       "--header", "--parents", "--max-count=1",
+                       $commit_id, "--"
                        or return;
                @commit_lines = split '\n', <$fd>;
                close $fd or return;
@@ -1275,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;
@@ -1292,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;
+}
+
+sub git_get_tags_list {
+       my $limit = shift;
+       my @tagslist;
+
+       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;
 
-               my $type = $ref->{'type'} || git_get_type($ref_id) || next;
-               my %ref_item = parse_ref($ref_file, $ref_id, $type);
+               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 @reflist, \%ref_item;
+               push @tagslist, \%ref_item;
        }
-       # sort refs by age
-       @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
-       return (\@reflist, \%refs);
+       close $fd;
+
+       return wantarray ? @tagslist : \@tagslist;
 }
 
 ## ----------------------------------------------------------------------
@@ -1943,19 +2017,19 @@ sub git_difftree_body {
                        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'})},
@@ -1986,19 +2060,19 @@ 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'})},
@@ -2049,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,
@@ -2142,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) = @_;
@@ -2262,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);
@@ -2296,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");
                }
@@ -2321,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>";
        }
@@ -2360,25 +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 (@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'}") || "";
-               }
-               push @projects, $pr;
-       }
 
        git_header_html();
        if (-f $home_text) {
@@ -2388,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->{'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";
+       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->{'age'}) . "\">" .
-                     $pr->{'age_string'} . "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
-                     $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',
@@ -2487,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();
@@ -2529,7 +2647,7 @@ sub git_summary {
        }
 
        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;
@@ -2549,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();
 }
 
@@ -2659,7 +2784,7 @@ sub git_blame2 {
                print "<tr class=\"$rev_color[$current_color]\">\n";
                if ($group_size) {
                        print "<td class=\"sha1\"";
-                       print " title=\"$author, $date\"";
+                       print " title=\"". esc_html($author) . ", $date\"";
                        print " rowspan=\"$group_size\"" if ($group_size > 1);
                        print ">";
                        print $cgi->a({-href => href(action=>"commit",
@@ -2790,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();
 }
@@ -2803,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();
 }
@@ -2918,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";
@@ -3072,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;
@@ -3130,7 +3255,7 @@ sub git_commit {
                $parent = "--root";
        }
        open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
-               @diff_opts, $parent, $hash
+               @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");
@@ -3235,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>;
@@ -3249,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'
@@ -3319,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")
@@ -3396,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';
        }
@@ -3405,8 +3578,8 @@ sub git_commitdiff {
        my @difftree;
        if ($format eq 'html') {
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       "--no-commit-id",
-                       "--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>)) {
@@ -3417,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 {
@@ -3434,10 +3607,6 @@ 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);
@@ -3598,7 +3767,9 @@ sub git_search {
        my $alternate = 1;
        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;
@@ -3744,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;
@@ -3772,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");
@@ -3796,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
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 e33f60524f240f288df7dc6d398486d1d6e7e5ee..042aea8842b531254e9a2d0ec67cbaeb40990575 100644 (file)
@@ -6,9 +6,11 @@
 #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
 {
@@ -33,37 +35,83 @@ union delta_base {
 
 struct delta_entry
 {
-       struct object_entry *obj;
        union delta_base base;
+       int obj_no;
 };
 
-static const char *pack_name;
 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 progress_interval(int signum)
+{
+       progress_update = 1;
+}
+
+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;
+       }
+       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;
+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;
+       }
+}
 
 /*
  * 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 <= input_len)
                return input_buffer + input_offset;
        if (min > sizeof(input_buffer))
                die("cannot fill %d bytes", min);
-       if (input_offset) {
-               SHA1_Update(&input_ctx, input_buffer, input_offset);
-               memcpy(input_buffer, input_buffer + input_offset, input_len);
-               input_offset = 0;
-       }
+       flush();
        do {
                int ret = xread(input_fd, input_buffer + input_len,
                                sizeof(input_buffer) - input_len);
@@ -86,13 +134,31 @@ static void use(int bytes)
        consumed_bytes += bytes;
 }
 
-static void open_pack_file(void)
+static const char *open_pack_file(const char *pack_name)
 {
-       input_fd = open(pack_name, O_RDONLY);
-       if (input_fd < 0)
-               die("cannot open packfile '%s': %s", pack_name,
-                   strerror(errno));
+       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;
 }
 
 static void parse_pack_header(void)
@@ -101,14 +167,12 @@ static void parse_pack_header(void)
 
        /* Header consistency check */
        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);
        use(sizeof(struct pack_header));
-       /*fprintf(stderr, "Indexing %d objects\n", nr_objects);*/
 }
 
 static void bad_object(unsigned long offset, const char *format,
@@ -122,8 +186,7 @@ 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 size)
@@ -212,7 +275,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        return unpack_entry_data(obj->offset, obj->size);
 }
 
-static void * get_data_from_pack(struct object_entry *obj)
+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;
@@ -222,9 +285,9 @@ static void * get_data_from_pack(struct object_entry *obj)
        int st;
 
        map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
-                  input_fd, from - pg_offset);
+                  mmap_fd, from - pg_offset);
        if (map == MAP_FAILED)
-               die("cannot mmap packfile '%s': %s", pack_name, strerror(errno));
+               die("cannot mmap pack file: %s", strerror(errno));
        data = xmalloc(obj->size);
        memset(&stream, 0, sizeof(stream));
        stream.next_out = data;
@@ -261,8 +324,8 @@ static int find_delta(const union delta_base *base)
         return -first-1;
 }
 
-static int find_delta_childs(const union delta_base *base,
-                            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);
        int last = first;
@@ -304,10 +367,9 @@ 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;
@@ -315,29 +377,34 @@ static void resolve_delta(struct delta_entry *delta, void *base_data,
        union delta_base delta_base;
        int j, first, last;
 
-       obj->real_type = type;
-       delta_data = get_data_from_pack(obj);
-       delta_size = obj->size;
+       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);
-
-       hashcpy(delta_base.sha1, obj->sha1);
-       if (!find_delta_childs(&delta_base, &first, &last)) {
-               for (j = first; j <= last; j++)
-                       if (deltas[j].obj->type == OBJ_REF_DELTA)
-                               resolve_delta(&deltas[j], result, result_size, type);
+               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 = obj->offset;
-       if (!find_delta_childs(&delta_base, &first, &last)) {
-               for (j = first; j <= last; j++)
-                       if (deltas[j].obj->type == OBJ_OFS_DELTA)
-                               resolve_delta(&deltas[j], result, result_size, type);
+       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);
@@ -353,7 +420,7 @@ static int compare_delta_entry(const void *a, const void *b)
 /* Parse all objects and return the pack content SHA1 hash */
 static void parse_pack_objects(unsigned char *sha1)
 {
-       int i;
+       int i, percent = -1;
        struct delta_entry *delta = deltas;
        void *data;
        struct stat st;
@@ -362,34 +429,43 @@ static void parse_pack_objects(unsigned char *sha1)
         * 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];
                data = unpack_raw_entry(obj, &delta->base);
                obj->real_type = obj->type;
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
                        nr_deltas++;
-                       delta->obj = obj;
+                       delta->obj_no = i;
                        delta++;
                } else
                        sha1_object(data, obj->size, obj->type, obj->sha1);
                free(data);
+               if (verbose)
+                       percent = display_progress(i+1, nr_objects, percent);
        }
        objects[i].offset = consumed_bytes;
+       if (verbose)
+               fputc('\n', stderr);
 
        /* Check pack integrity */
-       SHA1_Update(&input_ctx, input_buffer, input_offset);
+       flush();
        SHA1_Final(sha1, &input_ctx);
        if (hashcmp(fill(20), sha1))
-               die("packfile '%s' SHA1 mismatch", pack_name);
+               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': %s", pack_name, strerror(errno));
+               die("cannot fstat packfile: %s", strerror(errno));
        if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
-               die("packfile '%s' has junk at the end", pack_name);
+               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),
@@ -403,6 +479,8 @@ static void parse_pack_objects(unsigned char *sha1)
         *   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];
                union delta_base base;
@@ -411,32 +489,179 @@ static void parse_pack_objects(unsigned char *sha1)
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
                        continue;
                hashcpy(base.sha1, obj->sha1);
-               ref = !find_delta_childs(&base, &ref_first, &ref_last);
+               ref = !find_delta_children(&base, &ref_first, &ref_last);
                memset(&base, 0, sizeof(base));
                base.offset = obj->offset;
-               ofs = !find_delta_childs(&base, &ofs_first, &ofs_last);
+               ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
                if (!ref && !ofs)
                        continue;
                data = get_data_from_pack(obj);
                if (ref)
-                       for (j = ref_first; j <= ref_last; j++)
-                               if (deltas[j].obj->type == OBJ_REF_DELTA)
-                                       resolve_delta(&deltas[j], data,
+                       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++)
-                               if (deltas[j].obj->type == OBJ_OFS_DELTA)
-                                       resolve_delta(&deltas[j], data,
+                       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_REF_DELTA ||
-                   deltas[i].obj->real_type == OBJ_OFS_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)
@@ -450,12 +675,12 @@ static int sha1_compare(const void *_a, const void *_b)
  * On entry *sha1 contains the pack content SHA1 hash, on exit it is
  * the SHA1 hash of sorted object names.
  */
-static void write_index_file(const char *index_name, unsigned char *sha1)
+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) {
@@ -472,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,
@@ -513,20 +749,128 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
        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];
@@ -540,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'",
@@ -552,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 + 1, sizeof(struct object_entry));
-       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+       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 cdc2f780a277ad8b55eedf84cdfbafdd4732a711..cd2cc77bf47025a9dfc88f594961521c3ec8f22c 100644 (file)
@@ -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)
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 ee7d62c751272f511d46894e5c5cd32e4403c721..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;
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 f1e0caaae3d2a96de6cf9bad12e8641c19d788dd..3dbc26c49c585088ff4861992ce70a94465df401 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)
@@ -1015,7 +1012,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) {
@@ -1149,17 +1146,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 e89d24c01595aa8ea6c6306928639b40f3313a50..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();
@@ -1203,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;
@@ -1214,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;
@@ -1399,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 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 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 9ec3775049a44bf1ffdc145240e6d7cdea303b91..ddaa72f0a98e9b7f424279b74798bc3cfaadbd48 100644 (file)
@@ -420,7 +420,7 @@ 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 ofs-delta";
        struct object *o = parse_object(sha1);
@@ -448,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 3952809c2a2d5526e5b97ad57a53b601906e01a2..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;
@@ -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);