Merge branch 'tr/log-tree-optim'
authorJunio C Hamano <gitster@pobox.com>
Tue, 2 Apr 2013 22:10:46 +0000 (15:10 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 2 Apr 2013 22:10:46 +0000 (15:10 -0700)
Optimize "log" that shows the difference between the parent and the
child.

* tr/log-tree-optim:
Avoid loading commits twice in log with diffs

200 files changed:
Documentation/CodingGuidelines
Documentation/RelNotes/1.8.2.1.txt
Documentation/RelNotes/1.8.3.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-count-objects.txt
Documentation/git-difftool.txt
Documentation/git-merge.txt
Documentation/git-push.txt
Documentation/git-rm.txt
Documentation/git-send-email.txt
Documentation/git-sh-setup.txt
Documentation/git-shell.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/merge-options.txt
Documentation/pretty-formats.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
advice.c
advice.h
archive-zip.c
archive.c
builtin/archive.c
builtin/branch.c
builtin/cat-file.c
builtin/commit-tree.c
builtin/commit.c
builtin/count-objects.c
builtin/describe.c
builtin/fast-export.c
builtin/fetch-pack.c
builtin/fmt-merge-msg.c
builtin/grep.c
builtin/index-pack.c
builtin/log.c
builtin/prune.c
builtin/push.c
builtin/receive-pack.c
builtin/send-pack.c
builtin/tag.c
builtin/upload-archive.c
builtin/verify-tag.c
bundle.c
cache.h
combine-diff.c
commit.c
commit.h
compat/cygwin.c
compat/cygwin.h
compat/msvc.h
compat/vcbuild/include/sys/poll.h [deleted file]
compat/vcbuild/include/unistd.h
config.mak.in
config.mak.uname
connect.c
contrib/completion/git-completion.bash
contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
contrib/credential/netrc/Makefile [new file with mode: 0644]
contrib/credential/netrc/git-credential-netrc [new file with mode: 0755]
contrib/credential/netrc/test.netrc [new file with mode: 0644]
contrib/credential/netrc/test.pl [new file with mode: 0755]
contrib/credential/osxkeychain/git-credential-osxkeychain.c
contrib/credential/wincred/git-credential-wincred.c
contrib/examples/git-remote.perl
contrib/examples/git-svnimport.perl
contrib/fast-import/git-import.perl
contrib/fast-import/git-import.sh
contrib/fast-import/import-zips.py
contrib/hooks/setgitperms.perl
contrib/mw-to-git/t/install-wiki.sh
daemon.c
date.c
diff.c
diffcore-rename.c
entry.c
environment.c
fast-import.c
fetch-pack.c
fetch-pack.h
git-am.sh
git-archimport.perl
git-compat-util.h
git-cvsexportcommit.perl
git-cvsimport.perl
git-cvsserver.perl
git-difftool.perl
git-merge-one-file.sh
git-p4.py
git-pull.sh
git-relink.perl
git-send-email.perl
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git.c
gpg-interface.c
gpg-interface.h
grep.c
grep.h
hash.h
http-backend.c
http.c
log-tree.c
match-trees.c
mergetools/p4merge
name-hash.c
object.c
object.h
pack-refs.c
path.c
perl/Git.pm
perl/Git/SVN/Ra.pm
pkt-line.c
pkt-line.h
pretty.c
reachable.c
read-cache.c
refs.c
refs.h
remote-curl.c
remote.c
remote.h
revision.c
revision.h
send-pack.c
sequencer.c
sequencer.h
setup.c
sha1_file.c
sha1_name.c
shell.c
sideband.c
sideband.h
submodule.c
t/lib-git-svn.sh
t/t0024-crlf-archive.sh
t/t1509/prepare-chroot.sh
t/t1510-repo-setup.sh
t/t2003-checkout-cache-mkdir.sh
t/t2200-add-update.sh
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3211-peel-ref.sh [new file with mode: 0755]
t/t3400-rebase.sh
t/t3404-rebase-interactive.sh
t/t3511-cherry-pick-x.sh [new file with mode: 0755]
t/t4001-diff-rename.sh
t/t4014-format-patch.sh
t/t4018-diff-funcname.sh
t/t4034-diff-words.sh
t/t4038-diff-combined.sh
t/t4202-log.sh
t/t5003-archive-zip.sh
t/t5004-archive-corner-cases.sh [new file with mode: 0755]
t/t5004/empty.zip [new file with mode: 0644]
t/t5304-prune.sh
t/t5500-fetch-pack.sh
t/t5503-tagfollow.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5521-pull-options.sh
t/t5541-http-push.sh
t/t5700-clone-reference.sh
t/t6009-rev-list-parent.sh
t/t6012-rev-list-simplify.sh
t/t6030-bisect-porcelain.sh
t/t7004-tag.sh
t/t7060-wtstatus.sh
t/t7062-wtstatus-ignorecase.sh [new file with mode: 0755]
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7500-commit.sh
t/t7502-commit.sh
t/t7508-status.sh
t/t7512-status-help.sh
t/t7600-merge.sh
t/t7800-difftool.sh
t/t7811-grep-open.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9808-git-p4-chdir.sh
t/test-lib-functions.sh
t/test-lib.sh
templates/hooks--update.sample
test-chmtime.c
test-delta.c
test-genrandom.c
test-svn-fe.c
transport.c
transport.h
upload-pack.c
utf8.c
utf8.h
write_or_die.c
wt-status.c
wt-status.h
zlib.c
index b1bfff630fc0ec22d68757ebab84e76ee5abbc2c..7e4d5716a62bf59e8f072163ebac3064dad323ae 100644 (file)
@@ -237,6 +237,9 @@ For Python scripts:
 
 Writing Documentation:
 
+ Most (if not all) of the documentation pages are written in AsciiDoc
+ and processed into HTML output and manpages.
+
  Every user-visible change should be reflected in the documentation.
  The same general rule as for code applies -- imitate the existing
  conventions.  A few commented examples follow to provide reference
index b4cf3cccde57d5dff014fb036e8dbf072554f93f..2cd562233eac46c67f2eb06d7a84f57ca1540d3b 100644 (file)
@@ -4,6 +4,10 @@ Git v1.8.2.1 Release Notes
 Fixes since v1.8.2
 ------------------
 
+ * The logic used by "git diff -M --stat" to shorten the names of
+   files before and after a rename did not work correctly when the
+   common prefix and suffix between the two filenames overlapped.
+
  * "git submodule update", when recursed into sub-submodules, did not
    acccumulate the prefix paths.
 
diff --git a/Documentation/RelNotes/1.8.3.txt b/Documentation/RelNotes/1.8.3.txt
new file mode 100644 (file)
index 0000000..dcef36e
--- /dev/null
@@ -0,0 +1,233 @@
+Git v1.8.3 Release Notes
+========================
+
+Backward compatibility notes (for Git 2.0)
+------------------------------------------
+
+When "git push [$there]" does not say what to push, we have used the
+traditional "matching" semantics so far (all your branches were sent
+to the remote as long as there already are branches of the same name
+over there).  In Git 2.0, the default will change to the "simple"
+semantics that pushes the current branch to the branch with the same
+name, only when the current branch is set to integrate with that
+remote branch.  There is a user preference configuration variable
+"push.default" to change this.  If you are an old-timer who is used
+to the "matching" semantics, you can set it to "matching" to keep the
+traditional behaviour.  If you want to live in the future early,
+you can set it to "simple" today without waiting for Git 2.0.
+
+When "git add -u" and "git add -A", that does not specify what paths
+to add on the command line is run from inside a subdirectory, these
+commands will operate on the entire tree in Git 2.0 for consistency
+with "git commit -a" and other commands. Because there will be no
+mechanism to make "git add -u" behave as if "git add -u .", it is
+important for those who are used to "git add -u" (without pathspec)
+updating the index only for paths in the current subdirectory to start
+training their fingers to explicitly say "git add -u ." when they mean
+it before Git 2.0 comes.
+
+
+Updates since v1.8.2
+--------------------
+
+UI, Workflows & Features
+
+ * When the interactive access to git-shell is not enabled, it issues
+   a message meant to help the system admininstrator to enable it.
+   An explicit way to help the end users who connect to the service by
+   issuing custom messages to refuse such an access has been added.
+
+ * In addition to the case where the user edits the log message with
+   the "e)dit" option of "am -i", replace the "Applying: this patch"
+   message with the final log message contents after applymsg hook
+   munges it.
+
+ * "git status" suggests users to look into using --untracked=no option
+   when it takes too long.
+
+ * "git status" shows a bit more information to "git status" during a
+   rebase/bisect session.
+
+ * "git fetch" learned to fetch a commit at the tip of an unadvertised
+   ref by specifying a raw object name from the command line when the
+   server side supports this feature.
+
+ * "git count-objects -v" learned to report leftover temporary
+   packfiles and other garbage in the object store.
+
+ * A new read-only credential helper (in contrib/) to interact with
+   the .netrc/.authinfo files has been added.
+
+ * "git send-email" can be used with the credential helper system.
+
+ * There was no Porcelain way to say "I no longer am interested in
+   this submodule", once you express your interest in a submodule with
+   "submodule init".  "submodule deinit" is the way to do so.
+
+ * "git pull --rebase" learned to pass "-v/-q" options to underlying
+   "git rebase".
+
+ * The new "--follow-tags" option tells "git push" to push relevant
+   annotated tags when pushing branches out.
+
+ * "git mergetool" now feeds files to the "p4merge" backend in the
+   order that matches the p4 convention, where "theirs" is usually
+   shown on the left side, which is the opposite from other backend
+   expects.
+
+
+Performance, Internal Implementation, etc.
+
+ * Updates for building under msvc.
+
+ * A few codepaths knew how much data they need to put in the
+   hashtables they use upfront, but still started from a small table
+   repeatedly growing and rehashing.
+
+ * The API to walk reflog entries from the latest to older, which was
+   necessary for operations such as "git checkout -", was cumbersome
+   to use correctly and also inefficient.
+
+ * Codepaths that inspect log-message-to-be and decide when to add a
+   new Signed-off-by line in various commands have been consolidated.
+
+ * The pkt-line API, implementation and its callers have been cleaned
+   up to make them more robust.
+
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.8.2
+------------------
+
+Unless otherwise noted, all the fixes since v1.8.2 in the maintenance
+track are contained in this release (see release notes to them for
+details).
+
+ * "index-pack --fix-thin" used uninitialize value to compute delta
+   depths of objects it appends to the resulting pack.
+   (merge 57165db jk/index-pack-correct-depth-fix later to maint).
+
+ * "index-pack --verify-stat" used a few counters outside protection
+   of mutex, possibly showing incorrect numbers.
+   (merge 8f82aad nd/index-pack-threaded-fixes later to maint).
+
+ * The code to keep track of what directory names are known to Git on
+   platforms with case insensitive filesystems can get confused upon a
+   hash collision between these pathnames and looped forever.
+   (merge 2092678 kb/name-hash later to maint).
+
+ * Annotated tags outside refs/tags/ hierarchy were not advertised
+   correctly to the ls-remote and fetch with recent version of Git.
+   (merge c29c46f jk/fully-peeled-packed-ref later to maint).
+
+ * Recent optimization broke shallow clones.
+   (merge f59de5d jk/peel-ref later to maint).
+
+ * "git cmd -- ':(top'" was not diagnosed as an invalid syntax, and
+   instead the parser kept reading beyond the end of the string.
+   (merge f612a67 lf/setup-prefix-pathspec later to maint).
+
+ * "git tag -f <tag>" always said "Updated tag '<tag>'" even when
+   creating a new tag (i.e. not overwriting nor updating).
+   (merge 3ae851e ph/tag-force-no-warn-on-creation later to maint).
+
+ * "git p4" did not behave well when the path to the root of the P4
+   client was not its real path.
+   (merge bbd8486 pw/p4-symlinked-root later to maint).
+
+ * "git archive" reports a failure when asked to create an archive out
+   of an empty tree.  It would be more intuitive to give an empty
+   archive back in such a case.
+   (merge bd54cf1 jk/empty-archive later to maint).
+
+ * When "format-patch" quoted a non-ascii strings on the header files,
+   it incorrectly applied rfc2047 and chopped a single character in
+   the middle of it.
+   (merge 6cd3c05 ks/rfc2047-one-char-at-a-time later to maint).
+
+ * An aliased command spawned from a bare repository that does not say
+   it is bare with "core.bare = yes" is treated as non-bare by mistake.
+   (merge 2cd83d1 jk/alias-in-bare later to maint).
+
+ * In "git reflog expire", REACHABLE bit was not cleared from the
+   correct objects.
+
+ * The logic used by "git diff -M --stat" to shorten the names of
+   files before and after a rename did not work correctly when the
+   common prefix and suffix between the two filenames overlapped.
+
+ * The "--match=<pattern>" option of "git describe", when used with
+   "--all" to allow refs that are not annotated tags to be used as a
+   base of description, did not restrict the output from the command
+   to those that match the given pattern.
+   (merge 46e1d6e jc/describe later to maint).
+
+ * Clarify in the documentation "what" gets pushed to "where" when the
+   command line to "git push" does not say these explicitly.
+
+ * The "--color=<when>" argument to the commands in the diff family
+   was described poorly.
+
+ * The arguments given to pre-rebase hook were not documented.
+
+ * The v4 index format was not documented.
+
+ * The "--match=<pattern>" argument "git describe" takes uses glob
+   pattern but it wasn't obvious from the documentation.
+
+ * Some sources failed to compile on systems that lack NI_MAXHOST in
+   their system header (e.g. z/OS).
+
+ * Add an example use of "--env-filter" in "filter-branch"
+   documentation.
+
+ * "git bundle verify" did not say "records a complete history" for a
+   bundle that does not have any prerequisites.
+
+ * In the v1.8.0 era, we changed symbols that do not have to be global
+   to file scope static, but a few functions in graph.c were used by
+   CGit from sideways bypassing the entry points of the API the
+   in-tree users use.
+
+ * "git update-index -h" did not do the usual "-h(elp)" thing.
+
+ * "git index-pack" had a buffer-overflow while preparing an
+   informational message when the translated version of it was too
+   long.
+
+ * 'git commit -m "$msg"' used to add an extra newline even when
+   $msg already ended with one.
+   (merge 46fbf75 bc/commit-complete-lines-given-via-m-option later to maint).
+
+ * The SSL peer verification done by "git imap-send" did not ask for
+   Server Name Indication (RFC 4366), failing to connect SSL/TLS
+   sites that serve multiple hostnames on a single IP.
+
+ * perl/Git.pm::cat_blob slurped everything in core only to write it
+   out to a file descriptor, which was not a very smart thing to do.
+
+ * "git branch" did not bother to check nonsense command line
+   parameters and issue errors in many cases.
+
+ * Verification of signed tags were not done correctly when not in C
+   or en/US locale.
+   (merge 0174eea mg/gpg-interface-using-status later to maint).
+
+ * Some platforms and users spell UTF-8 differently; retry with the
+   most official "UTF-8" when the system does not understand the
+   user-supplied encoding name that are the common alternative
+   spellings of UTF-8.
+
+ * When export-subst is used, "zip" output recorded incorrect
+   size of the file.
+
+ * "git am $maildir/" applied messages in an unexpected order; sort
+   filenames read from the maildir/ in a way that is more likely to
+   sort messages in the order the writing MUA meant to, by sorting
+   numeric segment in numeric order and non-numeric segment in
+   alphabetical order.
+
+ * "git submodule update", when recursed into sub-submodules, did not
+   acccumulate the prefix paths.
index b3023b817244d5b315f8e3227eaa87fd888ecf95..f79184c0a83170ea54298f2b8ac960372371b0f3 100644 (file)
@@ -178,6 +178,10 @@ advice.*::
                the template shown when writing commit messages in
                linkgit:git-commit[1], and in the help message shown
                by linkgit:git-checkout[1] when switching branch.
+       statusUoption::
+               Advise to consider using the `-u` option to linkgit:git-status[1]
+               when the command takes more than 2 seconds to enumerate untracked
+               files.
        commitBeforeMerge::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting local changes.
@@ -551,7 +555,7 @@ core.commentchar::
        (default '#').
 
 sequence.editor::
-       Text editor used by `git rebase -i` for editing the rebase insn file.
+       Text editor used by `git rebase -i` for editing the rebase instruction file.
        The value is meant to be interpreted by the shell when it is used.
        It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
        When not configured the default commit message editor is used instead.
@@ -2119,7 +2123,13 @@ uploadpack.hiderefs::
        are under the hierarchies listed on the value of this
        variable is excluded, and is hidden from `git ls-remote`,
        `git fetch`, etc.  An attempt to fetch a hidden ref by `git
-       fetch` will fail.
+       fetch` will fail.  See also `uploadpack.allowtipsha1inwant`.
+
+uploadpack.allowtipsha1inwant::
+       When `uploadpack.hiderefs` is in effect, allow `upload-pack`
+       to accept a fetch request that asks for an object at the tip
+       of a hidden ref (by default, such a request is rejected).
+       see also `uploadpack.hiderefs`.
 
 url.<base>.insteadOf::
        Any URL that starts with this value will be rewritten to
index 86ef56e7c8760d622848da4131ebf231d27eb873..cafdc9642d312776b0122c4d010a9e9246bad8ff 100644 (file)
@@ -10,7 +10,9 @@ SYNOPSIS
 --------
 [verse]
 'git commit-tree' <tree> [(-p <parent>)...] < changelog
-'git commit-tree' [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...] <tree>
+'git commit-tree' [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...]
+                 [(-F <file>)...] <tree>
+
 
 DESCRIPTION
 -----------
@@ -52,6 +54,9 @@ OPTIONS
        Read the commit log message from the given file. Use `-` to read
        from the standard input.
 
+-S[<keyid>]::
+       GPG-sign commit.
+
 
 Commit Information
 ------------------
index 42c22bb59da1dac87ee94994ab3f56a07b132887..05f82973687bb3fd7fbb066f725d72d386cd446f 100644 (file)
@@ -174,16 +174,25 @@ OPTIONS
        linkgit:git-commit-tree[1].
 
 --cleanup=<mode>::
-       This option sets how the commit message is cleaned up.
-       The  '<mode>' can be one of 'verbatim', 'whitespace', 'strip',
-       and 'default'. The 'default' mode will strip leading and
-       trailing empty lines and #commentary from the commit message
-       only if the message is to be edited. Otherwise only whitespace
-       removed. The 'verbatim' mode does not change message at all,
-       'whitespace' removes just leading/trailing whitespace lines
-       and 'strip' removes both whitespace and commentary. The default
-       can be changed by the 'commit.cleanup' configuration variable
-       (see linkgit:git-config[1]).
+       This option determines how the supplied commit message should be
+       cleaned up before committing.  The '<mode>' can be `strip`,
+       `whitespace`, `verbatim`, or `default`.
++
+--
+strip::
+       Strip leading and trailing empty lines, trailing whitespace, and
+       #commentary and collapse consecutive empty lines.
+whitespace::
+       Same as `strip` except #commentary is not removed.
+verbatim::
+       Do not change the message at all.
+default::
+       Same as `strip` if the message is to be edited.
+       Otherwise `whitespace`.
+--
++
+The default can be changed by the 'commit.cleanup' configuration
+variable (see linkgit:git-config[1]).
 
 -e::
 --edit::
index 23c80cea6465d23476935abcfabba8e1deb915ee..da6e72e696d4af013b3c717d31a0e98cd41922d4 100644 (file)
@@ -20,11 +20,23 @@ OPTIONS
 -------
 -v::
 --verbose::
-       In addition to the number of loose objects and disk
-       space consumed, it reports the number of in-pack
-       objects, number of packs, disk space consumed by those packs,
-       and number of objects that can be removed by running
-       `git prune-packed`.
+       Report in more detail:
++
+count: the number of loose objects
++
+size: disk space consumed by loose objects, in KiB
++
+in-pack: the number of in-pack objects
++
+size-pack: disk space consumed by the packs, in KiB
++
+prune-packable: the number of loose objects that are also present in
+the packs. These objects could be pruned using `git prune-packed`.
++
+garbage: the number of files in object database that are not valid
+loose objects nor valid packs
++
+size-garbage: disk space consumed by garbage files, in KiB
 
 GIT
 ---
index e0e12e947034b3f690d7f103ee90e0bc1a21f926..8361e6e4e3d2d4829d3ce3c6f3ec4a2ac974e86e 100644 (file)
@@ -72,10 +72,12 @@ with custom merge tool commands and has the same value as `$MERGED`.
 --symlinks::
 --no-symlinks::
        'git difftool''s default behavior is create symlinks to the
-       working tree when run in `--dir-diff` mode.
+       working tree when run in `--dir-diff` mode and the right-hand
+       side of the comparison yields the same content as the file in
+       the working tree.
 +
-       Specifying `--no-symlinks` instructs 'git difftool' to create
-       copies instead.  `--no-symlinks` is the default on Windows.
+Specifying `--no-symlinks` instructs 'git difftool' to create copies
+instead.  `--no-symlinks` is the default on Windows.
 
 -x <command>::
 --extcmd=<command>::
index c852a2677ab9e32801b0dc6ed8bc85b03982233d..42391f2ae76aa579605470b6cad8cf4812f29aae 100644 (file)
@@ -170,6 +170,30 @@ happens:
 If you tried a merge which resulted in complex conflicts and
 want to start over, you can recover with `git merge --abort`.
 
+MERGING TAG
+-----------
+
+When merging an annotated (and possibly signed) tag, Git always
+creates a merge commit even if a fast-forward merge is possible, and
+the commit message template is prepared with the tag message.
+Additionally, if the tag is signed, the signature check is reported
+as a comment in the message template. See also linkgit:git-tag[1].
+
+When you want to just integrate with the work leading to the commit
+that happens to be tagged, e.g. synchronizing with an upstream
+release point, you may not want to make an unnecessary merge commit.
+
+In such a case, you can "unwrap" the tag yourself before feeding it
+to `git merge`, or pass `--ff-only` when you do not have any work on
+your own. e.g.
+
+---
+git fetch origin
+git merge v1.2.3^0
+git merge --ff-only v1.2.3
+---
+
+
 HOW CONFLICTS ARE PRESENTED
 ---------------------------
 
index 577d201c006ab6c72acef9bd800aa36f6cc356d3..eb2883c94cfad91efcc1f70bbdbc1609a3702d3b 100644 (file)
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
+'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
           [<repository> [<refspec>...]]
 
@@ -117,6 +117,12 @@ already exists on the remote side.
        addition to refspecs explicitly listed on the command
        line.
 
+--follow-tags::
+       Push all the refs that would be pushed without this option,
+       and also push annotated tags in `refs/tags` that are missing
+       from the remote but are pointing at committish that are
+       reachable from the refs being pushed.
+
 --receive-pack=<git-receive-pack>::
 --exec=<git-receive-pack>::
        Path to the 'git-receive-pack' program on the remote
index 92bac27e05fca2f532fd2ae309dbc9f7b160c3d2..1d876c2619a414159a121868c5dbaab48e55a708 100644 (file)
@@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.
 Ignored files are deemed expendable and won't stop a submodule's work
 tree from being removed.
 
+If you only want to remove the local checkout of a submodule from your
+work tree without committing the removal,
+use linkgit:git-submodule[1] `deinit` instead.
+
 EXAMPLES
 --------
 `git rm Documentation/\*.txt`::
index 44a1f7c4e8efbc78813de7c3ba13816e68c984cd..0cffef8aa51cc5c2f34393e39e45e84c4997b195 100644 (file)
@@ -164,8 +164,8 @@ Sending
 Furthermore, passwords need not be specified in configuration files
 or on the command line. If a username has been specified (with
 '--smtp-user' or a 'sendemail.smtpuser'), but no password has been
-specified (with '--smtp-pass' or 'sendemail.smtppass'), then the
-user is prompted for a password while the input is masked for privacy.
+specified (with '--smtp-pass' or 'sendemail.smtppass'), then
+a password is obtained using 'git-credential'.
 
 --smtp-server=<host>::
        If set, specifies the outgoing SMTP server to use (e.g.
index 6a9f66d1d920f0bfce1db823e2de3f696ce99f23..5d709d02c39e10ac946c3870a10d959a4c12a5d4 100644 (file)
@@ -82,6 +82,12 @@ get_author_ident_from_commit::
        outputs code for use with eval to set the GIT_AUTHOR_NAME,
        GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit.
 
+create_virtual_base::
+       modifies the first file so only lines in common with the
+       second file remain. If there is insufficient common material,
+       then the first file is left empty. The result is suitable
+       as a virtual base input for a 3-way merge.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 9b9250600f651eac7d6e2fd6ca7ff320a64de67c..c35051ba58b63cdc489b0ee3f4ffb72631b619e3 100644 (file)
@@ -9,25 +9,81 @@ git-shell - Restricted login shell for Git-only SSH access
 SYNOPSIS
 --------
 [verse]
-'git shell' [-c <command> <argument>]
+'chsh' -s $(command -v git-shell) <user>
+'git clone' <user>`@localhost:/path/to/repo.git`
+'ssh' <user>`@localhost`
 
 DESCRIPTION
 -----------
 
-A login shell for SSH accounts to provide restricted Git access. When
-'-c' is given, the program executes <command> non-interactively;
-<command> can be one of 'git receive-pack', 'git upload-pack', 'git
-upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell
-is started in interactive mode when no arguments are given; in this
-case, COMMAND_DIR must exist, and any of the executables in it can be
-invoked.
+This is a login shell for SSH accounts to provide restricted Git access.
+It permits execution only of server-side Git commands implementing the
+pull/push functionality, plus custom commands present in a subdirectory
+named `git-shell-commands` in the user's home directory.
 
-'cvs server' is a special command which executes git-cvsserver.
+COMMANDS
+--------
+
+'git shell' accepts the following commands after the '-c' option:
+
+'git receive-pack <argument>'::
+'git upload-pack <argument>'::
+'git upload-archive <argument>'::
+       Call the corresponding server-side command to support
+       the client's 'git push', 'git fetch', or 'git archive --remote'
+       request.
+'cvs server'::
+       Imitate a CVS server.  See linkgit:git-cvsserver[1].
+
+If a `~/git-shell-commands` directory is present, 'git shell' will
+also handle other, custom commands by running
+"`git-shell-commands/<command> <arguments>`" from the user's home
+directory.
+
+INTERACTIVE USE
+---------------
+
+By default, the commands above can be executed only with the '-c'
+option; the shell is not interactive.
 
-COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have
-read and execute permissions to the directory in order to execute the
-programs in it. The programs are executed with a cwd of $HOME, and
-<argument> is parsed as a command-line string.
+If a `~/git-shell-commands` directory is present, 'git shell'
+can also be run interactively (with no arguments).  If a `help`
+command is present in the `git-shell-commands` directory, it is
+run to provide the user with an overview of allowed actions.  Then a
+"git> " prompt is presented at which one can enter any of the
+commands from the `git-shell-commands` directory, or `exit` to close
+the connection.
+
+Generally this mode is used as an administrative interface to allow
+users to list repositories they have access to, create, delete, or
+rename repositories, or change repository descriptions and
+permissions.
+
+If a `no-interactive-login` command exists, then it is run and the
+interactive shell is aborted.
+
+EXAMPLE
+-------
+
+To disable interactive logins, displaying a greeting instead:
++
+----------------
+$ chsh -s /usr/bin/git-shell
+$ mkdir $HOME/git-shell-commands
+$ cat >$HOME/git-shell-commands/no-interactive-login <<\EOF
+#!/bin/sh
+printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
+printf '%s\n' "provide interactive shell access."
+exit 128
+EOF
+$ chmod +x $HOME/git-shell-commands/no-interactive-login
+----------------
+
+SEE ALSO
+--------
+ssh(1),
+linkgit:git-daemon[1],
+contrib/git-shell-commands/README
 
 GIT
 ---
index 0412c4017d07814804ab9e9adcbf2a8d0de0304f..9046df98a03fe11a203f06d79be0128309a201c5 100644 (file)
@@ -46,15 +46,21 @@ OPTIONS
        Show untracked files.
 +
 The mode parameter is optional (defaults to 'all'), and is used to
-specify the handling of untracked files; when -u is not used, the
-default is 'normal', i.e. show untracked files and directories.
+specify the handling of untracked files.
 +
 The possible options are:
 +
-       - 'no'     - Show no untracked files
-       - 'normal' - Shows untracked files and directories
+       - 'no'     - Show no untracked files.
+       - 'normal' - Shows untracked files and directories.
        - 'all'    - Also shows individual files in untracked directories.
 +
+When `-u` option is not used, untracked files and directories are
+shown (i.e. the same as specifying `normal`), to help you avoid
+forgetting to add newly created files.  Because it takes extra work
+to find untracked files in the filesystem, this mode may take some
+time in a large working tree.  You can use `no` to have `git status`
+return more quickly without showing untracked files.
++
 The default can be changed using the status.showUntrackedFiles
 configuration variable documented in linkgit:git-config[1].
 
index c99d795618f59bf98969b551f2a931680534f18e..74d5bdc59d58f743bc6f5cb5f7f9ee24d384d5c8 100644 (file)
@@ -13,6 +13,7 @@ SYNOPSIS
              [--reference <repository>] [--] <repository> [<path>]
 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
+'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]
              [-f|--force] [--rebase] [--reference <repository>]
              [--merge] [--recursive] [--] [<path>...]
@@ -135,6 +136,19 @@ init::
        the explicit 'init' step if you do not intend to customize
        any submodule locations.
 
+deinit::
+       Unregister the given submodules, i.e. remove the whole
+       `submodule.$name` section from .git/config together with their work
+       tree. Further calls to `git submodule update`, `git submodule foreach`
+       and `git submodule sync` will skip any unregistered submodules until
+       they are initialized again, so use this command if you don't want to
+       have a local checkout of the submodule in your work tree anymore. If
+       you really want to remove a submodule from the repository and commit
+       that use linkgit:git-rm[1] instead.
++
+If `--force` is specified, the submodule's work tree will be removed even if
+it contains local modifications.
+
 update::
        Update the registered submodules, i.e. clone missing submodules and
        checkout the commit specified in the index of the containing repository.
@@ -214,8 +228,10 @@ OPTIONS
 
 -f::
 --force::
-       This option is only valid for add and update commands.
+       This option is only valid for add, deinit and update commands.
        When running add, allow adding an otherwise ignored submodule path.
+       When running deinit the submodule work trees will be removed even if
+       they contain local changes.
        When running update, throw away local changes in submodules when
        switching to a different commit; and always run a checkout operation
        in the submodule, even if the commit listed in the index of the
index 0bcbe0ac3c474ab12068f468476946b9db5ef3e8..34a844582846ae409e17347a65ac6cbeb28202a5 100644 (file)
@@ -30,7 +30,8 @@ set to `no` at the beginning of them.
 
 --no-ff::
        Create a merge commit even when the merge resolves as a
-       fast-forward.
+       fast-forward.  This is the default behaviour when merging an
+       annotated (and possibly signed) tag.
 
 --ff-only::
        Refuse to merge and exit with a non-zero status unless the
index 105f18a6f9fd9f4a5a388cb3ec8a0e6a99f7abe3..293965524e81b240996a795498ba127f723d18eb 100644 (file)
@@ -133,6 +133,7 @@ The placeholders are:
 - '%GG': raw verification message from GPG for a signed commit
 - '%G?': show either "G" for Good or "B" for Bad for a signed commit
 - '%GS': show the name of the signer for a signed commit
+- '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
 - '%gd': shortened reflog selector, e.g., `stash@{1}`
 - '%gn': reflog identity name
index 0170a2346c3788dbf2d983fdc630c4ef961b2960..e831cc2020b2b728d972c9e33f7fef8041381547 100644 (file)
@@ -2338,7 +2338,7 @@ origin)
        fi
        ;;
 *)
-       echo "Usage: $0 origin|test|release" 1>&2
+       echo "usage: $0 origin|test|release" 1>&2
        exit 1
        ;;
 esac
@@ -2352,7 +2352,7 @@ pname=$0
 
 usage()
 {
-       echo "Usage: $pname branch test|release" 1>&2
+       echo "usage: $pname branch test|release" 1>&2
        exit 1
 }
 
index f189b7889e7b19fd1e8a5412c899d8b70fab9d9d..6722e621d119dbefeda2e8ea02dfcc0ebb2b4a5a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.8.2
+DEF_VER=v1.8.2.GIT
 
 LF='
 '
index 598d6313da96f8ba8f76fbd982f491077b6d5d97..0f931a203002aec397f8b454f93df9aee97a21ad 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -358,33 +358,39 @@ STRIP ?= strip
 # Among the variables below, these:
 #   gitexecdir
 #   template_dir
-#   mandir
-#   infodir
-#   htmldir
 #   sysconfdir
 # can be specified as a relative path some/where/else;
 # this is interpreted as relative to $(prefix) and "git" at
 # runtime figures out where they are based on the path to the executable.
+# Additionally, the following will be treated as relative by "git" if they
+# begin with "$(prefix)/":
+#   mandir
+#   infodir
+#   htmldir
 # This can help installing the suite in a relocatable way.
 
 prefix = $(HOME)
 bindir_relative = bin
 bindir = $(prefix)/$(bindir_relative)
-mandir = share/man
-infodir = share/info
+mandir = $(prefix)/share/man
+infodir = $(prefix)/share/info
 gitexecdir = libexec/git-core
 mergetoolsdir = $(gitexecdir)/mergetools
 sharedir = $(prefix)/share
 gitwebdir = $(sharedir)/gitweb
 localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
-htmldir = share/doc/git-doc
+htmldir = $(prefix)/share/doc/git-doc
 ETC_GITCONFIG = $(sysconfdir)/gitconfig
 ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
 lib = lib
 # DESTDIR =
 pathsep = :
 
+mandir_relative = $(patsubst $(prefix)/%,%,$(mandir))
+infodir_relative = $(patsubst $(prefix)/%,%,$(infodir))
+htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir))
+
 export prefix bindir sharedir sysconfdir gitwebdir localedir
 
 CC = cc
@@ -1539,12 +1545,12 @@ ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES))
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 bindir_SQ = $(subst ','\'',$(bindir))
 bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
-mandir_SQ = $(subst ','\'',$(mandir))
-infodir_SQ = $(subst ','\'',$(infodir))
+mandir_relative_SQ = $(subst ','\'',$(mandir_relative))
+infodir_relative_SQ = $(subst ','\'',$(infodir_relative))
 localedir_SQ = $(subst ','\'',$(localedir))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
-htmldir_SQ = $(subst ','\'',$(htmldir))
+htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative))
 prefix_SQ = $(subst ','\'',$(prefix))
 gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
 
@@ -1676,9 +1682,9 @@ strip: $(PROGRAMS) git$X
 
 git.sp git.s git.o: GIT-PREFIX
 git.sp git.s git.o: EXTRA_CPPFLAGS = \
-       '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-       '-DGIT_MAN_PATH="$(mandir_SQ)"' \
-       '-DGIT_INFO_PATH="$(infodir_SQ)"'
+       '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
+       '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
+       '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
 
 git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
@@ -1688,9 +1694,9 @@ help.sp help.s help.o: common-cmds.h
 
 builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX
 builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
-       '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-       '-DGIT_MAN_PATH="$(mandir_SQ)"' \
-       '-DGIT_INFO_PATH="$(infodir_SQ)"'
+       '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
+       '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
+       '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
 
 version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT
 version.sp version.s version.o: EXTRA_CPPFLAGS = \
index 7a99a15f2fd9141247b100eb9a5d0b9b88b0f2b9..80b7e388ad321ea679e2b14ce4f18972da35156c 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.8.2.1.txt
\ No newline at end of file
+Documentation/RelNotes/1.8.3.txt
\ No newline at end of file
index 780f58da0f5b508eaa278c98dd15bdf6f919c468..3bc86260b8a2a809a379c91627f919ef0a529aa1 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -8,6 +8,7 @@ int advice_push_already_exists = 1;
 int advice_push_fetch_first = 1;
 int advice_push_needs_force = 1;
 int advice_status_hints = 1;
+int advice_status_u_option = 1;
 int advice_commit_before_merge = 1;
 int advice_resolve_conflict = 1;
 int advice_implicit_identity = 1;
@@ -25,6 +26,7 @@ static struct {
        { "pushfetchfirst", &advice_push_fetch_first },
        { "pushneedsforce", &advice_push_needs_force },
        { "statushints", &advice_status_hints },
+       { "statusuoption", &advice_status_u_option },
        { "commitbeforemerge", &advice_commit_before_merge },
        { "resolveconflict", &advice_resolve_conflict },
        { "implicitidentity", &advice_implicit_identity },
index fad36df467f065bf7ff7a82d9d447ee4ac5cecb9..af0c983c686b9ca5be2e8631562e60bd19656c22 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -11,6 +11,7 @@ extern int advice_push_already_exists;
 extern int advice_push_fetch_first;
 extern int advice_push_needs_force;
 extern int advice_status_hints;
+extern int advice_status_u_option;
 extern int advice_commit_before_merge;
 extern int advice_resolve_conflict;
 extern int advice_implicit_identity;
index a8d119305ff5c0161e7239a8ad70a92c76122631..b2c4fe0e9f9816a6cb79aa8f7a0cb59ae5bf7f8d 100644 (file)
@@ -111,8 +111,9 @@ static void copy_le32(unsigned char *dest, unsigned int n)
        dest[3] = 0xff & (n >> 030);
 }
 
-static void *zlib_deflate(void *data, unsigned long size,
-               int compression_level, unsigned long *compressed_size)
+static void *zlib_deflate_raw(void *data, unsigned long size,
+                             int compression_level,
+                             unsigned long *compressed_size)
 {
        git_zstream stream;
        unsigned long maxsize;
@@ -120,7 +121,7 @@ static void *zlib_deflate(void *data, unsigned long size,
        int result;
 
        memset(&stream, 0, sizeof(stream));
-       git_deflate_init(&stream, compression_level);
+       git_deflate_init_raw(&stream, compression_level);
        maxsize = git_deflate_bound(&stream, size);
        buffer = xmalloc(maxsize);
 
@@ -265,14 +266,11 @@ static int write_zip_entry(struct archiver_args *args,
        }
 
        if (buffer && method == 8) {
-               deflated = zlib_deflate(buffer, size, args->compression_level,
-                               &compressed_size);
-               if (deflated && compressed_size - 6 < size) {
-                       /* ZLIB --> raw compressed data (see RFC 1950) */
-                       /* CMF and FLG ... */
-                       out = (unsigned char *)deflated + 2;
-                       compressed_size -= 6;   /* ... and ADLER32 */
-               } else {
+               out = deflated = zlib_deflate_raw(buffer, size,
+                                                 args->compression_level,
+                                                 &compressed_size);
+               if (!out || compressed_size >= size) {
+                       out = buffer;
                        method = 0;
                        compressed_size = size;
                }
@@ -353,7 +351,7 @@ static int write_zip_entry(struct archiver_args *args,
                unsigned char compressed[STREAM_BUFFER_SIZE * 2];
 
                memset(&zstream, 0, sizeof(zstream));
-               git_deflate_init(&zstream, args->compression_level);
+               git_deflate_init_raw(&zstream, args->compression_level);
 
                compressed_size = 0;
                zstream.next_out = compressed;
@@ -370,13 +368,10 @@ static int write_zip_entry(struct archiver_args *args,
                        result = git_deflate(&zstream, 0);
                        if (result != Z_OK)
                                die("deflate error (%d)", result);
-                       out = compressed;
-                       if (!compressed_size)
-                               out += 2;
-                       out_len = zstream.next_out - out;
+                       out_len = zstream.next_out - compressed;
 
                        if (out_len > 0) {
-                               write_or_die(1, out, out_len);
+                               write_or_die(1, compressed, out_len);
                                compressed_size += out_len;
                                zstream.next_out = compressed;
                                zstream.avail_out = sizeof(compressed);
@@ -394,11 +389,8 @@ static int write_zip_entry(struct archiver_args *args,
                        die("deflate error (%d)", result);
 
                git_deflate_end(&zstream);
-               out = compressed;
-               if (!compressed_size)
-                       out += 2;
-               out_len = zstream.next_out - out - 4;
-               write_or_die(1, out, out_len);
+               out_len = zstream.next_out - compressed;
+               write_or_die(1, compressed, out_len);
                compressed_size += out_len;
                zip_offset += compressed_size;
 
index 93e00bb4ae8ff9637be9e55fd41536b8837c3ba2..d254fa5d5cc32b0254dc863d015bc3d97dc59681 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -234,7 +234,7 @@ static void parse_pathspec_arg(const char **pathspec,
        ar_args->pathspec = pathspec = get_pathspec("", pathspec);
        if (pathspec) {
                while (*pathspec) {
-                       if (!path_exists(ar_args->tree, *pathspec))
+                       if (**pathspec && !path_exists(ar_args->tree, *pathspec))
                                die("path not found: %s", *pathspec);
                        pathspec++;
                }
index 9a1cfd3dac0123cb65d3db7597221a29e9010bd9..49178f159e246c9a99805d806fa5f096337e8d2f 100644 (file)
@@ -27,8 +27,8 @@ static int run_remote_archiver(int argc, const char **argv,
                               const char *remote, const char *exec,
                               const char *name_hint)
 {
-       char buf[LARGE_PACKET_MAX];
-       int fd[2], i, len, rv;
+       char *buf;
+       int fd[2], i, rv;
        struct transport *transport;
        struct remote *_remote;
 
@@ -53,21 +53,18 @@ static int run_remote_archiver(int argc, const char **argv,
                packet_write(fd[1], "argument %s\n", argv[i]);
        packet_flush(fd[1]);
 
-       len = packet_read_line(fd[0], buf, sizeof(buf));
-       if (!len)
+       buf = packet_read_line(fd[0], NULL);
+       if (!buf)
                die(_("git archive: expected ACK/NAK, got EOF"));
-       if (buf[len-1] == '\n')
-               buf[--len] = 0;
        if (strcmp(buf, "ACK")) {
-               if (len > 5 && !prefixcmp(buf, "NACK "))
+               if (!prefixcmp(buf, "NACK "))
                        die(_("git archive: NACK %s"), buf + 5);
-               if (len > 4 && !prefixcmp(buf, "ERR "))
+               if (!prefixcmp(buf, "ERR "))
                        die(_("remote error: %s"), buf + 4);
                die(_("git archive: protocol error"));
        }
 
-       len = packet_read_line(fd[0], buf, sizeof(buf));
-       if (len)
+       if (packet_read_line(fd[0], NULL))
                die(_("git archive: expected a flush"));
 
        /* Now, start reading from fd[0] and spit it out to stdout */
index 00d17d25d1866323f9e1dffcb334a573f5de5221..e09ce51c2ee59ee4a9f44dbf43c97a2d31911b7b 100644 (file)
@@ -18,6 +18,7 @@
 #include "string-list.h"
 #include "column.h"
 #include "utf8.h"
+#include "wt-status.h"
 
 static const char * const builtin_branch_usage[] = {
        N_("git branch [options] [-r | -a] [--merged | --no-merged]"),
@@ -550,6 +551,29 @@ static int calc_maxwidth(struct ref_list *refs)
        return w;
 }
 
+static char *get_head_description(void)
+{
+       struct strbuf desc = STRBUF_INIT;
+       struct wt_status_state state;
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, 1);
+       if (state.rebase_in_progress ||
+           state.rebase_interactive_in_progress)
+               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+                           state.branch);
+       else if (state.bisect_in_progress)
+               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+                           state.branch);
+       else if (state.detached_from)
+               strbuf_addf(&desc, _("(detached from %s)"),
+                           state.detached_from);
+       else
+               strbuf_addstr(&desc, _("(no branch)"));
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+       return strbuf_detach(&desc, NULL);
+}
 
 static void show_detached(struct ref_list *ref_list)
 {
@@ -557,7 +581,7 @@ static void show_detached(struct ref_list *ref_list)
 
        if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
                struct ref_item item;
-               item.name = xstrdup(_("(no branch)"));
+               item.name = get_head_description();
                item.width = utf8_strwidth(item.name);
                item.kind = REF_LOCAL_BRANCH;
                item.dest = NULL;
index ad290007363120062b044ecb47cf976dbe118384..40f87b4649aa97ca1f33a02f09f5938ce5f41f74 100644 (file)
@@ -193,7 +193,7 @@ static int batch_one_object(const char *obj_name, int print_contents)
        unsigned char sha1[20];
        enum object_type type = 0;
        unsigned long size;
-       void *contents;
+       void *contents = NULL;
 
        if (!obj_name)
           return 1;
index eac901a0ee15f72eacffea32d1b327c7a336f19c..f641ff2a898cf76d288ed139772e247015ca554b 100644 (file)
@@ -10,7 +10,7 @@
 #include "utf8.h"
 #include "gpg-interface.h"
 
-static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
+static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
index 3348aa14e9d5a76fa06e43625c5d74cb047accb6..46204375e786ea583ca1a7eabc3060611adc3d04 100644 (file)
@@ -124,8 +124,10 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
        if (unset)
                strbuf_setlen(buf, 0);
        else {
+               if (buf->len)
+                       strbuf_addch(buf, '\n');
                strbuf_addstr(buf, arg);
-               strbuf_addstr(buf, "\n\n");
+               strbuf_complete_line(buf);
        }
        return 0;
 }
@@ -700,7 +702,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                        previous = eol;
                }
 
-               append_signoff(&sb, ignore_footer);
+               append_signoff(&sb, ignore_footer, 0);
        }
 
        if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
index 9afaa88f776468a0de33dd153eadae7621cf6267..3a01a8d08591d93426d436ce2e1e17fccd3ebf78 100644 (file)
@@ -9,11 +9,22 @@
 #include "builtin.h"
 #include "parse-options.h"
 
+static unsigned long garbage;
+static off_t size_garbage;
+
+static void real_report_garbage(const char *desc, const char *path)
+{
+       struct stat st;
+       if (!stat(path, &st))
+               size_garbage += st.st_size;
+       warning("%s: %s", desc, path);
+       garbage++;
+}
+
 static void count_objects(DIR *d, char *path, int len, int verbose,
                          unsigned long *loose,
                          off_t *loose_size,
-                         unsigned long *packed_loose,
-                         unsigned long *garbage)
+                         unsigned long *packed_loose)
 {
        struct dirent *ent;
        while ((ent = readdir(d)) != NULL) {
@@ -46,9 +57,11 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
                }
                if (bad) {
                        if (verbose) {
-                               error("garbage found: %.*s/%s",
-                                     len + 2, path, ent->d_name);
-                               (*garbage)++;
+                               struct strbuf sb = STRBUF_INIT;
+                               strbuf_addf(&sb, "%.*s/%s",
+                                           len + 2, path, ent->d_name);
+                               report_garbage("garbage found", sb.buf);
+                               strbuf_release(&sb);
                        }
                        continue;
                }
@@ -76,7 +89,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
        const char *objdir = get_object_directory();
        int len = strlen(objdir);
        char *path = xmalloc(len + 50);
-       unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
+       unsigned long loose = 0, packed = 0, packed_loose = 0;
        off_t loose_size = 0;
        struct option opts[] = {
                OPT__VERBOSE(&verbose, N_("be verbose")),
@@ -87,6 +100,8 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
        /* we do not take arguments other than flags for now */
        if (argc)
                usage_with_options(count_objects_usage, opts);
+       if (verbose)
+               report_garbage = real_report_garbage;
        memcpy(path, objdir, len);
        if (len && objdir[len-1] != '/')
                path[len++] = '/';
@@ -97,7 +112,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
                if (!d)
                        continue;
                count_objects(d, path, len, verbose,
-                             &loose, &loose_size, &packed_loose, &garbage);
+                             &loose, &loose_size, &packed_loose);
                closedir(d);
        }
        if (verbose) {
@@ -122,6 +137,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
                printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
                printf("prune-packable: %lu\n", packed_loose);
                printf("garbage: %lu\n", garbage);
+               printf("size-garbage: %lu\n", (unsigned long) (size_garbage / 1024));
        }
        else
                printf("%lu objects, %lu kilobytes\n",
index ca084c675ef80deaac7a3f8615c1ea3329a97421..6636a68cd919ba227a10cfa39efcd5f4e77a801d 100644 (file)
@@ -137,40 +137,39 @@ static void add_to_known_names(const char *path,
 
 static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       int might_be_tag = !prefixcmp(path, "refs/tags/");
+       int is_tag = !prefixcmp(path, "refs/tags/");
        unsigned char peeled[20];
-       int is_tag, prio;
+       int is_annotated, prio;
 
-       if (!all && !might_be_tag)
+       /* Reject anything outside refs/tags/ unless --all */
+       if (!all && !is_tag)
                return 0;
 
+       /* Accept only tags that match the pattern, if given */
+       if (pattern && (!is_tag || fnmatch(pattern, path + 10, 0)))
+               return 0;
+
+       /* Is it annotated? */
        if (!peel_ref(path, peeled)) {
-               is_tag = !!hashcmp(sha1, peeled);
+               is_annotated = !!hashcmp(sha1, peeled);
        } else {
                hashcpy(peeled, sha1);
-               is_tag = 0;
+               is_annotated = 0;
        }
 
-       /* If --all, then any refs are used.
-        * If --tags, then any tags are used.
-        * Otherwise only annotated tags are used.
+       /*
+        * By default, we only use annotated tags, but with --tags
+        * we fall back to lightweight ones (even without --tags,
+        * we still remember lightweight ones, only to give hints
+        * in an error message).  --all allows any refs to be used.
         */
-       if (might_be_tag) {
-               if (is_tag)
-                       prio = 2;
-               else
-                       prio = 1;
-
-               if (pattern && fnmatch(pattern, path + 10, 0))
-                       prio = 0;
-       }
+       if (is_annotated)
+               prio = 2;
+       else if (is_tag)
+               prio = 1;
        else
                prio = 0;
 
-       if (!all) {
-               if (!prio)
-                       return 0;
-       }
        add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
        return 0;
 }
index 77dffd1ce3a5f809a81793f67b9297a555bab1fa..d380155d9cf8fedf133dddbae479a910decd55b2 100644 (file)
@@ -113,12 +113,13 @@ static void show_progress(void)
                printf("progress %d objects\n", counter);
 }
 
-static void handle_object(const unsigned char *sha1)
+static void export_blob(const unsigned char *sha1)
 {
        unsigned long size;
        enum object_type type;
        char *buf;
        struct object *object;
+       int eaten;
 
        if (no_data)
                return;
@@ -126,16 +127,18 @@ static void handle_object(const unsigned char *sha1)
        if (is_null_sha1(sha1))
                return;
 
-       object = parse_object(sha1);
-       if (!object)
-               die ("Could not read blob %s", sha1_to_hex(sha1));
-
-       if (object->flags & SHOWN)
+       object = lookup_object(sha1);
+       if (object && object->flags & SHOWN)
                return;
 
        buf = read_sha1_file(sha1, &type, &size);
        if (!buf)
                die ("Could not read blob %s", sha1_to_hex(sha1));
+       if (check_sha1_signature(sha1, buf, size, typename(type)) < 0)
+               die("sha1 mismatch in blob %s", sha1_to_hex(sha1));
+       object = parse_object_buffer(sha1, type, size, buf, &eaten);
+       if (!object)
+               die("Could not read blob %s", sha1_to_hex(sha1));
 
        mark_next_object(object);
 
@@ -147,7 +150,8 @@ static void handle_object(const unsigned char *sha1)
        show_progress();
 
        object->flags |= SHOWN;
-       free(buf);
+       if (!eaten)
+               free(buf);
 }
 
 static int depth_first(const void *a_, const void *b_)
@@ -312,7 +316,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
        /* Export the referenced blobs, and remember the marks. */
        for (i = 0; i < diff_queued_diff.nr; i++)
                if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
-                       handle_object(diff_queued_diff.queue[i]->two->sha1);
+                       export_blob(diff_queued_diff.queue[i]->two->sha1);
 
        mark_next_object(&commit->object);
        if (!is_encoding_utf8(encoding))
@@ -512,7 +516,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info,
                                commit = (struct commit *)tag;
                                break;
                        case OBJ_BLOB:
-                               handle_object(tag->object.sha1);
+                               export_blob(tag->object.sha1);
                                continue;
                        default: /* OBJ_TAG (nested tags) is already handled */
                                warning("Tag points to object of unexpected type %s, skipping.",
index 940ae35dc2ce4923a248896eb764f145312d71b4..aba44655524ff722d90de09760945f5f30088752 100644 (file)
@@ -7,12 +7,31 @@ static const char fetch_pack_usage[] =
 "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
 "[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
 
+static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
+                                const char *name, int namelen)
+{
+       struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1);
+
+       memcpy(ref->name, name, namelen);
+       ref->name[namelen] = '\0';
+       (*nr)++;
+       ALLOC_GROW(*sought, *nr, *alloc);
+       (*sought)[*nr - 1] = ref;
+}
+
+static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
+                            const char *string)
+{
+       add_sought_entry_mem(sought, nr, alloc, string, strlen(string));
+}
+
 int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
 {
        int i, ret;
        struct ref *ref = NULL;
        const char *dest = NULL;
-       struct string_list sought = STRING_LIST_INIT_DUP;
+       struct ref **sought = NULL;
+       int nr_sought = 0, alloc_sought = 0;
        int fd[2];
        char *pack_lockfile = NULL;
        char **pack_lockfile_ptr = NULL;
@@ -94,27 +113,24 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
         * refs from the standard input:
         */
        for (; i < argc; i++)
-               string_list_append(&sought, xstrdup(argv[i]));
+               add_sought_entry(&sought, &nr_sought, &alloc_sought, argv[i]);
        if (args.stdin_refs) {
                if (args.stateless_rpc) {
                        /* in stateless RPC mode we use pkt-line to read
                         * from stdin, until we get a flush packet
                         */
-                       static char line[1000];
                        for (;;) {
-                               int n = packet_read_line(0, line, sizeof(line));
-                               if (!n)
+                               char *line = packet_read_line(0, NULL);
+                               if (!line)
                                        break;
-                               if (line[n-1] == '\n')
-                                       n--;
-                               string_list_append(&sought, xmemdupz(line, n));
+                               add_sought_entry(&sought, &nr_sought,  &alloc_sought, line);
                        }
                }
                else {
                        /* read from stdin one ref per line, until EOF */
                        struct strbuf line = STRBUF_INIT;
                        while (strbuf_getline(&line, stdin, '\n') != EOF)
-                               string_list_append(&sought, strbuf_detach(&line, NULL));
+                               add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf);
                        strbuf_release(&line);
                }
        }
@@ -128,10 +144,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                   args.verbose ? CONNECT_VERBOSE : 0);
        }
 
-       get_remote_heads(fd[0], &ref, 0, NULL);
+       get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL);
 
        ref = fetch_pack(&args, fd, conn, ref, dest,
-                        &sought, pack_lockfile_ptr);
+                        sought, nr_sought, pack_lockfile_ptr);
        if (pack_lockfile) {
                printf("lock %s\n", pack_lockfile);
                fflush(stdout);
@@ -141,7 +157,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        if (finish_connect(conn))
                return 1;
 
-       ret = !ref || sought.nr;
+       ret = !ref;
 
        /*
         * If the heads to pull were given, we should have consumed
@@ -149,8 +165,13 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
         * remote no-such-ref' would silently succeed without issuing
         * an error.
         */
-       for (i = 0; i < sought.nr; i++)
-               error("no such remote ref %s", sought.items[i].string);
+       for (i = 0; i < nr_sought; i++) {
+               if (!sought[i] || sought[i]->matched)
+                       continue;
+               error("no such remote ref %s", sought[i]->name);
+               ret = 1;
+       }
+
        while (ref) {
                printf("%s %s\n",
                       sha1_to_hex(ref->old_sha1), ref->name);
index b49612f0ce02e0c8503bee77477800297e84065d..265a9253bfd7cacd730789212fdb5284fcd270ac 100644 (file)
@@ -492,7 +492,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
 
                if (size == len)
                        ; /* merely annotated */
-               else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) {
+               else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) {
                        if (!sig.len)
                                strbuf_addstr(&sig, "gpg verification failed.\n");
                }
index 8025964987553b8f1ef21b8319b2085d6d31bb0c..159e65d47a41b56634bc3fb480f9c051d40e692c 100644 (file)
@@ -820,9 +820,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                unsigned char sha1[20];
                /* Is it a rev? */
                if (!get_sha1(arg, sha1)) {
-                       struct object *object = parse_object(sha1);
-                       if (!object)
-                               die(_("bad object %s"), arg);
+                       struct object *object = parse_object_or_die(sha1, arg);
                        if (!seen_dashdash)
                                verify_non_filename(prefix, arg);
                        add_object_array(object, arg, &list);
index ef62124aa491a9ac1e430d5baa3cf448902ac9c4..79dfe47320e244c3110ce65d5b19fa87ebca3cb5 100644 (file)
@@ -78,6 +78,7 @@ static int nr_threads;
 static int from_stdin;
 static int strict;
 static int verbose;
+static int show_stat;
 
 static struct progress *progress;
 
@@ -108,6 +109,10 @@ static pthread_mutex_t work_mutex;
 #define work_lock()            lock_mutex(&work_mutex)
 #define work_unlock()          unlock_mutex(&work_mutex)
 
+static pthread_mutex_t deepest_delta_mutex;
+#define deepest_delta_lock()   lock_mutex(&deepest_delta_mutex)
+#define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex)
+
 static pthread_key_t key;
 
 static inline void lock_mutex(pthread_mutex_t *mutex)
@@ -130,6 +135,8 @@ static void init_thread(void)
        init_recursive_mutex(&read_mutex);
        pthread_mutex_init(&counter_mutex, NULL);
        pthread_mutex_init(&work_mutex, NULL);
+       if (show_stat)
+               pthread_mutex_init(&deepest_delta_mutex, NULL);
        pthread_key_create(&key, NULL);
        thread_data = xcalloc(nr_threads, sizeof(*thread_data));
        threads_active = 1;
@@ -143,6 +150,8 @@ static void cleanup_thread(void)
        pthread_mutex_destroy(&read_mutex);
        pthread_mutex_destroy(&counter_mutex);
        pthread_mutex_destroy(&work_mutex);
+       if (show_stat)
+               pthread_mutex_destroy(&deepest_delta_mutex);
        pthread_key_delete(key);
        free(thread_data);
 }
@@ -158,6 +167,9 @@ static void cleanup_thread(void)
 #define work_lock()
 #define work_unlock()
 
+#define deepest_delta_lock()
+#define deepest_delta_unlock()
+
 #endif
 
 
@@ -833,9 +845,13 @@ static void resolve_delta(struct object_entry *delta_obj,
        void *base_data, *delta_data;
 
        delta_obj->real_type = base->obj->real_type;
-       delta_obj->delta_depth = base->obj->delta_depth + 1;
-       if (deepest_delta < delta_obj->delta_depth)
-               deepest_delta = delta_obj->delta_depth;
+       if (show_stat) {
+               delta_obj->delta_depth = base->obj->delta_depth + 1;
+               deepest_delta_lock();
+               if (deepest_delta < delta_obj->delta_depth)
+                       deepest_delta = delta_obj->delta_depth;
+               deepest_delta_unlock();
+       }
        delta_obj->base_object_no = base->obj - objects;
        delta_data = get_data_from_pack(delta_obj);
        base_data = get_base_data(base);
@@ -951,8 +967,10 @@ static void *threaded_second_pass(void *data)
        set_thread_data(data);
        for (;;) {
                int i;
-               work_lock();
+               counter_lock();
                display_progress(progress, nr_resolved_deltas);
+               counter_unlock();
+               work_lock();
                while (nr_dispatched < nr_objects &&
                       is_delta_type(objects[nr_dispatched].type))
                        nr_dispatched++;
@@ -1107,6 +1125,8 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
                objects = xrealloc(objects,
                                   (nr_objects + nr_unresolved + 1)
                                   * sizeof(*objects));
+               memset(objects + nr_objects + 1, 0,
+                      nr_unresolved * sizeof(*objects));
                f = sha1fd(output_fd, curr_pack);
                fix_unresolved_deltas(f, nr_unresolved);
                strbuf_addf(&msg, _("completed with %d local objects"),
@@ -1463,7 +1483,7 @@ static void show_pack_info(int stat_only)
 
 int cmd_index_pack(int argc, const char **argv, const char *prefix)
 {
-       int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
+       int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
        const char *curr_pack, *curr_index;
        const char *index_name = NULL, *pack_name = NULL;
        const char *keep_name = NULL, *keep_msg = NULL;
@@ -1496,10 +1516,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                verify = 1;
                        } else if (!strcmp(arg, "--verify-stat")) {
                                verify = 1;
-                               stat = 1;
+                               show_stat = 1;
                        } else if (!strcmp(arg, "--verify-stat-only")) {
                                verify = 1;
-                               stat = 1;
+                               show_stat = 1;
                                stat_only = 1;
                        } else if (!strcmp(arg, "--keep")) {
                                keep_msg = "";
@@ -1607,7 +1627,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        if (strict)
                check_objects();
 
-       if (stat)
+       if (show_stat)
                show_pack_info(stat_only);
 
        idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
index 8f0b2e84fef5d1b9c07ea8846c9fbc1318d8d51b..59de484bc29a38fb538e1146a91ddef708ebc3cc 100644 (file)
@@ -1086,7 +1086,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        struct commit *origin = NULL, *head = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
-       char *add_signoff = NULL;
        struct strbuf buf = STRBUF_INIT;
        int use_patch_format = 0;
        int quiet = 0;
@@ -1193,16 +1192,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                rev.subject_prefix = strbuf_detach(&sprefix, NULL);
        }
 
-       if (do_signoff) {
-               const char *committer;
-               const char *endpos;
-               committer = git_committer_info(IDENT_STRICT);
-               endpos = strchr(committer, '>');
-               if (!endpos)
-                       die(_("bogus committer info %s"), committer);
-               add_signoff = xmemdupz(committer, endpos - committer + 1);
-       }
-
        for (i = 0; i < extra_hdr.nr; i++) {
                strbuf_addstr(&buf, extra_hdr.items[i].string);
                strbuf_addch(&buf, '\n');
@@ -1393,7 +1382,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                total++;
                start_number--;
        }
-       rev.add_signoff = add_signoff;
+       rev.add_signoff = do_signoff;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
index 8cb8b9186a3a268630680e4b224d3767017e1e38..85843d4f1728a907aafab9938b4c827990a7695f 100644 (file)
@@ -149,9 +149,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
                const char *name = *argv++;
 
                if (!get_sha1(name, sha1)) {
-                       struct object *object = parse_object(sha1);
-                       if (!object)
-                               die("bad object: %s", name);
+                       struct object *object = parse_object_or_die(sha1, name);
                        add_pending_object(&revs, object, "");
                }
                else
index 42b129d36cf615ed264be0f1bff523a7fc327b12..5e4a0e958f3649c5a5a9f317876e8afd6f6bea6f 100644 (file)
@@ -437,6 +437,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
                        TRANSPORT_PUSH_PRUNE),
                OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
+               OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
+                       TRANSPORT_PUSH_FOLLOW_TAGS),
                OPT_END()
        };
 
index 62ba6e7a3d014be01fe01ed497c083f7e44e9d34..ccebd74f166f5d02e8e6a0bb6033d741f8580f7b 100644 (file)
@@ -754,17 +754,15 @@ static struct command *read_head_info(void)
        struct command *commands = NULL;
        struct command **p = &commands;
        for (;;) {
-               static char line[1000];
+               char *line;
                unsigned char old_sha1[20], new_sha1[20];
                struct command *cmd;
                char *refname;
                int len, reflen;
 
-               len = packet_read_line(0, line, sizeof(line));
-               if (!len)
+               line = packet_read_line(0, &len);
+               if (!line)
                        break;
-               if (line[len-1] == '\n')
-                       line[--len] = 0;
                if (len < 83 ||
                    line[40] != ' ' ||
                    line[81] != ' ' ||
@@ -932,7 +930,7 @@ static void report(struct command *commands, const char *unpack_status)
        if (use_sideband)
                send_sideband(1, 1, buf.buf, buf.len, use_sideband);
        else
-               safe_write(1, buf.buf, buf.len);
+               write_or_die(1, buf.buf, buf.len);
        strbuf_release(&buf);
 }
 
index 57a46b2654aa82b154d32eb77addd6a4f89c8e8d..152c4ea092a44ff9e70e293ddeecb4ad7105e25d 100644 (file)
@@ -79,7 +79,7 @@ static void print_helper_status(struct ref *ref)
                }
                strbuf_addch(&buf, '\n');
 
-               safe_write(1, buf.buf, buf.len);
+               write_or_die(1, buf.buf, buf.len);
        }
        strbuf_release(&buf);
 }
@@ -207,7 +207,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 
        memset(&extra_have, 0, sizeof(extra_have));
 
-       get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have);
+       get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have);
 
        transport_verify_remote_names(nr_refspecs, refspecs);
 
index f8266888cca97f492dc5b52a992b1dc7cab2909e..af3af3f64935c3f92ed3f85acd44ed2ca2a35b3f 100644 (file)
@@ -582,7 +582,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                die(_("%s: cannot lock the ref"), ref.buf);
        if (write_ref_sha1(lock, object, NULL) < 0)
                die(_("%s: cannot update the ref"), ref.buf);
-       if (force && hashcmp(prev, object))
+       if (force && !is_null_sha1(prev) && hashcmp(prev, object))
                printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
 
        strbuf_release(&buf);
index b928beb8ed51c9af9aa4f640e80443a5e059d505..af2da35e7d05f4de2d7a77a708ee3cf6f490266e 100644 (file)
@@ -7,6 +7,7 @@
 #include "pkt-line.h"
 #include "sideband.h"
 #include "run-command.h"
+#include "argv-array.h"
 
 static const char upload_archive_usage[] =
        "git upload-archive <repo>";
@@ -18,51 +19,31 @@ static const char deadchild[] =
 
 int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
 {
-       const char *sent_argv[MAX_ARGS];
+       struct argv_array sent_argv = ARGV_ARRAY_INIT;
        const char *arg_cmd = "argument ";
-       char *p, buf[4096];
-       int sent_argc;
-       int len;
 
        if (argc != 2)
                usage(upload_archive_usage);
 
-       if (strlen(argv[1]) + 1 > sizeof(buf))
-               die("insanely long repository name");
-
-       strcpy(buf, argv[1]); /* enter-repo smudges its argument */
-
-       if (!enter_repo(buf, 0))
-               die("'%s' does not appear to be a git repository", buf);
+       if (!enter_repo(argv[1], 0))
+               die("'%s' does not appear to be a git repository", argv[1]);
 
        /* put received options in sent_argv[] */
-       sent_argc = 1;
-       sent_argv[0] = "git-upload-archive";
-       for (p = buf;;) {
-               /* This will die if not enough free space in buf */
-               len = packet_read_line(0, p, (buf + sizeof buf) - p);
-               if (len == 0)
+       argv_array_push(&sent_argv, "git-upload-archive");
+       for (;;) {
+               char *buf = packet_read_line(0, NULL);
+               if (!buf)
                        break;  /* got a flush */
-               if (sent_argc > MAX_ARGS - 2)
-                       die("Too many options (>%d)", MAX_ARGS - 2);
+               if (sent_argv.argc > MAX_ARGS)
+                       die("Too many options (>%d)", MAX_ARGS - 1);
 
-               if (p[len-1] == '\n') {
-                       p[--len] = 0;
-               }
-               if (len < strlen(arg_cmd) ||
-                   strncmp(arg_cmd, p, strlen(arg_cmd)))
+               if (prefixcmp(buf, arg_cmd))
                        die("'argument' token or flush expected");
-
-               len -= strlen(arg_cmd);
-               memmove(p, p + strlen(arg_cmd), len);
-               sent_argv[sent_argc++] = p;
-               p += len;
-               *p++ = 0;
+               argv_array_push(&sent_argv, buf + strlen(arg_cmd));
        }
-       sent_argv[sent_argc] = NULL;
 
        /* parse all options sent by the client */
-       return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1);
+       return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1);
 }
 
 __attribute__((format (printf, 1, 2)))
index a8eee886a5281965a0660308df9e94d14c1cb2b8..9cdf332333b95b3ab2ea6e133f345105bd67d019 100644 (file)
@@ -29,7 +29,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
        if (size == len)
                return error("no signature found");
 
-       return verify_signed_buffer(buf, len, buf + len, size - len, NULL);
+       return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL);
 }
 
 static int verify_tag(const char *name, int verbose)
index 6210a6be894fb3f5538eb2939c6dc4212382ae2d..505e07e93468b68454c7ec906f40eacea9746551 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -279,12 +279,12 @@ int create_bundle(struct bundle_header *header, const char *path,
                if (buf.len > 0 && buf.buf[0] == '-') {
                        write_or_die(bundle_fd, buf.buf, buf.len);
                        if (!get_sha1_hex(buf.buf + 1, sha1)) {
-                               struct object *object = parse_object(sha1);
+                               struct object *object = parse_object_or_die(sha1, buf.buf);
                                object->flags |= UNINTERESTING;
                                add_pending_object(&revs, object, xstrdup(buf.buf));
                        }
                } else if (!get_sha1_hex(buf.buf, sha1)) {
-                       struct object *object = parse_object(sha1);
+                       struct object *object = parse_object_or_die(sha1, buf.buf);
                        object->flags |= SHOWN;
                }
        }
@@ -361,7 +361,7 @@ int create_bundle(struct bundle_header *header, const char *path,
                                 * end up triggering "empty bundle"
                                 * error.
                                 */
-                               obj = parse_object(sha1);
+                               obj = parse_object_or_die(sha1, e->name);
                                obj->flags |= SHOWN;
                                add_pending_object(&revs, obj, e->name);
                        }
diff --git a/cache.h b/cache.h
index e493563f4c07e6adcd00a1b2476926d69a4a67f8..ec2fd7a30431acb23f00f05e930ab441a244e18b 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -34,6 +34,7 @@ int git_inflate(git_zstream *, int flush);
 
 void git_deflate_init(git_zstream *, int level);
 void git_deflate_init_gzip(git_zstream *, int level);
+void git_deflate_init_raw(git_zstream *, int level);
 void git_deflate_end(git_zstream *);
 int git_deflate_abort(git_zstream *);
 int git_deflate_end_gently(git_zstream *);
@@ -131,7 +132,6 @@ struct cache_entry {
        unsigned int ce_namelen;
        unsigned char sha1[20];
        struct cache_entry *next;
-       struct cache_entry *dir_next;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -267,25 +267,15 @@ struct index_state {
        unsigned name_hash_initialized : 1,
                 initialized : 1;
        struct hash_table name_hash;
+       struct hash_table dir_hash;
 };
 
 extern struct index_state the_index;
 
 /* Name hashing */
 extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
-/*
- * We don't actually *remove* it, we can just mark it invalid so that
- * we won't find it in lookups.
- *
- * Not only would we have to search the lists (simple enough), but
- * we'd also have to rehash other hash buckets in case this makes the
- * hash bucket empty (common). So it's much better to just mark
- * it.
- */
-static inline void remove_name_hash(struct cache_entry *ce)
-{
-       ce->ce_flags |= CE_UNHASHED;
-}
+extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
+extern void free_name_hash(struct index_state *istate);
 
 
 #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
@@ -341,9 +331,11 @@ static inline enum object_type object_type(unsigned int mode)
                OBJ_BLOB;
 }
 
+/* Double-check local_repo_env below if you add to this list. */
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
+#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@ -365,13 +357,24 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
 
 /*
- * Repository-local GIT_* environment variables
- * The array is NULL-terminated to simplify its usage in contexts such
- * environment creation or simple walk of the list.
- * The number of non-NULL entries is available as a macro.
+ * This environment variable is expected to contain a boolean indicating
+ * whether we should or should not treat:
+ *
+ *   GIT_DIR=foo.git git ...
+ *
+ * as if GIT_WORK_TREE=. was given. It's not expected that users will make use
+ * of this, but we use it internally to communicate to sub-processes that we
+ * are in a bare repo. If not set, defaults to true.
  */
-#define LOCAL_REPO_ENV_SIZE 9
-extern const char *const local_repo_env[LOCAL_REPO_ENV_SIZE + 1];
+#define GIT_IMPLICIT_WORK_TREE_ENVIRONMENT "GIT_IMPLICIT_WORK_TREE"
+
+/*
+ * Repository-local GIT_* environment variables; these will be cleared
+ * when git spawns a sub-process that runs inside another repository.
+ * The array is NULL-terminated, which makes it easy to pass in the "env"
+ * parameter of a run-command invocation, or to do a simple walk.
+ */
+extern const char * const local_repo_env[];
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
@@ -1017,7 +1020,8 @@ struct ref {
                force:1,
                forced_update:1,
                merge:1,
-               deletion:1;
+               deletion:1,
+               matched:1;
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
@@ -1049,7 +1053,9 @@ struct extra_have_objects {
        int nr, alloc;
        unsigned char (*array)[20];
 };
-extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
+extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+                                    struct ref **list, unsigned int flags,
+                                    struct extra_have_objects *);
 extern int server_supports(const char *feature);
 extern int parse_feature_request(const char *features, const char *feature);
 extern const char *server_feature_value(const char *feature, int *len_ret);
@@ -1057,6 +1063,9 @@ extern const char *parse_feature_value(const char *feature_list, const char *fea
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
 
+/* A hook for count-objects to report invalid files in pack directory */
+extern void (*report_garbage)(const char *desc, const char *path);
+
 extern void prepare_packed_git(void);
 extern void reprepare_packed_git(void);
 extern void install_packed_git(struct packed_git *pack);
index 35d41cd56d2ddb8df416e5e422552ec473294055..6288485965b5f79c1311e48e8de5c1be97730977 100644 (file)
@@ -5,6 +5,7 @@
 #include "diffcore.h"
 #include "quote.h"
 #include "xdiff-interface.h"
+#include "xdiff/xmacros.h"
 #include "log-tree.h"
 #include "refs.h"
 #include "userdiff.h"
@@ -122,7 +123,47 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode,
        return blob;
 }
 
-static void append_lost(struct sline *sline, int n, const char *line, int len)
+static int match_string_spaces(const char *line1, int len1,
+                              const char *line2, int len2,
+                              long flags)
+{
+       if (flags & XDF_WHITESPACE_FLAGS) {
+               for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--);
+               for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--);
+       }
+
+       if (!(flags & (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE)))
+               return (len1 == len2 && !memcmp(line1, line2, len1));
+
+       while (len1 > 0 && len2 > 0) {
+               len1--;
+               len2--;
+               if (XDL_ISSPACE(line1[len1]) || XDL_ISSPACE(line2[len2])) {
+                       if ((flags & XDF_IGNORE_WHITESPACE_CHANGE) &&
+                           (!XDL_ISSPACE(line1[len1]) || !XDL_ISSPACE(line2[len2])))
+                               return 0;
+
+                       for (; len1 > 0 && XDL_ISSPACE(line1[len1]); len1--);
+                       for (; len2 > 0 && XDL_ISSPACE(line2[len2]); len2--);
+               }
+               if (line1[len1] != line2[len2])
+                       return 0;
+       }
+
+       if (flags & XDF_IGNORE_WHITESPACE) {
+               /* Consume remaining spaces */
+               for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--);
+               for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--);
+       }
+
+       /* We matched full line1 and line2 */
+       if (!len1 && !len2)
+               return 1;
+
+       return 0;
+}
+
+static void append_lost(struct sline *sline, int n, const char *line, int len, long flags)
 {
        struct lline *lline;
        unsigned long this_mask = (1UL<<n);
@@ -133,8 +174,8 @@ static void append_lost(struct sline *sline, int n, const char *line, int len)
        if (sline->lost_head) {
                lline = sline->next_lost;
                while (lline) {
-                       if (lline->len == len &&
-                           !memcmp(lline->line, line, len)) {
+                       if (match_string_spaces(lline->line, lline->len,
+                                               line, len, flags)) {
                                lline->parent_map |= this_mask;
                                sline->next_lost = lline->next;
                                return;
@@ -162,6 +203,7 @@ struct combine_diff_state {
        int n;
        struct sline *sline;
        struct sline *lost_bucket;
+       long flags;
 };
 
 static void consume_line(void *state_, char *line, unsigned long len)
@@ -201,7 +243,7 @@ static void consume_line(void *state_, char *line, unsigned long len)
                return; /* not in any hunk yet */
        switch (line[0]) {
        case '-':
-               append_lost(state->lost_bucket, state->n, line+1, len-1);
+               append_lost(state->lost_bucket, state->n, line+1, len-1, state->flags);
                break;
        case '+':
                state->sline[state->lno-1].flag |= state->nmask;
@@ -215,7 +257,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
                         struct sline *sline, unsigned int cnt, int n,
                         int num_parent, int result_deleted,
                         struct userdiff_driver *textconv,
-                        const char *path)
+                        const char *path, long flags)
 {
        unsigned int p_lno, lno;
        unsigned long nmask = (1UL << n);
@@ -231,9 +273,10 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
        parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path);
        parent_file.size = sz;
        memset(&xpp, 0, sizeof(xpp));
-       xpp.flags = 0;
+       xpp.flags = flags;
        memset(&xecfg, 0, sizeof(xecfg));
        memset(&state, 0, sizeof(state));
+       state.flags = flags;
        state.nmask = nmask;
        state.sline = sline;
        state.lno = 1;
@@ -962,7 +1005,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                                     elem->parent[i].mode,
                                     &result_file, sline,
                                     cnt, i, num_parent, result_deleted,
-                                    textconv, elem->path);
+                                    textconv, elem->path, opt->xdl_opts);
        }
 
        show_hunks = make_hunks(sline, cnt, num_parent, dense);
index e8eb0aec5509a4fd09698bd15a37d1f4e8341f5e..b4512ab0b2ebd008aa90e0dfe00fbc1aa42a5c64 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -463,14 +463,23 @@ static void clear_commit_marks_1(struct commit_list **plist,
        }
 }
 
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark)
 {
        struct commit_list *list = NULL;
-       commit_list_insert(commit, &list);
+
+       while (nr--) {
+               commit_list_insert(*commit, &list);
+               commit++;
+       }
        while (list)
                clear_commit_marks_1(&list, pop_commit(&list), mark);
 }
 
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+       clear_commit_marks_many(1, &commit, mark);
+}
+
 void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
 {
        struct object *object;
@@ -797,8 +806,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
        if (!result || !result->next) {
                if (cleanup) {
                        clear_commit_marks(one, all_flags);
-                       for (i = 0; i < n; i++)
-                               clear_commit_marks(twos[i], all_flags);
+                       clear_commit_marks_many(n, twos, all_flags);
                }
                return result;
        }
@@ -816,8 +824,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
        free_commit_list(result);
 
        clear_commit_marks(one, all_flags);
-       for (i = 0; i < n; i++)
-               clear_commit_marks(twos[i], all_flags);
+       clear_commit_marks_many(n, twos, all_flags);
 
        cnt = remove_redundant(rslt, cnt);
        result = NULL;
@@ -852,25 +859,36 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
 }
 
 /*
- * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ * Is "commit" an ancestor of one of the "references"?
  */
-int in_merge_bases(struct commit *commit, struct commit *reference)
+int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
 {
        struct commit_list *bases;
-       int ret = 0;
+       int ret = 0, i;
 
-       if (parse_commit(commit) || parse_commit(reference))
+       if (parse_commit(commit))
                return ret;
+       for (i = 0; i < nr_reference; i++)
+               if (parse_commit(reference[i]))
+                       return ret;
 
-       bases = paint_down_to_common(commit, 1, &reference);
+       bases = paint_down_to_common(commit, nr_reference, reference);
        if (commit->object.flags & PARENT2)
                ret = 1;
        clear_commit_marks(commit, all_flags);
-       clear_commit_marks(reference, all_flags);
+       clear_commit_marks_many(nr_reference, reference, all_flags);
        free_commit_list(bases);
        return ret;
 }
 
+/*
+ * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ */
+int in_merge_bases(struct commit *commit, struct commit *reference)
+{
+       return in_merge_bases_many(commit, 1, &reference);
+}
+
 struct commit_list *reduce_heads(struct commit_list *heads)
 {
        struct commit_list *p;
index 4138bb4c0850f95ba7215cb62f457cd682495968..2d90d9c27c8fb947e30a368cb5fb266f643b8bae 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -137,6 +137,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
 struct commit *pop_commit(struct commit_list **stack);
 
 void clear_commit_marks(struct commit *commit, unsigned int mark);
+void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
 void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
 
 /*
@@ -176,6 +177,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
 
 int is_descendant_of(struct commit *, struct commit_list *);
 int in_merge_bases(struct commit *, struct commit *);
+int in_merge_bases_many(struct commit *, int, struct commit **);
 
 extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
 extern int run_add_interactive(const char *revision, const char *patch_mode,
index 5428858875a20b89411d3662e777586d9d798fab..871b41d23a7471724a0446ff3333b577a175c579 100644 (file)
@@ -1,3 +1,4 @@
+#define CYGWIN_C
 #define WIN32_LEAN_AND_MEAN
 #ifdef CYGWIN_V15_WIN32API
 #include "../git-compat-util.h"
 #endif
 #include "../cache.h" /* to read configuration */
 
+/*
+ * Return POSIX permission bits, regardless of core.ignorecygwinfstricks
+ */
+int cygwin_get_st_mode_bits(const char *path, int *mode)
+{
+       struct stat st;
+       if (lstat(path, &st) < 0)
+               return -1;
+       *mode = st.st_mode;
+       return 0;
+}
+
 static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
 {
        long long winTime = ((long long)ft->dwHighDateTime << 32) +
index a3229f5b4fbb819ba1beabcf560725e80ad51bb4..c04965a2e0e259d3bded51730f699bde5c4262e6 100644 (file)
@@ -4,6 +4,11 @@
 typedef int (*stat_fn_t)(const char*, struct stat*);
 extern stat_fn_t cygwin_stat_fn;
 extern stat_fn_t cygwin_lstat_fn;
+int cygwin_get_st_mode_bits(const char *path, int *mode);
 
+#define get_st_mode_bits(p,m) cygwin_get_st_mode_bits((p),(m))
+#ifndef CYGWIN_C
+/* cygwin.c needs the original lstat() */
 #define stat(path, buf) (*cygwin_stat_fn)(path, buf)
 #define lstat(path, buf) (*cygwin_lstat_fn)(path, buf)
+#endif
index aa4b56315ae2488ea656d695f0f660e4dbd745ef..96b6d605dad3872f02933581e501ee4380cb8f68 100644 (file)
@@ -12,6 +12,8 @@
 #define __attribute__(x)
 #define strncasecmp  _strnicmp
 #define ftruncate    _chsize
+#define strtoull     _strtoui64
+#define strtoll      _strtoi64
 
 static __inline int strcasecmp (const char *s1, const char *s2)
 {
diff --git a/compat/vcbuild/include/sys/poll.h b/compat/vcbuild/include/sys/poll.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
index b14fcf94da405c6f3267437e59936d598168e00f..c65c2cd566b51473da12786f28074040c410f343 100644 (file)
@@ -49,6 +49,9 @@ typedef int64_t off64_t;
 #define INTMAX_MAX  _I64_MAX
 #define UINTMAX_MAX _UI64_MAX
 
+#define UINT32_MAX 0xffffffff  /* 4294967295U */
+
+#define STDIN_FILENO  0
 #define STDOUT_FILENO 1
 #define STDERR_FILENO 2
 
index fa02bdd82ada7c45b2c666438aa2df9407f5c1af..e6a6d0f941cb2d61586c5d5eddb2ccf27c6439db 100644 (file)
@@ -22,9 +22,3 @@ docdir = @docdir@
 
 mandir = @mandir@
 htmldir = @htmldir@
-
-srcdir = @srcdir@
-VPATH = @srcdir@
-
-export exec_prefix mandir
-export srcdir VPATH
index e09af8fc13ec4c3c6fa326e8208c98be52347a10..9080054f7617017a67ca0ae8414f829a01d3d546 100644 (file)
@@ -326,7 +326,6 @@ ifeq ($(uname_S),Windows)
        # NEEDS_LIBICONV = YesPlease
        NO_ICONV = YesPlease
        NO_STRTOUMAX = YesPlease
-       NO_STRTOULL = YesPlease
        NO_MKDTEMP = YesPlease
        NO_MKSTEMPS = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
@@ -343,6 +342,9 @@ ifeq ($(uname_S),Windows)
        NO_CURL = YesPlease
        NO_PYTHON = YesPlease
        BLK_SHA1 = YesPlease
+       ETAGS_TARGET = ETAGS
+       NO_INET_PTON = YesPlease
+       NO_INET_NTOP = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
        DEFAULT_HELP_FORMAT = html
index 49e56ba35a645359b0d7c1f7bbc9e2108b3424d9..f57efd06c1d7ab01076b67d37ed24e34e17c4ebb 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -62,8 +62,8 @@ static void die_initial_contact(int got_at_least_one_head)
 /*
  * Read all the refs from the other end
  */
-struct ref **get_remote_heads(int in, struct ref **list,
-                             unsigned int flags,
+struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+                             struct ref **list, unsigned int flags,
                              struct extra_have_objects *extra_have)
 {
        int got_at_least_one_head = 0;
@@ -72,18 +72,19 @@ struct ref **get_remote_heads(int in, struct ref **list,
        for (;;) {
                struct ref *ref;
                unsigned char old_sha1[20];
-               static char buffer[1000];
                char *name;
                int len, name_len;
+               char *buffer = packet_buffer;
 
-               len = packet_read(in, buffer, sizeof(buffer));
+               len = packet_read(in, &src_buf, &src_len,
+                                 packet_buffer, sizeof(packet_buffer),
+                                 PACKET_READ_GENTLE_ON_EOF |
+                                 PACKET_READ_CHOMP_NEWLINE);
                if (len < 0)
                        die_initial_contact(got_at_least_one_head);
 
                if (!len)
                        break;
-               if (buffer[len-1] == '\n')
-                       buffer[--len] = 0;
 
                if (len > 4 && !prefixcmp(buffer, "ERR "))
                        die("remote error: %s", buffer + 4);
index 93eba467502282db2697e1cf6c93a6909d35ee5c..2ba1461422c4e6c16e6c85ed3914536e1f4aa56e 100644 (file)
@@ -2419,7 +2419,7 @@ _git_submodule ()
 {
        __git_has_doubledash && return
 
-       local subcommands="add status init update summary foreach sync"
+       local subcommands="add status init deinit update summary foreach sync"
        if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
                case "$cur" in
                --*)
index 41f61c5db3378d234b2d1f5e67d0fc180bea678a..f2cdefee605aebe6c577129ae2c8cd76a3555a96 100644 (file)
@@ -401,7 +401,7 @@ static void usage(const char *name)
        const char *basename = strrchr(name,'/');
 
        basename = (basename) ? basename + 1 : name;
-       fprintf(stderr, "Usage: %s <", basename);
+       fprintf(stderr, "usage: %s <", basename);
        while(try_op->name) {
                fprintf(stderr,"%s",(try_op++)->name);
                if(try_op->name)
diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile
new file mode 100644 (file)
index 0000000..51b7613
--- /dev/null
@@ -0,0 +1,5 @@
+test:
+       ./test.pl
+
+testverbose:
+       ./test.pl -d -v
diff --git a/contrib/credential/netrc/git-credential-netrc b/contrib/credential/netrc/git-credential-netrc
new file mode 100755 (executable)
index 0000000..6c51c43
--- /dev/null
@@ -0,0 +1,421 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use File::Basename;
+
+my $VERSION = "0.1";
+
+my %options = (
+              help => 0,
+              debug => 0,
+              verbose => 0,
+              insecure => 0,
+              file => [],
+
+              # identical token maps, e.g. host -> host, will be inserted later
+              tmap => {
+                       port => 'protocol',
+                       machine => 'host',
+                       path => 'path',
+                       login => 'username',
+                       user => 'username',
+                       password => 'password',
+                      }
+             );
+
+# Map each credential protocol token to itself on the netrc side.
+foreach (values %{$options{tmap}}) {
+       $options{tmap}->{$_} = $_;
+}
+
+# Now, $options{tmap} has a mapping from the netrc format to the Git credential
+# helper protocol.
+
+# Next, we build the reverse token map.
+
+# When $rmap{foo} contains 'bar', that means that what the Git credential helper
+# protocol calls 'bar' is found as 'foo' in the netrc/authinfo file.  Keys in
+# %rmap are what we expect to read from the netrc/authinfo file.
+
+my %rmap;
+foreach my $k (keys %{$options{tmap}}) {
+       push @{$rmap{$options{tmap}->{$k}}}, $k;
+}
+
+Getopt::Long::Configure("bundling");
+
+# TODO: maybe allow the token map $options{tmap} to be configurable.
+GetOptions(\%options,
+           "help|h",
+           "debug|d",
+           "insecure|k",
+           "verbose|v",
+           "file|f=s@",
+          );
+
+if ($options{help}) {
+       my $shortname = basename($0);
+       $shortname =~ s/git-credential-//;
+
+       print <<EOHIPPUS;
+
+$0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] [-v] [-k] get
+
+Version $VERSION by tzz\@lifelogs.com.  License: BSD.
+
+Options:
+
+  -f|--file AUTHFILE : specify netrc-style files.  Files with the .gpg extension
+                       will be decrypted by GPG before parsing.  Multiple -f
+                       arguments are OK.  They are processed in order, and the
+                       first matching entry found is returned via the credential
+                       helper protocol (see below).
+
+                       When no -f option is given, .authinfo.gpg, .netrc.gpg,
+                      .authinfo, and .netrc files in your home directory are used
+                      in this order.
+
+  -k|--insecure      : ignore bad file ownership or permissions
+
+  -d|--debug         : turn on debugging (developer info)
+
+  -v|--verbose       : be more verbose (show files and information found)
+
+To enable this credential helper:
+
+  git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2'
+
+(Note that Git will prepend "git-credential-" to the helper name and look for it
+in the path.)
+
+...and if you want lots of debugging info:
+
+  git config credential.helper '$shortname -f AUTHFILE -d'
+
+...or to see the files opened and data found:
+
+  git config credential.helper '$shortname -f AUTHFILE -v'
+
+Only "get" mode is supported by this credential helper.  It opens every AUTHFILE
+and looks for the first entry that matches the requested search criteria:
+
+ 'port|protocol':
+   The protocol that will be used (e.g., https). (protocol=X)
+
+ 'machine|host':
+   The remote hostname for a network credential. (host=X)
+
+ 'path':
+   The path with which the credential will be used. (path=X)
+
+ 'login|user|username':
+   The credential’s username, if we already have one. (username=X)
+
+Thus, when we get this query on STDIN:
+
+host=github.com
+protocol=https
+username=tzz
+
+this credential helper will look for the first entry in every AUTHFILE that
+matches
+
+machine github.com port https login tzz
+
+OR
+
+machine github.com protocol https login tzz
+
+OR... etc. acceptable tokens as listed above.  Any unknown tokens are
+simply ignored.
+
+Then, the helper will print out whatever tokens it got from the entry, including
+"password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped
+back to "protocol".  Any redundant entry tokens (part of the original query) are
+skipped.
+
+Again, note that only the first matching entry from all the AUTHFILEs, processed
+in the sequence given on the command line, is used.
+
+Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
+
+No caching is performed by this credential helper.
+
+EOHIPPUS
+
+       exit 0;
+}
+
+my $mode = shift @ARGV;
+
+# Credentials must get a parameter, so die if it's missing.
+die "Syntax: $0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] get" unless defined $mode;
+
+# Only support 'get' mode; with any other unsupported ones we just exit.
+exit 0 unless $mode eq 'get';
+
+my $files = $options{file};
+
+# if no files were given, use a predefined list.
+# note that .gpg files come first
+unless (scalar @$files) {
+       my @candidates = qw[
+                                  ~/.authinfo.gpg
+                                  ~/.netrc.gpg
+                                  ~/.authinfo
+                                  ~/.netrc
+                         ];
+
+       $files = $options{file} = [ map { glob $_ } @candidates ];
+}
+
+my $query = read_credential_data_from_stdin();
+
+FILE:
+foreach my $file (@$files) {
+       my $gpgmode = $file =~ m/\.gpg$/;
+       unless (-r $file) {
+               log_verbose("Unable to read $file; skipping it");
+               next FILE;
+       }
+
+       # the following check is copied from Net::Netrc, for non-GPG files
+       # OS/2 and Win32 do not handle stat in a way compatible with this check :-(
+       unless ($gpgmode || $options{insecure} ||
+               $^O eq 'os2'
+               || $^O eq 'MSWin32'
+               || $^O eq 'MacOS'
+               || $^O =~ /^cygwin/) {
+               my @stat = stat($file);
+
+               if (@stat) {
+                       if ($stat[2] & 077) {
+                               log_verbose("Insecure $file (mode=%04o); skipping it",
+                                           $stat[2] & 07777);
+                               next FILE;
+                       }
+
+                       if ($stat[4] != $<) {
+                               log_verbose("Not owner of $file; skipping it");
+                               next FILE;
+                       }
+               }
+       }
+
+       my @entries = load_netrc($file, $gpgmode);
+
+       unless (scalar @entries) {
+               if ($!) {
+                       log_verbose("Unable to open $file: $!");
+               } else {
+                       log_verbose("No netrc entries found in $file");
+               }
+
+               next FILE;
+       }
+
+       my $entry = find_netrc_entry($query, @entries);
+       if ($entry) {
+               print_credential_data($entry, $query);
+               # we're done!
+               last FILE;
+       }
+}
+
+exit 0;
+
+sub load_netrc {
+       my $file = shift @_;
+       my $gpgmode = shift @_;
+
+       my $io;
+       if ($gpgmode) {
+               my @cmd = (qw(gpg --decrypt), $file);
+               log_verbose("Using GPG to open $file: [@cmd]");
+               open $io, "-|", @cmd;
+       } else {
+               log_verbose("Opening $file...");
+               open $io, '<', $file;
+       }
+
+       # nothing to do if the open failed (we log the error later)
+       return unless $io;
+
+       # Net::Netrc does this, but the functionality is merged with the file
+       # detection logic, so we have to extract just the part we need
+       my @netrc_entries = net_netrc_loader($io);
+
+       # these entries will use the credential helper protocol token names
+       my @entries;
+
+       foreach my $nentry (@netrc_entries) {
+               my %entry;
+               my $num_port;
+
+               if (!defined $nentry->{machine}) {
+                       next;
+               }
+               if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) {
+                       $num_port = $nentry->{port};
+                       delete $nentry->{port};
+               }
+
+               # create the new entry for the credential helper protocol
+               $entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry;
+
+               # for "host X port Y" where Y is an integer (captured by
+               # $num_port above), set the host to "X:Y"
+               if (defined $entry{host} && defined $num_port) {
+                       $entry{host} = join(':', $entry{host}, $num_port);
+               }
+
+               push @entries, \%entry;
+       }
+
+       return @entries;
+}
+
+sub net_netrc_loader {
+       my $fh = shift @_;
+       my @entries;
+       my ($mach, $macdef, $tok, @tok);
+
+    LINE:
+       while (<$fh>) {
+               undef $macdef if /\A\n\Z/;
+
+               if ($macdef) {
+                       next LINE;
+               }
+
+               s/^\s*//;
+               chomp;
+
+               while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) {
+                       (my $tok = $+) =~ s/\\(.)/$1/g;
+                       push(@tok, $tok);
+               }
+
+           TOKEN:
+               while (@tok) {
+                       if ($tok[0] eq "default") {
+                               shift(@tok);
+                               $mach = { machine => undef };
+                               next TOKEN;
+                       }
+
+                       $tok = shift(@tok);
+
+                       if ($tok eq "machine") {
+                               my $host = shift @tok;
+                               $mach = { machine => $host };
+                               push @entries, $mach;
+                       } elsif (exists $options{tmap}->{$tok}) {
+                               unless ($mach) {
+                                       log_debug("Skipping token $tok because no machine was given");
+                                       next TOKEN;
+                               }
+
+                               my $value = shift @tok;
+                               unless (defined $value) {
+                                       log_debug("Token $tok had no value, skipping it.");
+                                       next TOKEN;
+                               }
+
+                               # Following line added by rmerrell to remove '/' escape char in .netrc
+                               $value =~ s/\/\\/\\/g;
+                               $mach->{$tok} = $value;
+                       } elsif ($tok eq "macdef") { # we ignore macros
+                               next TOKEN unless $mach;
+                               my $value = shift @tok;
+                               $macdef = 1;
+                       }
+               }
+       }
+
+       return @entries;
+}
+
+sub read_credential_data_from_stdin {
+       # the query: start with every token with no value
+       my %q = map { $_ => undef } values(%{$options{tmap}});
+
+       while (<STDIN>) {
+               next unless m/^([^=]+)=(.+)/;
+
+               my ($token, $value) = ($1, $2);
+               die "Unknown search token $token" unless exists $q{$token};
+               $q{$token} = $value;
+               log_debug("We were given search token $token and value $value");
+       }
+
+       foreach (sort keys %q) {
+               log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)');
+       }
+
+       return \%q;
+}
+
+# takes the search tokens and then a list of entries
+# each entry is a hash reference
+sub find_netrc_entry {
+       my $query = shift @_;
+
+    ENTRY:
+       foreach my $entry (@_)
+       {
+               my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry;
+               foreach my $check (sort keys %$query) {
+                       if (defined $query->{$check}) {
+                               log_debug("compare %s [%s] to [%s] (entry: %s)",
+                                         $check,
+                                         $entry->{$check},
+                                         $query->{$check},
+                                         $entry_text);
+                               unless ($query->{$check} eq $entry->{$check}) {
+                                       next ENTRY;
+                               }
+                       } else {
+                               log_debug("OK: any value satisfies check $check");
+                       }
+               }
+
+               return $entry;
+       }
+
+       # nothing was found
+       return;
+}
+
+sub print_credential_data {
+       my $entry = shift @_;
+       my $query = shift @_;
+
+       log_debug("entry has passed all the search checks");
+ TOKEN:
+       foreach my $git_token (sort keys %$entry) {
+               log_debug("looking for useful token $git_token");
+               # don't print unknown (to the credential helper protocol) tokens
+               next TOKEN unless exists $query->{$git_token};
+
+               # don't print things asked in the query (the entry matches them)
+               next TOKEN if defined $query->{$git_token};
+
+               log_debug("FOUND: $git_token=$entry->{$git_token}");
+               printf "%s=%s\n", $git_token, $entry->{$git_token};
+       }
+}
+sub log_verbose {
+       return unless $options{verbose};
+       printf STDERR @_;
+       printf STDERR "\n";
+}
+
+sub log_debug {
+       return unless $options{debug};
+       printf STDERR @_;
+       printf STDERR "\n";
+}
diff --git a/contrib/credential/netrc/test.netrc b/contrib/credential/netrc/test.netrc
new file mode 100644 (file)
index 0000000..ba119a9
--- /dev/null
@@ -0,0 +1,13 @@
+machine imap login tzz@lifelogs.com port imaps password letmeknow
+machine imap login bob port imaps password bobwillknow
+
+# comment test
+
+machine imap2 login tzz port 1099 password tzzknow
+machine imap2 login bob password bobwillknow
+
+# another command
+
+machine github.com
+  multilinetoken anothervalue
+  login carol password carolknows
diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl
new file mode 100755 (executable)
index 0000000..169b646
--- /dev/null
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Test;
+use IPC::Open2;
+
+BEGIN { plan tests => 15 }
+
+my @global_credential_args = @ARGV;
+my $netrc = './test.netrc';
+print "# Testing insecure file, nothing should be found\n";
+chmod 0644, $netrc;
+my $cred = run_credential(['-f', $netrc, 'get'],
+                         { host => 'github.com' });
+
+ok(scalar keys %$cred, 0, "Got 0 keys from insecure file");
+
+print "# Testing missing file, nothing should be found\n";
+chmod 0644, $netrc;
+$cred = run_credential(['-f', '///nosuchfile///', 'get'],
+                      { host => 'github.com' });
+
+ok(scalar keys %$cred, 0, "Got 0 keys from missing file");
+
+chmod 0600, $netrc;
+
+print "# Testing with invalid data\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+                      "bad data");
+ok(scalar keys %$cred, 4, "Got first found keys with bad data");
+
+print "# Testing netrc file for a missing corovamilkbar entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+                      { host => 'corovamilkbar' });
+
+ok(scalar keys %$cred, 0, "Got no corovamilkbar keys");
+
+print "# Testing netrc file for a github.com entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+                      { host => 'github.com' });
+
+ok(scalar keys %$cred, 2, "Got 2 Github keys");
+
+ok($cred->{password}, 'carolknows', "Got correct Github password");
+ok($cred->{username}, 'carol', "Got correct Github username");
+
+print "# Testing netrc file for a username-specific entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+                      { host => 'imap', username => 'bob' });
+
+ok(scalar keys %$cred, 2, "Got 2 username-specific keys");
+
+ok($cred->{password}, 'bobwillknow', "Got correct user-specific password");
+ok($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
+
+print "# Testing netrc file for a host:port-specific entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+                      { host => 'imap2:1099' });
+
+ok(scalar keys %$cred, 2, "Got 2 host:port-specific keys");
+
+ok($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
+ok($cred->{username}, 'tzz', "Got correct host:port-specific username");
+
+print "# Testing netrc file that 'host:port kills host' entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+                      { host => 'imap2' });
+
+ok(scalar keys %$cred, 2, "Got 2 'host:port kills host' keys");
+
+ok($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
+ok($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+
+sub run_credential
+{
+       my $args = shift @_;
+       my $data = shift @_;
+       my $pid = open2(my $chld_out, my $chld_in,
+                       './git-credential-netrc', @global_credential_args,
+                       @$args);
+
+       die "Couldn't open pipe to netrc credential helper: $!" unless $pid;
+
+       if (ref $data eq 'HASH')
+       {
+               print $chld_in "$_=$data->{$_}\n" foreach sort keys %$data;
+       }
+       else
+       {
+               print $chld_in "$data\n";
+       }
+
+       close $chld_in;
+       my %ret;
+
+       while (<$chld_out>)
+       {
+               chomp;
+               next unless m/^([^=]+)=(.+)/;
+
+               $ret{$1} = $2;
+       }
+
+       return \%ret;
+}
index 6beed123ab1f46f43b35059384d6ecfdc571ee7c..3940202b36ce00a77bdc2e17a6002dac158afcb6 100644 (file)
@@ -154,7 +154,7 @@ static void read_credential(void)
 int main(int argc, const char **argv)
 {
        const char *usage =
-               "Usage: git credential-osxkeychain <get|store|erase>";
+               "usage: git credential-osxkeychain <get|store|erase>";
 
        if (!argv[1])
                die(usage);
index dac19eac81665b4f809688255b2eb9aca6874f20..a1d38f035bea404aa9a5fa37518f0af21143dbf0 100644 (file)
@@ -259,7 +259,7 @@ static void read_credential(void)
 int main(int argc, char *argv[])
 {
        const char *usage =
-           "Usage: git credential-wincred <get|store|erase>\n";
+           "usage: git credential-wincred <get|store|erase>\n";
 
        if (!argv[1])
                die(usage);
index b17952a785d9e45dea8a71c211f76f0271e659da..d42df7b4188672e01dd7c95d5dcd73939f0fd600 100755 (executable)
@@ -347,7 +347,7 @@ sub rm_remote {
 }
 
 sub add_usage {
-       print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
+       print STDERR "usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
        exit(1);
 }
 
@@ -380,7 +380,7 @@ sub add_usage {
                }
        }
        if ($i >= @ARGV) {
-               print STDERR "Usage: git remote show <remote>\n";
+               print STDERR "usage: git remote show <remote>\n";
                exit(1);
        }
        my $status = 0;
@@ -410,7 +410,7 @@ sub add_usage {
                }
        }
        if ($i >= @ARGV) {
-               print STDERR "Usage: git remote prune <remote>\n";
+               print STDERR "usage: git remote prune <remote>\n";
                exit(1);
        }
        my $status = 0;
@@ -458,13 +458,13 @@ sub add_usage {
 }
 elsif ($ARGV[0] eq 'rm') {
        if (@ARGV <= 1) {
-               print STDERR "Usage: git remote rm <remote>\n";
+               print STDERR "usage: git remote rm <remote>\n";
                exit(1);
        }
        exit(rm_remote($ARGV[1]));
 }
 else {
-       print STDERR "Usage: git remote\n";
+       print STDERR "usage: git remote\n";
        print STDERR "       git remote add <name> <url>\n";
        print STDERR "       git remote rm <name>\n";
        print STDERR "       git remote show <name>\n";
index b09ff8f12f7e5b5b6faeaf857d7c61973de8590e..c414f0d9c7ecfac7074d0e052c19f73cae4344d1 100755 (executable)
@@ -36,7 +36,7 @@
 
 sub usage() {
        print STDERR <<END;
-Usage: ${\basename $0}     # fetch/update GIT from SVN
+usage: ${\basename $0}     # fetch/update GIT from SVN
        [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-R repack_each_revs]
        [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
        [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
index f9fef6db28dbba0ef67589f05eeb937760d2facf..0891b9e366725cc7bbf9ce996839fdf182a6422c 100755 (executable)
@@ -7,7 +7,7 @@
 use strict;
 use File::Find;
 
-my $USAGE = 'Usage: git-import branch import-message';
+my $USAGE = 'usage: git-import branch import-message';
 my $branch = shift or die "$USAGE\n";
 my $message = shift or die "$USAGE\n";
 
index 0ca7718d0518db2e559ecd17eb6f7f57338b80fd..f8d803c5e2bea65f1fb458d23f80bd7717f2a8c9 100755 (executable)
@@ -5,7 +5,7 @@
 # but is meant to be a simple fast-import example.
 
 if [ -z "$1" -o -z "$2" ]; then
-       echo "Usage: git-import branch import-message"
+       echo "usage: git-import branch import-message"
        exit 1
 fi
 
index 5cec9b012994de61ba6ed1e4c17992edbfd8a07d..d12c2962230b80b5e6310ade8424d9b60cc37d20 100755 (executable)
 from zipfile import ZipFile
 
 if hexversion < 0x01060000:
-        # The limiter is the zipfile module
-        sys.stderr.write("import-zips.py: requires Python 1.6.0 or later.\n")
-        sys.exit(1)
+    # The limiter is the zipfile module
+    stderr.write("import-zips.py: requires Python 1.6.0 or later.\n")
+    exit(1)
 
 if len(argv) < 2:
-       print 'Usage:', argv[0], '<zipfile>...'
-       exit(1)
+    print 'usage:', argv[0], '<zipfile>...'
+    exit(1)
 
 branch_ref = 'refs/heads/import-zips'
 committer_name = 'Z Ip Creator'
 
 fast_import = popen('git fast-import --quiet', 'w')
 def printlines(list):
-       for str in list:
-               fast_import.write(str + "\n")
+    for str in list:
+        fast_import.write(str + "\n")
 
 for zipfile in argv[1:]:
-       commit_time = 0
-       next_mark = 1
-       common_prefix = None
-       mark = dict()
-
-       zip = ZipFile(zipfile, 'r')
-       for name in zip.namelist():
-               if name.endswith('/'):
-                       continue
-               info = zip.getinfo(name)
-
-               if commit_time < info.date_time:
-                       commit_time = info.date_time
-               if common_prefix == None:
-                       common_prefix = name[:name.rfind('/') + 1]
-               else:
-                       while not name.startswith(common_prefix):
-                               last_slash = common_prefix[:-1].rfind('/') + 1
-                               common_prefix = common_prefix[:last_slash]
-
-               mark[name] = ':' + str(next_mark)
-               next_mark += 1
-
-               printlines(('blob', 'mark ' + mark[name], \
-                                       'data ' + str(info.file_size)))
-               fast_import.write(zip.read(name) + "\n")
-
-       committer = committer_name + ' <' + committer_email + '> %d +0000' % \
-               mktime(commit_time + (0, 0, 0))
-
-       printlines(('commit ' + branch_ref, 'committer ' + committer, \
-               'data <<EOM', 'Imported from ' + zipfile + '.', 'EOM', \
-               '', 'deleteall'))
-
-       for name in mark.keys():
-               fast_import.write('M 100644 ' + mark[name] + ' ' +
-                       name[len(common_prefix):] + "\n")
-
-       printlines(('',  'tag ' + path.basename(zipfile), \
-               'from ' + branch_ref, 'tagger ' + committer, \
-               'data <<EOM', 'Package ' + zipfile, 'EOM', ''))
+    commit_time = 0
+    next_mark = 1
+    common_prefix = None
+    mark = dict()
+
+    zip = ZipFile(zipfile, 'r')
+    for name in zip.namelist():
+        if name.endswith('/'):
+            continue
+        info = zip.getinfo(name)
+
+        if commit_time < info.date_time:
+            commit_time = info.date_time
+        if common_prefix == None:
+            common_prefix = name[:name.rfind('/') + 1]
+        else:
+            while not name.startswith(common_prefix):
+                last_slash = common_prefix[:-1].rfind('/') + 1
+                common_prefix = common_prefix[:last_slash]
+
+        mark[name] = ':' + str(next_mark)
+        next_mark += 1
+
+        printlines(('blob', 'mark ' + mark[name], \
+                    'data ' + str(info.file_size)))
+        fast_import.write(zip.read(name) + "\n")
+
+    committer = committer_name + ' <' + committer_email + '> %d +0000' % \
+        mktime(commit_time + (0, 0, 0))
+
+    printlines(('commit ' + branch_ref, 'committer ' + committer, \
+        'data <<EOM', 'Imported from ' + zipfile + '.', 'EOM', \
+        '', 'deleteall'))
+
+    for name in mark.keys():
+        fast_import.write('M 100644 ' + mark[name] + ' ' +
+            name[len(common_prefix):] + "\n")
+
+    printlines(('',  'tag ' + path.basename(zipfile), \
+        'from ' + branch_ref, 'tagger ' + committer, \
+        'data <<EOM', 'Package ' + zipfile, 'EOM', ''))
 
 if fast_import.close():
-       exit(1)
+    exit(1)
index a577ad095f1cfea1c11efe53c4cd82625e508594..2770a1b1d205ee6b6edaec291a7dce3fc417027f 100644 (file)
@@ -24,7 +24,7 @@
 use File::Basename;
 
 my $usage =
-"Usage: setgitperms.perl [OPTION]... <--read|--write>
+"usage: setgitperms.perl [OPTION]... <--read|--write>
 This program uses a file `.gitmeta` to store/restore permissions and uid/gid
 info for all files/dirs tracked by git in the repository.
 
index c6d6fa3aefafa8f782be20b7919f2acfc7667a6f..70a53f67fd06bd2ce6f2f544655b03b99c6b2ee9 100755 (executable)
@@ -15,7 +15,7 @@ fi
 
 . "$WIKI_TEST_DIR"/test-gitmw-lib.sh
 usage () {
-       echo "Usage: "
+       echo "usage: "
        echo "  ./install-wiki.sh <install | delete | --help>"
        echo "          install | -i :  Install a wiki on your computer."
        echo "          delete | -d : Delete the wiki and all its pages and "
index df8c0ab0588e70ad6e6f56195535030055782d85..6aeddcb98d2694a705d34e80293fc81a20bc39a8 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -600,7 +600,7 @@ static void parse_host_arg(char *extra_args, int buflen)
 
 static int execute(void)
 {
-       static char line[1000];
+       char *line = packet_buffer;
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
 
@@ -608,7 +608,7 @@ static int execute(void)
                loginfo("Connection from %s:%s", addr, port);
 
        alarm(init_timeout ? init_timeout : timeout);
-       pktlen = packet_read_line(0, line, sizeof(line));
+       pktlen = packet_read(0, NULL, NULL, packet_buffer, sizeof(packet_buffer), 0);
        alarm(0);
 
        len = strlen(line);
diff --git a/date.c b/date.c
index 57331ed406e2391e0a2bf327fbb86d3b62012324..df20d0ba1d9c5d9fa03316968c1b7038659add05 100644 (file)
--- a/date.c
+++ b/date.c
@@ -383,7 +383,7 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now,
                 * sense to specify timestamp way into the future.  Make
                 * sure it is not later than ten days from now...
                 */
-               if (now + 10*24*3600 < specified)
+               if ((specified != -1) && (now + 10*24*3600 < specified))
                        return 0;
                tm->tm_mon = r->tm_mon;
                tm->tm_mday = r->tm_mday;
@@ -694,8 +694,14 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
 
        /* mktime uses local timezone */
        *timestamp = tm_to_time_t(&tm);
-       if (*offset == -1)
-               *offset = ((time_t)*timestamp - mktime(&tm)) / 60;
+       if (*offset == -1) {
+               time_t temp_time = mktime(&tm);
+               if ((time_t)*timestamp > temp_time) {
+                       *offset = ((time_t)*timestamp - temp_time) / 60;
+               } else {
+                       *offset = -(int)((temp_time - (time_t)*timestamp) / 60);
+               }
+       }
 
        if (*timestamp == -1)
                return -1;
diff --git a/diff.c b/diff.c
index 052974eb9729cca92c9e86be6e71f46052bcb788..db952a5bc71ac967cb045bada8c71f5fda218ca2 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1264,6 +1264,7 @@ static char *pprint_rename(const char *a, const char *b)
        const char *new = b;
        struct strbuf name = STRBUF_INIT;
        int pfx_length, sfx_length;
+       int pfx_adjust_for_slash;
        int len_a = strlen(a);
        int len_b = strlen(b);
        int a_midlen, b_midlen;
@@ -1290,7 +1291,18 @@ static char *pprint_rename(const char *a, const char *b)
        old = a + len_a;
        new = b + len_b;
        sfx_length = 0;
-       while (a <= old && b <= new && *old == *new) {
+       /*
+        * If there is a common prefix, it must end in a slash.  In
+        * that case we let this loop run 1 into the prefix to see the
+        * same slash.
+        *
+        * If there is no common prefix, we cannot do this as it would
+        * underrun the input strings.
+        */
+       pfx_adjust_for_slash = (pfx_length ? 1 : 0);
+       while (a + pfx_length - pfx_adjust_for_slash <= old &&
+              b + pfx_length - pfx_adjust_for_slash <= new &&
+              *old == *new) {
                if (*old == '/')
                        sfx_length = len_a - (old - a);
                old--;
index 512d0ac5fd2bc0acfb57147a6eb77f61f92b7c7e..6c7a72fbe74e8dd6fca07d2555cdea62c16b8b1e 100644 (file)
@@ -389,6 +389,7 @@ static int find_exact_renames(struct diff_options *options)
        struct hash_table file_table;
 
        init_hash(&file_table);
+       preallocate_hash(&file_table, rename_src_nr + rename_dst_nr);
        for (i = 0; i < rename_src_nr; i++)
                insert_file_table(&file_table, -1, i, rename_src[i].p->one);
 
diff --git a/entry.c b/entry.c
index 17a6bccec64e0e523aacc124611c43bd818372e3..63c52edf600b5fd966e24cc5758bdab7dbeaf41b 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -145,7 +145,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        struct stat st;
 
        if (ce_mode_s_ifmt == S_IFREG) {
-               struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+               struct stream_filter *filter = get_stream_filter(ce->name, ce->sha1);
                if (filter &&
                    !streaming_write_entry(ce, path, filter,
                                           state, to_tempfile,
index 89d6c70c15a95aff82f0063e3c0dd6bdab026213..e2e75c16602d8e5841c2461defaba7b9866115d8 100644 (file)
@@ -83,20 +83,20 @@ static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
 
 /*
- * Repository-local GIT_* environment variables
- * Remember to update local_repo_env_size in cache.h when
- * the size of the list changes
+ * Repository-local GIT_* environment variables; see cache.h for details.
  */
-const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
+const char * const local_repo_env[] = {
        ALTERNATE_DB_ENVIRONMENT,
        CONFIG_ENVIRONMENT,
        CONFIG_DATA_ENVIRONMENT,
        DB_ENVIRONMENT,
        GIT_DIR_ENVIRONMENT,
        GIT_WORK_TREE_ENVIRONMENT,
+       GIT_IMPLICIT_WORK_TREE_ENVIRONMENT,
        GRAFT_ENVIRONMENT,
        INDEX_ENVIRONMENT,
        NO_REPLACE_OBJECTS_ENVIRONMENT,
+       GIT_PREFIX_ENVIRONMENT,
        NULL
 };
 
index a0c2c2ff14f471c4ffa6ff1f4ec9085562378ad1..5f539d7d8f7e087423734fb1d19e0d5e580fbe6e 100644 (file)
@@ -2465,6 +2465,7 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
                hashcpy(sha1, oe->idx.sha1);
        } else if (!prefixcmp(p, "inline ")) {
                inline_data = 1;
+               oe = NULL; /* not used with inline_data, but makes gcc happy */
                p += strlen("inline");  /* advance to space */
        } else {
                if (get_sha1_hex(p, sha1))
index 6d8926a5504a6cd93e19860f85d58816bdd5c222..f156dd4fac30cda4e09c508b7091cdb8d96917e2 100644 (file)
@@ -36,7 +36,7 @@ static int marked;
 #define MAX_IN_VAIN 256
 
 static struct commit_list *rev_list;
-static int non_common_revs, multi_ack, use_sideband;
+static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want;
 
 static void rev_list_push(struct commit *commit, int mark)
 {
@@ -172,8 +172,8 @@ static void consume_shallow_list(struct fetch_pack_args *args, int fd)
                 * shallow and unshallow commands every time there
                 * is a block of have lines exchanged.
                 */
-               char line[1000];
-               while (packet_read_line(fd, line, sizeof(line))) {
+               char *line;
+               while ((line = packet_read_line(fd, NULL))) {
                        if (!prefixcmp(line, "shallow "))
                                continue;
                        if (!prefixcmp(line, "unshallow "))
@@ -215,17 +215,17 @@ static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
 
 static enum ack_type get_ack(int fd, unsigned char *result_sha1)
 {
-       static char line[1000];
-       int len = packet_read_line(fd, line, sizeof(line));
+       int len;
+       char *line = packet_read_line(fd, &len);
 
        if (!len)
                die("git fetch-pack: expected ACK/NAK, got EOF");
-       if (line[len-1] == '\n')
-               line[--len] = 0;
        if (!strcmp(line, "NAK"))
                return NAK;
        if (!prefixcmp(line, "ACK ")) {
                if (!get_sha1_hex(line+4, result_sha1)) {
+                       if (len < 45)
+                               return ACK;
                        if (strstr(line+45, "continue"))
                                return ACK_continue;
                        if (strstr(line+45, "common"))
@@ -245,7 +245,7 @@ static void send_request(struct fetch_pack_args *args,
                send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
                packet_flush(fd);
        } else
-               safe_write(fd, buf->buf, buf->len);
+               write_or_die(fd, buf->buf, buf->len);
 }
 
 static void insert_one_alternate_ref(const struct ref *ref, void *unused)
@@ -346,11 +346,11 @@ static int find_common(struct fetch_pack_args *args,
        state_len = req_buf.len;
 
        if (args->depth > 0) {
-               char line[1024];
+               char *line;
                unsigned char sha1[20];
 
                send_request(args, fd[1], &req_buf);
-               while (packet_read_line(fd[0], line, sizeof(line))) {
+               while ((line = packet_read_line(fd[0], NULL))) {
                        if (!prefixcmp(line, "shallow ")) {
                                if (get_sha1_hex(line + 8, sha1))
                                        die("invalid shallow line: %s", line);
@@ -520,47 +520,37 @@ static void mark_recent_complete_commits(struct fetch_pack_args *args,
        }
 }
 
-static int non_matching_ref(struct string_list_item *item, void *unused)
-{
-       if (item->util) {
-               item->util = NULL;
-               return 0;
-       }
-       else
-               return 1;
-}
-
 static void filter_refs(struct fetch_pack_args *args,
-                       struct ref **refs, struct string_list *sought)
+                       struct ref **refs,
+                       struct ref **sought, int nr_sought)
 {
        struct ref *newlist = NULL;
        struct ref **newtail = &newlist;
        struct ref *ref, *next;
-       int sought_pos;
+       int i;
 
-       sought_pos = 0;
+       i = 0;
        for (ref = *refs; ref; ref = next) {
                int keep = 0;
                next = ref->next;
+
                if (!memcmp(ref->name, "refs/", 5) &&
                    check_refname_format(ref->name + 5, 0))
                        ; /* trash */
                else {
-                       while (sought_pos < sought->nr) {
-                               int cmp = strcmp(ref->name, sought->items[sought_pos].string);
+                       while (i < nr_sought) {
+                               int cmp = strcmp(ref->name, sought[i]->name);
                                if (cmp < 0)
                                        break; /* definitely do not have it */
                                else if (cmp == 0) {
                                        keep = 1; /* definitely have it */
-                                       sought->items[sought_pos++].util = "matched";
-                                       break;
+                                       sought[i]->matched = 1;
                                }
-                               else
-                                       sought_pos++; /* might have it; keep looking */
+                               i++;
                        }
                }
 
-               if (! keep && args->fetch_all &&
+               if (!keep && args->fetch_all &&
                    (!args->depth || prefixcmp(ref->name, "refs/tags/")))
                        keep = 1;
 
@@ -573,7 +563,21 @@ static void filter_refs(struct fetch_pack_args *args,
                }
        }
 
-       filter_string_list(sought, 0, non_matching_ref, NULL);
+       /* Append unmatched requests to the list */
+       if (allow_tip_sha1_in_want) {
+               for (i = 0; i < nr_sought; i++) {
+                       ref = sought[i];
+                       if (ref->matched)
+                               continue;
+                       if (get_sha1_hex(ref->name, ref->old_sha1))
+                               continue;
+
+                       ref->matched = 1;
+                       *newtail = ref;
+                       ref->next = NULL;
+                       newtail = &ref->next;
+               }
+       }
        *refs = newlist;
 }
 
@@ -583,7 +587,8 @@ static void mark_alternate_complete(const struct ref *ref, void *unused)
 }
 
 static int everything_local(struct fetch_pack_args *args,
-                           struct ref **refs, struct string_list *sought)
+                           struct ref **refs,
+                           struct ref **sought, int nr_sought)
 {
        struct ref *ref;
        int retval;
@@ -637,7 +642,7 @@ static int everything_local(struct fetch_pack_args *args,
                }
        }
 
-       filter_refs(args, refs, sought);
+       filter_refs(args, refs, sought, nr_sought);
 
        for (retval = 1, ref = *refs; ref ; ref = ref->next) {
                const unsigned char *remote = ref->old_sha1;
@@ -767,10 +772,17 @@ static int get_pack(struct fetch_pack_args *args,
        return 0;
 }
 
+static int cmp_ref_by_name(const void *a_, const void *b_)
+{
+       const struct ref *a = *((const struct ref **)a_);
+       const struct ref *b = *((const struct ref **)b_);
+       return strcmp(a->name, b->name);
+}
+
 static struct ref *do_fetch_pack(struct fetch_pack_args *args,
                                 int fd[2],
                                 const struct ref *orig_ref,
-                                struct string_list *sought,
+                                struct ref **sought, int nr_sought,
                                 char **pack_lockfile)
 {
        struct ref *ref = copy_ref_list(orig_ref);
@@ -779,6 +791,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
        int agent_len;
 
        sort_ref_list(&ref, ref_compare_name);
+       qsort(sought, nr_sought, sizeof(*sought), cmp_ref_by_name);
 
        if (is_repository_shallow() && !server_supports("shallow"))
                die("Server does not support shallow clients");
@@ -808,6 +821,11 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
                        fprintf(stderr, "Server supports side-band\n");
                use_sideband = 1;
        }
+       if (server_supports("allow-tip-sha1-in-want")) {
+               if (args->verbose)
+                       fprintf(stderr, "Server supports allow-tip-sha1-in-want\n");
+               allow_tip_sha1_in_want = 1;
+       }
        if (!server_supports("thin-pack"))
                args->use_thin_pack = 0;
        if (!server_supports("no-progress"))
@@ -827,7 +845,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
                                agent_len, agent_feature);
        }
 
-       if (everything_local(args, &ref, sought)) {
+       if (everything_local(args, &ref, sought, nr_sought)) {
                packet_flush(fd[1]);
                goto all_done;
        }
@@ -890,11 +908,32 @@ static void fetch_pack_setup(void)
        did_setup = 1;
 }
 
+static int remove_duplicates_in_refs(struct ref **ref, int nr)
+{
+       struct string_list names = STRING_LIST_INIT_NODUP;
+       int src, dst;
+
+       for (src = dst = 0; src < nr; src++) {
+               struct string_list_item *item;
+               item = string_list_insert(&names, ref[src]->name);
+               if (item->util)
+                       continue; /* already have it */
+               item->util = ref[src];
+               if (src != dst)
+                       ref[dst] = ref[src];
+               dst++;
+       }
+       for (src = dst; src < nr; src++)
+               ref[src] = NULL;
+       string_list_clear(&names, 0);
+       return dst;
+}
+
 struct ref *fetch_pack(struct fetch_pack_args *args,
                       int fd[], struct child_process *conn,
                       const struct ref *ref,
                       const char *dest,
-                      struct string_list *sought,
+                      struct ref **sought, int nr_sought,
                       char **pack_lockfile)
 {
        struct stat st;
@@ -906,16 +945,14 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
                        st.st_mtime = 0;
        }
 
-       if (sought->nr) {
-               sort_string_list(sought);
-               string_list_remove_duplicates(sought, 0);
-       }
+       if (nr_sought)
+               nr_sought = remove_duplicates_in_refs(sought, nr_sought);
 
        if (!ref) {
                packet_flush(fd[1]);
                die("no matching remote head");
        }
-       ref_cpy = do_fetch_pack(args, fd, ref, sought, pack_lockfile);
+       ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
 
        if (args->depth > 0) {
                static struct lock_file lock;
index cb148719bfd3bace27a0ca9611f1c38066fb6ed4..dc5266c970655a9fe4f971a0132c14e1cf731acc 100644 (file)
@@ -20,17 +20,16 @@ struct fetch_pack_args {
 };
 
 /*
- * sought contains the full names of remote references that should be
- * updated from.  On return, the names that were found on the remote
- * will have been removed from the list.  The util members of the
- * string_list_items are used internally; they must be NULL on entry
- * (and will be NULL on exit).
+ * sought represents remote references that should be updated from.
+ * On return, the names that were found on the remote will have been
+ * marked as such.
  */
 struct ref *fetch_pack(struct fetch_pack_args *args,
                       int fd[], struct child_process *conn,
                       const struct ref *ref,
                       const char *dest,
-                      struct string_list *sought,
+                      struct ref **sought,
+                      int nr_sought,
                       char **pack_lockfile);
 
 #endif
index 202130f888bee14e73b8cc108f39f2da8a14d23b..c092855dd7c277d41288ee89f033bef0a8677a9f 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -778,13 +778,6 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
            action=yes
        fi
 
-       if test -f "$dotest/final-commit"
-       then
-               FIRSTLINE=$(sed 1q "$dotest/final-commit")
-       else
-               FIRSTLINE=""
-       fi
-
        if test $action = skip
        then
                go_next
@@ -797,6 +790,13 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
                stop_here $this
        fi
 
+       if test -f "$dotest/final-commit"
+       then
+               FIRSTLINE=$(sed 1q "$dotest/final-commit")
+       else
+               FIRSTLINE=""
+       fi
+
        say "$(eval_gettext "Applying: \$FIRSTLINE")"
 
        case "$resolved" in
index bc32f18d6d9cbf915eb7797817390adbbc111506..9cb123a07df88c975cf1b0d6c5832cb410175dd0 100755 (executable)
@@ -75,7 +75,7 @@ =head1 Devel Notes
 
 sub usage() {
     print STDERR <<END;
-Usage: git archimport     # fetch/update GIT from Arch
+usage: git archimport     # fetch/update GIT from Arch
        [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ] [ -D depth ] [ -t tempdir ]
        repository/arch-branch [ repository/arch-branch] ...
 END
index e715285e7c2e0a3d0b959505921b344dda55089d..cde442fb5f47cd13757e09ae435963bc061db083 100644 (file)
@@ -86,6 +86,9 @@
 #define _SGI_SOURCE 1
 
 #ifdef WIN32 /* Both MinGW and MSVC */
+# if defined (_MSC_VER)
+#  define _WIN32_WINNT 0x0502
+# endif
 #define WIN32_LEAN_AND_MEAN  /* stops windows.h including winsock.h */
 #include <winsock2.h>
 #include <windows.h>
 typedef long intptr_t;
 typedef unsigned long uintptr_t;
 #endif
+int get_st_mode_bits(const char *path, int *mode);
 #if defined(__CYGWIN__)
 #undef _XOPEN_SOURCE
 #include <grp.h>
index e6bf25232c4ec27fc98f78e076678ae4d0a524c1..d13f02da95f3b9b3921c3ccff9e3b6a7511cd666 100755 (executable)
 
 sub usage {
        print STDERR <<END;
-Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-k] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
+usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-k] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
 END
        exit(1);
 }
index 344f1206d111a57ec529c34785ff8b1a6537e4d1..73d367cea8f130bc7721803e397df710e7cfff58 100755 (executable)
@@ -38,7 +38,7 @@ (;$)
        my $msg = shift;
        print(STDERR "Error: $msg\n") if $msg;
        print STDERR <<END;
-Usage: git cvsimport     # fetch/update GIT from CVS
+usage: git cvsimport     # fetch/update GIT from CVS
        [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
        [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
        [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
index 36790749830251854fa475f2bd8d33ece07203f6..f1c3f49a836a8cf7f980bd3bc99d4fe9c2d49b65 100755 (executable)
 $log->info("--------------- STARTING -----------------");
 
 my $usage =
-    "Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
+    "usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
     "    --base-path <path>  : Prepend to requested CVSROOT\n".
     "                          Can be read from GIT_CVSSERVER_BASE_PATH\n".
     "    --strict-paths      : Don't allow recursing into subdirectories\n".
index 0a90de414646b901e13abbf89aa78ae71e8b12f0..663640d33cb99a98135b38b95947416b4a8d49b9 100755 (executable)
@@ -83,6 +83,21 @@ sub exit_cleanup
        exit($status | ($status >> 8));
 }
 
+sub use_wt_file
+{
+       my ($repo, $workdir, $file, $sha1, $symlinks) = @_;
+       my $null_sha1 = '0' x 40;
+
+       if ($sha1 eq $null_sha1) {
+               return 1;
+       } elsif (not $symlinks) {
+               return 0;
+       }
+
+       my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file");
+       return $sha1 eq $wt_sha1;
+}
+
 sub setup_dir_diff
 {
        my ($repo, $workdir, $symlinks) = @_;
@@ -159,10 +174,10 @@ sub setup_dir_diff
                }
 
                if ($rmode ne $null_mode) {
-                       if ($rsha1 ne $null_sha1) {
-                               $rindex .= "$rmode $rsha1\t$dst_path\0";
-                       } else {
+                       if (use_wt_file($repo, $workdir, $dst_path, $rsha1, $symlinks)) {
                                push(@working_tree, $dst_path);
+                       } else {
+                               $rindex .= "$rmode $rsha1\t$dst_path\0";
                        }
                }
        }
@@ -209,7 +224,9 @@ sub setup_dir_diff
        delete($ENV{GIT_INDEX_FILE});
 
        # Changes in the working tree need special treatment since they are
-       # not part of the index
+       # not part of the index. Remove any trailing slash from $workdir
+       # before starting to avoid double slashes in symlink targets.
+       $workdir =~ s|/$||;
        for my $file (@working_tree) {
                my $dir = dirname($file);
                unless (-d "$rdir/$dir") {
@@ -336,7 +353,7 @@ sub main
        }
        if ($opts{gui}) {
                my $guitool = Git::config('diff.guitool');
-               if (length($guitool) > 0) {
+               if (defined($guitool) && length($guitool) > 0) {
                        $ENV{GIT_DIFF_TOOL} = $guitool;
                }
        }
index f612cb847aca981e16cb6006fa08d870497c6263..07dfeb8df4bf910c5c79fb4c3dd3345ba4719da0 100755 (executable)
@@ -18,7 +18,7 @@
 
 USAGE='<orig blob> <our blob> <their blob> <path>'
 USAGE="$USAGE <orig mode> <our mode> <their mode>"
-LONG_USAGE="Usage: git merge-one-file $USAGE
+LONG_USAGE="usage: git merge-one-file $USAGE
 
 Blob ids and modes should be empty for missing files."
 
@@ -27,7 +27,7 @@ SUBDIRECTORY_OK=Yes
 cd_to_toplevel
 require_work_tree
 
-if ! test "$#" -eq 7
+if test $# != 7
 then
        echo "$LONG_USAGE"
        exit 1
@@ -38,7 +38,8 @@ case "${1:-.}${2:-.}${3:-.}" in
 # Deleted in both or deleted in one and unchanged in the other
 #
 "$1.." | "$1.$1" | "$1$1.")
-       if [ "$2" ]; then
+       if test -n "$2"
+       then
                echo "Removing $4"
        else
                # read-tree checked that index matches HEAD already,
@@ -48,7 +49,8 @@ case "${1:-.}${2:-.}${3:-.}" in
                # we do not have it in the index, though.
                exec git update-index --remove -- "$4"
        fi
-       if test -f "$4"; then
+       if test -f "$4"
+       then
                rm -f -- "$4" &&
                rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || :
        fi &&
@@ -67,7 +69,7 @@ case "${1:-.}${2:-.}${3:-.}" in
        echo "Adding $4"
        if test -f "$4"
        then
-               echo "ERROR: untracked $4 is overwritten by the merge."
+               echo "ERROR: untracked $4 is overwritten by the merge." >&2
                exit 1
        fi
        git update-index --add --cacheinfo "$7" "$3" "$4" &&
@@ -78,9 +80,10 @@ case "${1:-.}${2:-.}${3:-.}" in
 # Added in both, identically (check for same permissions).
 #
 ".$3$2")
-       if [ "$6" != "$7" ]; then
-               echo "ERROR: File $4 added identically in both branches,"
-               echo "ERROR: but permissions conflict $6->$7."
+       if test "$6" != "$7"
+       then
+               echo "ERROR: File $4 added identically in both branches," >&2
+               echo "ERROR: but permissions conflict $6->$7." >&2
                exit 1
        fi
        echo "Adding $4"
@@ -95,44 +98,36 @@ case "${1:-.}${2:-.}${3:-.}" in
 
        case ",$6,$7," in
        *,120000,*)
-               echo "ERROR: $4: Not merging symbolic link changes."
+               echo "ERROR: $4: Not merging symbolic link changes." >&2
                exit 1
                ;;
        *,160000,*)
-               echo "ERROR: $4: Not merging conflicting submodule changes."
+               echo "ERROR: $4: Not merging conflicting submodule changes." >&2
                exit 1
                ;;
        esac
 
-       src2=`git-unpack-file $3`
+       src1=$(git-unpack-file $2)
+       src2=$(git-unpack-file $3)
        case "$1" in
        '')
                echo "Added $4 in both, but differently."
-               # This extracts OUR file in $orig, and uses git apply to
-               # remove lines that are unique to ours.
-               orig=`git-unpack-file $2`
-               sz0=`wc -c <"$orig"`
-               @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
-               sz1=`wc -c <"$orig"`
-
-               # If we do not have enough common material, it is not
-               # worth trying two-file merge using common subsections.
-               expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig
+               orig=$(git-unpack-file $2)
+               create_virtual_base "$orig" "$src2"
                ;;
        *)
                echo "Auto-merging $4"
-               orig=`git-unpack-file $1`
+               orig=$(git-unpack-file $1)
                ;;
        esac
 
-       # Be careful for funny filename such as "-L" in "$4", which
-       # would confuse "merge" greatly.
-       src1=`git-unpack-file $2`
        git merge-file "$src1" "$orig" "$src2"
        ret=$?
        msg=
-       if [ $ret -ne 0 ]; then
+       if test $ret != 0 || test -z "$1"
+       then
                msg='content conflict'
+               ret=1
        fi
 
        # Create the working tree file, using "our tree" version from the
@@ -140,26 +135,26 @@ case "${1:-.}${2:-.}${3:-.}" in
        git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1
        rm -f -- "$orig" "$src1" "$src2"
 
-       if [ "$6" != "$7" ]; then
-               if [ -n "$msg" ]; then
+       if test "$6" != "$7"
+       then
+               if test -n "$msg"
+               then
                        msg="$msg, "
                fi
                msg="${msg}permissions conflict: $5->$6,$7"
                ret=1
        fi
-       if [ "$1" = '' ]; then
-               ret=1
-       fi
 
-       if [ $ret -ne 0 ]; then
-               echo "ERROR: $msg in $4"
+       if test $ret != 0
+       then
+               echo "ERROR: $msg in $4" >&2
                exit 1
        fi
        exec git update-index -- "$4"
        ;;
 
 *)
-       echo "ERROR: $4: Not handling case $1 -> $2 -> $3"
+       echo "ERROR: $4: Not handling case $1 -> $2 -> $3" >&2
        ;;
 esac
 exit 1
index 647f11020257598a8245eb1e84f64b80a2c55aca..911bbce6c5d4e19243e50d5916005872ac672961 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -79,12 +79,27 @@ def p4_build_cmd(cmd):
         real_cmd += cmd
     return real_cmd
 
-def chdir(dir):
-    # P4 uses the PWD environment variable rather than getcwd(). Since we're
-    # not using the shell, we have to set it ourselves.  This path could
-    # be relative, so go there first, then figure out where we ended up.
-    os.chdir(dir)
-    os.environ['PWD'] = os.getcwd()
+def chdir(path, is_client_path=False):
+    """Do chdir to the given path, and set the PWD environment
+       variable for use by P4.  It does not look at getcwd() output.
+       Since we're not using the shell, it is necessary to set the
+       PWD environment variable explicitly.
+
+       Normally, expand the path to force it to be absolute.  This
+       addresses the use of relative path names inside P4 settings,
+       e.g. P4CONFIG=.p4config.  P4 does not simply open the filename
+       as given; it looks for .p4config using PWD.
+
+       If is_client_path, the path was handed to us directly by p4,
+       and may be a symbolic link.  Do not call os.getcwd() in this
+       case, because it will cause p4 to think that PWD is not inside
+       the client path.
+       """
+
+    os.chdir(path)
+    if not is_client_path:
+        path = os.getcwd()
+    os.environ['PWD'] = path
 
 def die(msg):
     if verbose:
@@ -1624,7 +1639,7 @@ def run(self, args):
             new_client_dir = True
             os.makedirs(self.clientPath)
 
-        chdir(self.clientPath)
+        chdir(self.clientPath, is_client_path=True)
         if self.dry_run:
             print "Would synchronize p4 checkout in %s" % self.clientPath
         else:
index 266e682f6c518ab72dc710d52ed4f1dbe22881b5..5d97e97bd95fe25d158058af025c48e47f6d241f 100755 (executable)
@@ -279,7 +279,7 @@ fi
 merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
 case "$rebase" in
 true)
-       eval="git-rebase $diffstat $strategy_args $merge_args"
+       eval="git-rebase $diffstat $strategy_args $merge_args $verbosity"
        eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
        ;;
 *)
index f29285c411a6dc9b58cffb47b91577bb2d023bc6..236a3521a19260f5f49b81a39d7193a68b25b64b 100755 (executable)
@@ -163,7 +163,7 @@ ($$)
 
 
 sub usage() {
-       print("Usage: git relink [--safe] <dir>... <master_dir> \n");
+       print("usage: git relink [--safe] <dir>... <master_dir> \n");
        print("All directories should contain a .git/objects/ subdirectory.\n");
        print("Options\n");
        print("\t--safe\t" .
index be809e5b59394a4fd2f4f2b73cad6bac47a39600..c3501d987e66d330189f156590316d335b580aaf 100755 (executable)
@@ -1045,6 +1045,47 @@ sub maildomain {
        return maildomain_net() || maildomain_mta() || 'localhost.localdomain';
 }
 
+sub smtp_host_string {
+       if (defined $smtp_server_port) {
+               return "$smtp_server:$smtp_server_port";
+       } else {
+               return $smtp_server;
+       }
+}
+
+# Returns 1 if authentication succeeded or was not necessary
+# (smtp_user was not specified), and 0 otherwise.
+
+sub smtp_auth_maybe {
+       if (!defined $smtp_authuser || $auth) {
+               return 1;
+       }
+
+       # Workaround AUTH PLAIN/LOGIN interaction defect
+       # with Authen::SASL::Cyrus
+       eval {
+               require Authen::SASL;
+               Authen::SASL->import(qw(Perl));
+       };
+
+       # TODO: Authentication may fail not because credentials were
+       # invalid but due to other reasons, in which we should not
+       # reject credentials.
+       $auth = Git::credential({
+               'protocol' => 'smtp',
+               'host' => smtp_host_string(),
+               'username' => $smtp_authuser,
+               # if there's no password, "git credential fill" will
+               # give us one, otherwise it'll just pass this one.
+               'password' => $smtp_authpass
+       }, sub {
+               my $cred = shift;
+               return !!$smtp->auth($cred->{'username'}, $cred->{'password'});
+       });
+
+       return $auth;
+}
+
 # Returns 1 if the message was sent, and 0 otherwise.
 # In actuality, the whole program dies when there
 # is an error sending a message.
@@ -1155,9 +1196,7 @@ sub send_message {
                else {
                        require Net::SMTP;
                        $smtp_domain ||= maildomain();
-                       $smtp ||= Net::SMTP->new((defined $smtp_server_port)
-                                                ? "$smtp_server:$smtp_server_port"
-                                                : $smtp_server,
+                       $smtp ||= Net::SMTP->new(smtp_host_string(),
                                                 Hello => $smtp_domain,
                                                 Debug => $debug_net_smtp);
                        if ($smtp_encryption eq 'tls' && $smtp) {
@@ -1185,31 +1224,7 @@ sub send_message {
                            defined $smtp_server_port ? " port=$smtp_server_port" : "";
                }
 
-               if (defined $smtp_authuser) {
-                       # Workaround AUTH PLAIN/LOGIN interaction defect
-                       # with Authen::SASL::Cyrus
-                       eval {
-                               require Authen::SASL;
-                               Authen::SASL->import(qw(Perl));
-                       };
-
-                       if (!defined $smtp_authpass) {
-
-                               system "stty -echo";
-
-                               do {
-                                       print "Password: ";
-                                       $_ = <STDIN>;
-                                       print "\n";
-                               } while (!defined $_);
-
-                               chomp($smtp_authpass = $_);
-
-                               system "stty echo";
-                       }
-
-                       $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
-               }
+               smtp_auth_maybe or die $smtp->message;
 
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
index 795edd2852aa1c680c3db1776bb84aca7662899d..2f7835941ecaa48be17b6dd64e64fb0df3db37e2 100644 (file)
@@ -84,14 +84,14 @@ if test -n "$OPTIONS_SPEC"; then
 else
        dashless=$(basename "$0" | sed -e 's/-/ /')
        usage() {
-               die "Usage: $dashless $USAGE"
+               die "usage: $dashless $USAGE"
        }
 
        if [ -z "$LONG_USAGE" ]
        then
-               LONG_USAGE="Usage: $dashless $USAGE"
+               LONG_USAGE="usage: $dashless $USAGE"
        else
-               LONG_USAGE="Usage: $dashless $USAGE
+               LONG_USAGE="usage: $dashless $USAGE
 
 $LONG_USAGE"
        fi
@@ -249,6 +249,18 @@ clear_local_git_env() {
        unset $(git rev-parse --local-env-vars)
 }
 
+# Generate a virtual base file for a two-file merge. Uses git apply to
+# remove lines from $1 that are not in $2, leaving only common lines.
+create_virtual_base() {
+       sz0=$(wc -c <"$1")
+       @@DIFF@@ -u -La/"$1" -Lb/"$1" "$1" "$2" | git apply --no-add
+       sz1=$(wc -c <"$1")
+
+       # If we do not have enough common material, it is not
+       # worth trying two-file merge using common subsections.
+       expr $sz0 \< $sz1 \* 2 >/dev/null || : >"$1"
+}
+
 
 # Platform specific tweaks to work around some commands
 case $(uname -s) in
index ab29bfeb738d1b8f452c558ba6f6d7aa4e854b3d..204bc78a9fdffcd11c397a85f94636bb17529b91 100755 (executable)
@@ -8,6 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /')
 USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
    or: $dashless [--quiet] init [--] [<path>...]
+   or: $dashless [--quiet] deinit [-f|--force] [--] <path>...
    or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
    or: $dashless [--quiet] foreach [--recursive] <command>
@@ -546,6 +547,80 @@ cmd_init()
        done
 }
 
+#
+# Unregister submodules from .git/config and remove their work tree
+#
+# $@ = requested paths (use '.' to deinit all submodules)
+#
+cmd_deinit()
+{
+       # parse $args after "submodule ... deinit".
+       while test $# -ne 0
+       do
+               case "$1" in
+               -f|--force)
+                       force=$1
+                       ;;
+               -q|--quiet)
+                       GIT_QUIET=1
+                       ;;
+               --)
+                       shift
+                       break
+                       ;;
+               -*)
+                       usage
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done
+
+       if test $# = 0
+       then
+               die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
+       fi
+
+       module_list "$@" |
+       while read mode sha1 stage sm_path
+       do
+               die_if_unmatched "$mode"
+               name=$(module_name "$sm_path") || exit
+
+               # Remove the submodule work tree (unless the user already did it)
+               if test -d "$sm_path"
+               then
+                       # Protect submodules containing a .git directory
+                       if test -d "$sm_path/.git"
+                       then
+                               echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")"
+                               die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
+                       fi
+
+                       if test -z "$force"
+                       then
+                               git rm -n "$sm_path" ||
+                               die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
+                       fi
+                       rm -rf "$sm_path" || say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
+               fi
+
+               mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
+
+               # Remove the .git/config entries (unless the user already did it)
+               if test -n "$(git config --get-regexp submodule."$name\.")"
+               then
+                       # Remove the whole section so we have a clean state when
+                       # the user later decides to init this submodule again
+                       url=$(git config submodule."$name".url)
+                       git config --remove-section submodule."$name" 2>/dev/null &&
+                       say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")"
+               fi
+       done
+}
+
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -1162,7 +1237,7 @@ cmd_sync()
 while test $# != 0 && test -z "$command"
 do
        case "$1" in
-       add | foreach | init | update | status | summary | sync)
+       add | foreach | init | deinit | update | status | summary | sync)
                command=$1
                ;;
        -q|--quiet)
index b46795f5935dd3d61a2f3e45f979aec04d12afcb..6c7bd95032b890d2f79cff0d18ac6aa688a74757 100755 (executable)
@@ -382,7 +382,7 @@ sub usage {
        my $fd = $exit ? \*STDERR : \*STDOUT;
        print $fd <<"";
 git-svn - bidirectional operations between a single Subversion tree and git
-Usage: git svn <command> [options] [arguments]\n
+usage: git svn <command> [options] [arguments]\n
 
        print $fd "Available commands:\n" unless $cmd;
 
@@ -534,7 +534,7 @@ sub cmd_fetch {
        }
        my ($remote) = @_;
        if (@_ > 1) {
-               die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
+               die "usage: $0 fetch [--all] [--parent] [svn-remote]\n";
        }
        $Git::SVN::no_reuse_existing = undef;
        if ($_fetch_parent) {
@@ -1404,7 +1404,7 @@ sub cmd_multi_fetch {
 # this command is special because it requires no metadata
 sub cmd_commit_diff {
        my ($ta, $tb, $url) = @_;
-       my $usage = "Usage: $0 commit-diff -r<revision> ".
+       my $usage = "usage: $0 commit-diff -r<revision> ".
                    "<tree-ish> <tree-ish> [<URL>]";
        fatal($usage) if (!defined $ta || !defined $tb);
        my $svn_path = '';
diff --git a/git.c b/git.c
index 39ba6b14618389bc9312df43a376b52f9a21d881..850d3f5527f4100f45cb5eb1a7d8772e04ae4443 100644 (file)
--- a/git.c
+++ b/git.c
@@ -125,6 +125,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        static char git_dir[PATH_MAX+1];
                        is_bare_repository_cfg = 1;
                        setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0);
+                       setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "-c")) {
index 45590330aa3131da69174899783913cee9a44c31..8b0e87436b687ce26e3e4987129a2fc171069280 100644 (file)
@@ -96,15 +96,18 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
 /*
  * Run "gpg" to see if the payload matches the detached signature.
  * gpg_output, when set, receives the diagnostic output from GPG.
+ * gpg_status, when set, receives the status output from GPG.
  */
 int verify_signed_buffer(const char *payload, size_t payload_size,
                         const char *signature, size_t signature_size,
-                        struct strbuf *gpg_output)
+                        struct strbuf *gpg_output, struct strbuf *gpg_status)
 {
        struct child_process gpg;
-       const char *args_gpg[] = {NULL, "--verify", "FILE", "-", NULL};
+       const char *args_gpg[] = {NULL, "--status-fd=1", "--verify", "FILE", "-", NULL};
        char path[PATH_MAX];
        int fd, ret;
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf *pbuf = &buf;
 
        args_gpg[0] = gpg_program;
        fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
@@ -119,9 +122,10 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
        memset(&gpg, 0, sizeof(gpg));
        gpg.argv = args_gpg;
        gpg.in = -1;
+       gpg.out = -1;
        if (gpg_output)
                gpg.err = -1;
-       args_gpg[2] = path;
+       args_gpg[3] = path;
        if (start_command(&gpg)) {
                unlink(path);
                return error(_("could not run gpg."));
@@ -134,9 +138,17 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
                strbuf_read(gpg_output, gpg.err, 0);
                close(gpg.err);
        }
+       if (gpg_status)
+               pbuf = gpg_status;
+       strbuf_read(pbuf, gpg.out, 0);
+       close(gpg.out);
+
        ret = finish_command(&gpg);
 
        unlink_or_warn(path);
 
+       ret |= !strstr(pbuf->buf, "\n[GNUPG:] GOODSIG ");
+       strbuf_release(&buf); /* no matter it was used or not */
+
        return ret;
 }
index b9c36088cea3c1d419c23354e095320ebebd90a4..cf9902184272d20c6d5826cf9f10dcaebeb3e6e8 100644 (file)
@@ -2,7 +2,7 @@
 #define GPG_INTERFACE_H
 
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
-extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);
+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
 extern void set_signing_key(const char *);
 extern const char *get_signing_key(void);
diff --git a/grep.c b/grep.c
index 4bd1b8b1dd67e4f5c2223536b435198ce67463ba..bb548cae69d1eece8832c97332a91daac5f8dceb 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -625,7 +625,8 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
        for (p = opt->header_list; p; p = p->next) {
                if (p->token != GREP_PATTERN_HEAD)
                        die("bug: a non-header pattern in grep header list.");
-               if (p->field < 0 || GREP_HEADER_FIELD_MAX <= p->field)
+               if (p->field < GREP_HEADER_FIELD_MIN ||
+                   GREP_HEADER_FIELD_MAX <= p->field)
                        die("bug: unknown header field %d", p->field);
                compile_regexp(p, opt);
        }
diff --git a/grep.h b/grep.h
index 8fc854f400802c3329fc3c028942de55b820ac90..e4a1df56a423548af4cba728811eb21b62518e15 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -28,7 +28,8 @@ enum grep_context {
 };
 
 enum grep_header_field {
-       GREP_HEADER_AUTHOR = 0,
+       GREP_HEADER_FIELD_MIN = 0,
+       GREP_HEADER_AUTHOR = GREP_HEADER_FIELD_MIN,
        GREP_HEADER_COMMITTER,
        GREP_HEADER_REFLOG,
 
diff --git a/hash.h b/hash.h
index b875ce67c49eb39a8ca8ad6a688a334985db0534..1d43ac0ba0120e947e40476946360efbdee80a93 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -40,4 +40,11 @@ static inline void init_hash(struct hash_table *table)
        table->array = NULL;
 }
 
+static inline void preallocate_hash(struct hash_table *table, unsigned int elts)
+{
+       assert(table->size == 0 && table->nr == 0 && table->array == NULL);
+       table->size = elts * 2;
+       table->array = xcalloc(sizeof(struct hash_table_entry), table->size);
+}
+
 #endif
index f50e77fb2890207bd7385b59bdd561133991c910..8144f3ad5eb68c789fd031921e6e702170310d68 100644 (file)
@@ -70,7 +70,7 @@ static void format_write(int fd, const char *fmt, ...)
        if (n >= sizeof(buffer))
                die("protocol error: impossibly long line");
 
-       safe_write(fd, buffer, n);
+       write_or_die(fd, buffer, n);
 }
 
 static void http_status(unsigned code, const char *msg)
@@ -111,7 +111,7 @@ static void hdr_cache_forever(void)
 
 static void end_headers(void)
 {
-       safe_write(1, "\r\n", 2);
+       write_or_die(1, "\r\n", 2);
 }
 
 __attribute__((format (printf, 1, 2)))
@@ -157,7 +157,7 @@ static void send_strbuf(const char *type, struct strbuf *buf)
        hdr_int(content_length, buf->len);
        hdr_str(content_type, type);
        end_headers();
-       safe_write(1, buf->buf, buf->len);
+       write_or_die(1, buf->buf, buf->len);
 }
 
 static void send_local_file(const char *the_type, const char *name)
@@ -185,7 +185,7 @@ static void send_local_file(const char *the_type, const char *name)
                        die_errno("Cannot read '%s'", p);
                if (!n)
                        break;
-               safe_write(1, buf, n);
+               write_or_die(1, buf, n);
        }
        close(fd);
        free(buf);
diff --git a/http.c b/http.c
index d9d1aad3be468394e39966c0b45e7e101b387b0c..8803c70825ac8aefb619b6c56d6015573eb5d227 100644 (file)
--- a/http.c
+++ b/http.c
@@ -5,6 +5,7 @@
 #include "url.h"
 #include "credential.h"
 #include "version.h"
+#include "pkt-line.h"
 
 int active_requests;
 int http_is_verbose;
index 8a34332e6df0d7a95406bf4f1c7772ce7fe14339..7cc7d598e712fab531f7c6f938be17c317161f3e 100644 (file)
@@ -9,6 +9,7 @@
 #include "string-list.h"
 #include "color.h"
 #include "gpg-interface.h"
+#include "sequencer.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -206,89 +207,6 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
        putchar(')');
 }
 
-/*
- * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
- * Signed-off-by: and Acked-by: lines.
- */
-static int detect_any_signoff(char *letter, int size)
-{
-       char *cp;
-       int seen_colon = 0;
-       int seen_at = 0;
-       int seen_name = 0;
-       int seen_head = 0;
-
-       cp = letter + size;
-       while (letter <= --cp && *cp == '\n')
-               continue;
-
-       while (letter <= cp) {
-               char ch = *cp--;
-               if (ch == '\n')
-                       break;
-
-               if (!seen_at) {
-                       if (ch == '@')
-                               seen_at = 1;
-                       continue;
-               }
-               if (!seen_colon) {
-                       if (ch == '@')
-                               return 0;
-                       else if (ch == ':')
-                               seen_colon = 1;
-                       else
-                               seen_name = 1;
-                       continue;
-               }
-               if (('A' <= ch && ch <= 'Z') ||
-                   ('a' <= ch && ch <= 'z') ||
-                   ch == '-') {
-                       seen_head = 1;
-                       continue;
-               }
-               /* no empty last line doesn't match */
-               return 0;
-       }
-       return seen_head && seen_name;
-}
-
-static void append_signoff(struct strbuf *sb, const char *signoff)
-{
-       static const char signed_off_by[] = "Signed-off-by: ";
-       size_t signoff_len = strlen(signoff);
-       int has_signoff = 0;
-       char *cp;
-
-       cp = sb->buf;
-
-       /* First see if we already have the sign-off by the signer */
-       while ((cp = strstr(cp, signed_off_by))) {
-
-               has_signoff = 1;
-
-               cp += strlen(signed_off_by);
-               if (cp + signoff_len >= sb->buf + sb->len)
-                       break;
-               if (strncmp(cp, signoff, signoff_len))
-                       continue;
-               if (!isspace(cp[signoff_len]))
-                       continue;
-               /* we already have him */
-               return;
-       }
-
-       if (!has_signoff)
-               has_signoff = detect_any_signoff(sb->buf, sb->len);
-
-       if (!has_signoff)
-               strbuf_addch(sb, '\n');
-
-       strbuf_addstr(sb, signed_off_by);
-       strbuf_add(sb, signoff, signoff_len);
-       strbuf_addch(sb, '\n');
-}
-
 static unsigned int digits_in_number(unsigned int number)
 {
        unsigned int i = 10, result = 1;
@@ -444,7 +362,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit)
 
        status = verify_signed_buffer(payload.buf, payload.len,
                                      signature.buf, signature.len,
-                                     &gpg_output);
+                                     &gpg_output, NULL);
        if (status && !gpg_output.len)
                strbuf_addstr(&gpg_output, "No signature\n");
 
@@ -508,20 +426,17 @@ static void show_one_mergetag(struct rev_info *opt,
        gpg_message_offset = verify_message.len;
 
        payload_size = parse_signature(extra->value, extra->len);
-       if ((extra->len <= payload_size) ||
-           (verify_signed_buffer(extra->value, payload_size,
-                                 extra->value + payload_size,
-                                 extra->len - payload_size,
-                                 &verify_message) &&
-            verify_message.len <= gpg_message_offset)) {
-               strbuf_addstr(&verify_message, "No signature\n");
-               status = -1;
-       }
-       else if (strstr(verify_message.buf + gpg_message_offset,
-                       ": Good signature from "))
-               status = 0;
-       else
-               status = -1;
+       status = -1;
+       if (extra->len > payload_size)
+               if (verify_signed_buffer(extra->value, payload_size,
+                                        extra->value + payload_size,
+                                        extra->len - payload_size,
+                                        &verify_message, NULL)) {
+                       if (verify_message.len <= gpg_message_offset)
+                               strbuf_addstr(&verify_message, "No signature\n");
+                       else
+                               status = 0;
+               }
 
        show_sig_lines(opt, status, verify_message.buf);
        strbuf_release(&verify_message);
@@ -672,8 +587,10 @@ void show_log(struct rev_info *opt)
        /*
         * And then the pretty-printed message itself
         */
-       if (ctx.need_8bit_cte >= 0)
-               ctx.need_8bit_cte = has_non_ascii(opt->add_signoff);
+       if (ctx.need_8bit_cte >= 0 && opt->add_signoff)
+               ctx.need_8bit_cte =
+                       has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"),
+                                              getenv("GIT_COMMITTER_EMAIL")));
        ctx.date_mode = opt->date_mode;
        ctx.date_mode_explicit = opt->date_mode_explicit;
        ctx.abbrev = opt->diffopt.abbrev;
@@ -686,7 +603,7 @@ void show_log(struct rev_info *opt)
        pretty_print_commit(&ctx, commit, &msgbuf);
 
        if (opt->add_signoff)
-               append_signoff(&msgbuf, opt->add_signoff);
+               append_signoff(&msgbuf, 0, APPEND_SIGNOFF_DEDUP);
 
        if ((ctx.fmt != CMIT_FMT_USERFORMAT) &&
            ctx.notes_message && *ctx.notes_message) {
index 26f7ed143e0649b6377ea427b5c57ca7ddb663d3..2bb734d51cbe59e3eed7c82e7522c594d5efa579 100644 (file)
@@ -47,6 +47,13 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path)
        return score;
 }
 
+static int base_name_entries_compare(const struct name_entry *a,
+                                    const struct name_entry *b)
+{
+       return base_name_compare(a->path, tree_entry_len(a), a->mode,
+                                b->path, tree_entry_len(b), b->mode);
+}
+
 /*
  * Inspect two trees, and give a score that tells how similar they are.
  */
@@ -71,54 +78,35 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2)
        if (type != OBJ_TREE)
                die("%s is not a tree", sha1_to_hex(hash2));
        init_tree_desc(&two, two_buf, size);
-       while (one.size | two.size) {
-               const unsigned char *elem1 = elem1;
-               const unsigned char *elem2 = elem2;
-               const char *path1 = path1;
-               const char *path2 = path2;
-               unsigned mode1 = mode1;
-               unsigned mode2 = mode2;
+       for (;;) {
+               struct name_entry e1, e2;
+               int got_entry_from_one = tree_entry(&one, &e1);
+               int got_entry_from_two = tree_entry(&two, &e2);
                int cmp;
 
-               if (one.size)
-                       elem1 = tree_entry_extract(&one, &path1, &mode1);
-               if (two.size)
-                       elem2 = tree_entry_extract(&two, &path2, &mode2);
-
-               if (!one.size) {
-                       /* two has more entries */
-                       score += score_missing(mode2, path2);
-                       update_tree_entry(&two);
-                       continue;
-               }
-               if (!two.size) {
+               if (got_entry_from_one && got_entry_from_two)
+                       cmp = base_name_entries_compare(&e1, &e2);
+               else if (got_entry_from_one)
                        /* two lacks this entry */
-                       score += score_missing(mode1, path1);
-                       update_tree_entry(&one);
-                       continue;
-               }
-               cmp = base_name_compare(path1, strlen(path1), mode1,
-                                       path2, strlen(path2), mode2);
-               if (cmp < 0) {
+                       cmp = -1;
+               else if (got_entry_from_two)
+                       /* two has more entries */
+                       cmp = 1;
+               else
+                       break;
+
+               if (cmp < 0)
                        /* path1 does not appear in two */
-                       score += score_missing(mode1, path1);
-                       update_tree_entry(&one);
-                       continue;
-               }
-               else if (cmp > 0) {
+                       score += score_missing(e1.mode, e1.path);
+               else if (cmp > 0)
                        /* path2 does not appear in one */
-                       score += score_missing(mode2, path2);
-                       update_tree_entry(&two);
-                       continue;
-               }
-               else if (hashcmp(elem1, elem2))
+                       score += score_missing(e2.mode, e2.path);
+               else if (hashcmp(e1.sha1, e2.sha1))
                        /* they are different */
-                       score += score_differs(mode1, mode2, path1);
+                       score += score_differs(e1.mode, e2.mode, e1.path);
                else
                        /* same subtree or blob */
-                       score += score_matches(mode1, mode2, path1);
-               update_tree_entry(&one);
-               update_tree_entry(&two);
+                       score += score_matches(e1.mode, e2.mode, e1.path);
        }
        free(one_buf);
        free(two_buf);
index 8a36916567a234c354af9994df9c28788749e7fa..5a608abf9c77f436ab1472592e3d5777ef83805d 100644 (file)
@@ -21,8 +21,12 @@ diff_cmd () {
 
 merge_cmd () {
        touch "$BACKUP"
-       $base_present || >"$BASE"
-       "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+       if ! $base_present
+       then
+               cp -- "$LOCAL" "$BASE"
+               create_virtual_base "$BASE" "$REMOTE"
+       fi
+       "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"
        check_unchanged
 }
 
index 942c45962252eba4c6f88b4f4f7593c9247749ae..617c86c53762575373d0bcbf3d8bab09cb042caa 100644 (file)
@@ -32,38 +32,96 @@ static unsigned int hash_name(const char *name, int namelen)
        return hash;
 }
 
-static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)
+struct dir_entry {
+       struct dir_entry *next;
+       struct dir_entry *parent;
+       struct cache_entry *ce;
+       int nr;
+       unsigned int namelen;
+};
+
+static struct dir_entry *find_dir_entry(struct index_state *istate,
+               const char *name, unsigned int namelen)
+{
+       unsigned int hash = hash_name(name, namelen);
+       struct dir_entry *dir;
+
+       for (dir = lookup_hash(hash, &istate->dir_hash); dir; dir = dir->next)
+               if (dir->namelen == namelen &&
+                   !strncasecmp(dir->ce->name, name, namelen))
+                       return dir;
+       return NULL;
+}
+
+static struct dir_entry *hash_dir_entry(struct index_state *istate,
+               struct cache_entry *ce, int namelen)
 {
        /*
         * Throw each directory component in the hash for quick lookup
         * during a git status. Directory components are stored with their
         * closing slash.  Despite submodules being a directory, they never
         * reach this point, because they are stored without a closing slash
-        * in the cache.
+        * in index_state.name_hash (as ordinary cache_entries).
         *
-        * Note that the cache_entry stored with the directory does not
-        * represent the directory itself.  It is a pointer to an existing
-        * filename, and its only purpose is to represent existence of the
-        * directory in the cache.  It is very possible multiple directory
-        * hash entries may point to the same cache_entry.
+        * Note that the cache_entry stored with the dir_entry merely
+        * supplies the name of the directory (up to dir_entry.namelen). We
+        * track the number of 'active' files in a directory in dir_entry.nr,
+        * so we can tell if the directory is still relevant, e.g. for git
+        * status. However, if cache_entries are removed, we cannot pinpoint
+        * an exact cache_entry that's still active. It is very possible that
+        * multiple dir_entries point to the same cache_entry.
         */
-       unsigned int hash;
-       void **pos;
+       struct dir_entry *dir;
+
+       /* get length of parent directory */
+       while (namelen > 0 && !is_dir_sep(ce->name[namelen - 1]))
+               namelen--;
+       if (namelen <= 0)
+               return NULL;
+
+       /* lookup existing entry for that directory */
+       dir = find_dir_entry(istate, ce->name, namelen);
+       if (!dir) {
+               /* not found, create it and add to hash table */
+               void **pdir;
+               unsigned int hash = hash_name(ce->name, namelen);
 
-       const char *ptr = ce->name;
-       while (*ptr) {
-               while (*ptr && *ptr != '/')
-                       ++ptr;
-               if (*ptr == '/') {
-                       ++ptr;
-                       hash = hash_name(ce->name, ptr - ce->name);
-                       pos = insert_hash(hash, ce, &istate->name_hash);
-                       if (pos) {
-                               ce->dir_next = *pos;
-                               *pos = ce;
-                       }
+               dir = xcalloc(1, sizeof(struct dir_entry));
+               dir->namelen = namelen;
+               dir->ce = ce;
+
+               pdir = insert_hash(hash, dir, &istate->dir_hash);
+               if (pdir) {
+                       dir->next = *pdir;
+                       *pdir = dir;
                }
+
+               /* recursively add missing parent directories */
+               dir->parent = hash_dir_entry(istate, ce, namelen - 1);
        }
+       return dir;
+}
+
+static void add_dir_entry(struct index_state *istate, struct cache_entry *ce)
+{
+       /* Add reference to the directory entry (and parents if 0). */
+       struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
+       while (dir && !(dir->nr++))
+               dir = dir->parent;
+}
+
+static void remove_dir_entry(struct index_state *istate, struct cache_entry *ce)
+{
+       /*
+        * Release reference to the directory entry (and parents if 0).
+        *
+        * Note: we do not remove / free the entry because there's no
+        * hash.[ch]::remove_hash and dir->next may point to other entries
+        * that are still valid, so we must not free the memory.
+        */
+       struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
+       while (dir && dir->nr && !(--dir->nr))
+               dir = dir->parent;
 }
 
 static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
@@ -74,7 +132,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
        if (ce->ce_flags & CE_HASHED)
                return;
        ce->ce_flags |= CE_HASHED;
-       ce->next = ce->dir_next = NULL;
+       ce->next = NULL;
        hash = hash_name(ce->name, ce_namelen(ce));
        pos = insert_hash(hash, ce, &istate->name_hash);
        if (pos) {
@@ -82,8 +140,8 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
                *pos = ce;
        }
 
-       if (ignore_case)
-               hash_index_entry_directories(istate, ce);
+       if (ignore_case && !(ce->ce_flags & CE_UNHASHED))
+               add_dir_entry(istate, ce);
 }
 
 static void lazy_init_name_hash(struct index_state *istate)
@@ -92,6 +150,8 @@ static void lazy_init_name_hash(struct index_state *istate)
 
        if (istate->name_hash_initialized)
                return;
+       if (istate->cache_nr)
+               preallocate_hash(&istate->name_hash, istate->cache_nr);
        for (nr = 0; nr < istate->cache_nr; nr++)
                hash_index_entry(istate, istate->cache[nr]);
        istate->name_hash_initialized = 1;
@@ -99,11 +159,33 @@ static void lazy_init_name_hash(struct index_state *istate)
 
 void add_name_hash(struct index_state *istate, struct cache_entry *ce)
 {
+       /* if already hashed, add reference to directory entries */
+       if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_STATE_MASK)
+               add_dir_entry(istate, ce);
+
        ce->ce_flags &= ~CE_UNHASHED;
        if (istate->name_hash_initialized)
                hash_index_entry(istate, ce);
 }
 
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+void remove_name_hash(struct index_state *istate, struct cache_entry *ce)
+{
+       /* if already hashed, release reference to directory entries */
+       if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_HASHED)
+               remove_dir_entry(istate, ce);
+
+       ce->ce_flags |= CE_UNHASHED;
+}
+
 static int slow_same_name(const char *name1, int len1, const char *name2, int len2)
 {
        if (len1 != len2)
@@ -137,18 +219,7 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
        if (!icase)
                return 0;
 
-       /*
-        * If the entry we're comparing is a filename (no trailing slash), then compare
-        * the lengths exactly.
-        */
-       if (name[namelen - 1] != '/')
-               return slow_same_name(name, namelen, ce->name, len);
-
-       /*
-        * For a directory, we point to an arbitrary cache_entry filename.  Just
-        * make sure the directory portion matches.
-        */
-       return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
+       return slow_same_name(name, namelen, ce->name, len);
 }
 
 struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
@@ -164,27 +235,54 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
                        if (same_name(ce, name, namelen, icase))
                                return ce;
                }
-               if (icase && name[namelen - 1] == '/')
-                       ce = ce->dir_next;
-               else
-                       ce = ce->next;
+               ce = ce->next;
        }
 
        /*
-        * Might be a submodule.  Despite submodules being directories,
+        * When looking for a directory (trailing '/'), it might be a
+        * submodule or a directory. Despite submodules being directories,
         * they are stored in the name hash without a closing slash.
-        * When ignore_case is 1, directories are stored in the name hash
-        * with their closing slash.
+        * When ignore_case is 1, directories are stored in a separate hash
+        * table *with* their closing slash.
         *
         * The side effect of this storage technique is we have need to
+        * lookup the directory in a separate hash table, and if not found
         * remove the slash from name and perform the lookup again without
         * the slash.  If a match is made, S_ISGITLINK(ce->mode) will be
         * true.
         */
        if (icase && name[namelen - 1] == '/') {
+               struct dir_entry *dir = find_dir_entry(istate, name, namelen);
+               if (dir && dir->nr)
+                       return dir->ce;
+
                ce = index_name_exists(istate, name, namelen - 1, icase);
                if (ce && S_ISGITLINK(ce->ce_mode))
                        return ce;
        }
        return NULL;
 }
+
+static int free_dir_entry(void *entry, void *unused)
+{
+       struct dir_entry *dir = entry;
+       while (dir) {
+               struct dir_entry *next = dir->next;
+               free(dir);
+               dir = next;
+       }
+       return 0;
+}
+
+void free_name_hash(struct index_state *istate)
+{
+       if (!istate->name_hash_initialized)
+               return;
+       istate->name_hash_initialized = 0;
+       if (ignore_case)
+               /* free directory entries */
+               for_each_hash(&istate->dir_hash, free_dir_entry, NULL);
+
+       free_hash(&istate->name_hash);
+       free_hash(&istate->dir_hash);
+}
index 4af3451bf89471a0ab7a148aad4924a8ac8ae053..20703f52ed24aa227d451bec332ac73cc3d49d3a 100644 (file)
--- a/object.c
+++ b/object.c
@@ -185,6 +185,16 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
        return obj;
 }
 
+struct object *parse_object_or_die(const unsigned char *sha1,
+                                  const char *name)
+{
+       struct object *o = parse_object(sha1);
+       if (o)
+               return o;
+
+       die(_("unable to parse object: %s"), name ? name : sha1_to_hex(sha1));
+}
+
 struct object *parse_object(const unsigned char *sha1)
 {
        unsigned long size;
index 6a97b6ba1a43e1d38eb07515ad298e0067628127..97d384b80a5dbf0cac88b59cbb8a2d333d2b9582 100644 (file)
--- a/object.h
+++ b/object.h
@@ -54,9 +54,20 @@ struct object *lookup_object(const unsigned char *sha1);
 
 extern void *create_object(const unsigned char *sha1, int type, void *obj);
 
-/** Returns the object, having parsed it to find out what it is. **/
+/*
+ * Returns the object, having parsed it to find out what it is.
+ *
+ * Returns NULL if the object is missing or corrupt.
+ */
 struct object *parse_object(const unsigned char *sha1);
 
+/*
+ * Like parse_object, but will die() instead of returning NULL. If the
+ * "name" parameter is not NULL, it is included in the error message
+ * (otherwise, the sha1 hex is given).
+ */
+struct object *parse_object_or_die(const unsigned char *sha1, const char *name);
+
 /* Given the result of read_sha1_file(), returns the object after
  * parsing it.  eaten_p indicates if the object has a borrowed copy
  * of buffer and the caller should not free() it.
index f09a05422854c919cd31c542b82b057624275959..4461f71a37bea5d935409761cc8a838bff443439 100644 (file)
@@ -27,6 +27,7 @@ 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;
+       struct object *o;
        int is_tag_ref;
 
        /* Do not pack the symbolic refs */
@@ -39,14 +40,13 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
                return 0;
 
        fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
-       if (is_tag_ref) {
-               struct object *o = parse_object(sha1);
-               if (o->type == OBJ_TAG) {
-                       o = deref_tag(o, path, 0);
-                       if (o)
-                               fprintf(cb->refs_file, "^%s\n",
-                                       sha1_to_hex(o->sha1));
-               }
+
+       o = parse_object_or_die(sha1, path);
+       if (o->type == OBJ_TAG) {
+               o = deref_tag(o, path, 0);
+               if (o)
+                       fprintf(cb->refs_file, "^%s\n",
+                               sha1_to_hex(o->sha1));
        }
 
        if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
@@ -128,7 +128,7 @@ int pack_refs(unsigned int flags)
                die_errno("unable to create ref-pack file structure");
 
        /* perhaps other traits later as well */
-       fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
+       fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n");
 
        for_each_ref(handle_one_ref, &cbdata);
        if (ferror(cbdata.refs_file))
diff --git a/path.c b/path.c
index d3d3f8b8ad75b9817df3014296aabc34b6a4eb14..2fdccc2f18367399b8247ee79ec8bf4934f596cb 100644 (file)
--- a/path.c
+++ b/path.c
 #include "strbuf.h"
 #include "string-list.h"
 
+#ifndef get_st_mode_bits
+/*
+ * The replacement lstat(2) we use on Cygwin is incomplete and
+ * may return wrong permission bits. Most of the time we do not care,
+ * but the callsites of this wrapper do care.
+ */
+int get_st_mode_bits(const char *path, int *mode)
+{
+       struct stat st;
+       if (lstat(path, &st) < 0)
+               return -1;
+       *mode = st.st_mode;
+       return 0;
+}
+#endif
+
 static char bad_path[] = "/bad-path/";
 
 static char *get_pathname(void)
@@ -391,7 +407,6 @@ const char *enter_repo(const char *path, int strict)
 
 int set_shared_perm(const char *path, int mode)
 {
-       struct stat st;
        int tweak, shared, orig_mode;
 
        if (!shared_repository) {
@@ -400,9 +415,8 @@ int set_shared_perm(const char *path, int mode)
                return 0;
        }
        if (!mode) {
-               if (lstat(path, &st) < 0)
+               if (get_st_mode_bits(path, &mode) < 0)
                        return -1;
-               mode = st.st_mode;
                orig_mode = mode;
        } else
                orig_mode = 0;
index 57a17160f9decb7879b8d6cba741ddb6d593be47..96cac39a4c8ebc0ce4e111271429fdf68b03bf97 100644 (file)
@@ -60,6 +60,7 @@ =head1 SYNOPSIS
                 version exec_path html_path hash_object git_cmd_try
                 remote_refs prompt
                 get_tz_offset
+                credential credential_read credential_write
                 temp_acquire temp_release temp_reset temp_path);
 
 
@@ -269,13 +270,13 @@ sub command {
 
        if (not defined wantarray) {
                # Nothing to pepper the possible exception with.
-               _cmd_close($fh, $ctx);
+               _cmd_close($ctx, $fh);
 
        } elsif (not wantarray) {
                local $/;
                my $text = <$fh>;
                try {
-                       _cmd_close($fh, $ctx);
+                       _cmd_close($ctx, $fh);
                } catch Git::Error::Command with {
                        # Pepper with the output:
                        my $E = shift;
@@ -288,7 +289,7 @@ sub command {
                my @lines = <$fh>;
                defined and chomp for @lines;
                try {
-                       _cmd_close($fh, $ctx);
+                       _cmd_close($ctx, $fh);
                } catch Git::Error::Command with {
                        my $E = shift;
                        $E->{'-outputref'} = \@lines;
@@ -315,7 +316,7 @@ sub command_oneline {
        my $line = <$fh>;
        defined $line and chomp $line;
        try {
-               _cmd_close($fh, $ctx);
+               _cmd_close($ctx, $fh);
        } catch Git::Error::Command with {
                # Pepper with the output:
                my $E = shift;
@@ -383,7 +384,7 @@ sub command_input_pipe {
 sub command_close_pipe {
        my ($self, $fh, $ctx) = _maybe_self(@_);
        $ctx ||= '<unknown>';
-       _cmd_close($fh, $ctx);
+       _cmd_close($ctx, $fh);
 }
 
 =item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] )
@@ -420,7 +421,7 @@ sub command_bidi_pipe {
 is:
 
        my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');
-       print "000000000\n" $out;
+       print $out "000000000\n";
        while (<$in>) { ... }
        $r->command_close_bidi_pipe($pid, $in, $out, $ctx);
 
@@ -428,23 +429,26 @@ sub command_bidi_pipe {
 currently it is simply the command name but in future the context might
 have more complicated structure.
 
+C<PIPE_IN> and C<PIPE_OUT> may be C<undef> if they have been closed prior to
+calling this function.  This may be useful in a query-response type of
+commands where caller first writes a query and later reads response, eg:
+
+       my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');
+       print $out "000000000\n";
+       close $out;
+       while (<$in>) { ... }
+       $r->command_close_bidi_pipe($pid, $in, undef, $ctx);
+
+This idiom may prevent potential dead locks caused by data sent to the output
+pipe not being flushed and thus not reaching the executed command.
+
 =cut
 
 sub command_close_bidi_pipe {
        local $?;
-       my ($pid, $in, $out, $ctx) = @_;
-       foreach my $fh ($in, $out) {
-               unless (close $fh) {
-                       if ($!) {
-                               carp "error closing pipe: $!";
-                       } elsif ($? >> 8) {
-                               throw Git::Error::Command($ctx, $? >>8);
-                       }
-               }
-       }
-
+       my ($self, $pid, $in, $out, $ctx) = _maybe_self(@_);
+       _cmd_close($ctx, (grep { defined } ($in, $out)));
        waitpid $pid, 0;
-
        if ($? >> 8) {
                throw Git::Error::Command($ctx, $? >>8);
        }
@@ -1020,6 +1024,156 @@ sub _close_cat_blob {
 }
 
 
+=item credential_read( FILEHANDLE )
+
+Reads credential key-value pairs from C<FILEHANDLE>.  Reading stops at EOF or
+when an empty line is encountered.  Each line must be of the form C<key=value>
+with a non-empty key.  Function returns hash with all read values.  Any white
+space (other than new-line character) is preserved.
+
+=cut
+
+sub credential_read {
+       my ($self, $reader) = _maybe_self(@_);
+       my %credential;
+       while (<$reader>) {
+               chomp;
+               if ($_ eq '') {
+                       last;
+               } elsif (!/^([^=]+)=(.*)$/) {
+                       throw Error::Simple("unable to parse git credential data:\n$_");
+               }
+               $credential{$1} = $2;
+       }
+       return %credential;
+}
+
+=item credential_write( FILEHANDLE, CREDENTIAL_HASHREF )
+
+Writes credential key-value pairs from hash referenced by
+C<CREDENTIAL_HASHREF> to C<FILEHANDLE>.  Keys and values cannot contain
+new-lines or NUL bytes characters, and key cannot contain equal signs nor be
+empty (if they do Error::Simple is thrown).  Any white space is preserved.  If
+value for a key is C<undef>, it will be skipped.
+
+If C<'url'> key exists it will be written first.  (All the other key-value
+pairs are written in sorted order but you should not depend on that).  Once
+all lines are written, an empty line is printed.
+
+=cut
+
+sub credential_write {
+       my ($self, $writer, $credential) = _maybe_self(@_);
+       my ($key, $value);
+
+       # Check if $credential is valid prior to writing anything
+       while (($key, $value) = each %$credential) {
+               if (!defined $key || !length $key) {
+                       throw Error::Simple("credential key empty or undefined");
+               } elsif ($key =~ /[=\n\0]/) {
+                       throw Error::Simple("credential key contains invalid characters: $key");
+               } elsif (defined $value && $value =~ /[\n\0]/) {
+                       throw Error::Simple("credential value for key=$key contains invalid characters: $value");
+               }
+       }
+
+       for $key (sort {
+               # url overwrites other fields, so it must come first
+               return -1 if $a eq 'url';
+               return  1 if $b eq 'url';
+               return $a cmp $b;
+       } keys %$credential) {
+               if (defined $credential->{$key}) {
+                       print $writer $key, '=', $credential->{$key}, "\n";
+               }
+       }
+       print $writer "\n";
+}
+
+sub _credential_run {
+       my ($self, $credential, $op) = _maybe_self(@_);
+       my ($pid, $reader, $writer, $ctx) = command_bidi_pipe('credential', $op);
+
+       credential_write $writer, $credential;
+       close $writer;
+
+       if ($op eq "fill") {
+               %$credential = credential_read $reader;
+       }
+       if (<$reader>) {
+               throw Error::Simple("unexpected output from git credential $op response:\n$_\n");
+       }
+
+       command_close_bidi_pipe($pid, $reader, undef, $ctx);
+}
+
+=item credential( CREDENTIAL_HASHREF [, OPERATION ] )
+
+=item credential( CREDENTIAL_HASHREF, CODE )
+
+Executes C<git credential> for a given set of credentials and specified
+operation.  In both forms C<CREDENTIAL_HASHREF> needs to be a reference to
+a hash which stores credentials.  Under certain conditions the hash can
+change.
+
+In the first form, C<OPERATION> can be C<'fill'>, C<'approve'> or C<'reject'>,
+and function will execute corresponding C<git credential> sub-command.  If
+it's omitted C<'fill'> is assumed.  In case of C<'fill'> the values stored in
+C<CREDENTIAL_HASHREF> will be changed to the ones returned by the C<git
+credential fill> command.  The usual usage would look something like:
+
+       my %cred = (
+               'protocol' => 'https',
+               'host' => 'example.com',
+               'username' => 'bob'
+       );
+       Git::credential \%cred;
+       if (try_to_authenticate($cred{'username'}, $cred{'password'})) {
+               Git::credential \%cred, 'approve';
+               ... do more stuff ...
+       } else {
+               Git::credential \%cred, 'reject';
+       }
+
+In the second form, C<CODE> needs to be a reference to a subroutine.  The
+function will execute C<git credential fill> to fill the provided credential
+hash, then call C<CODE> with C<CREDENTIAL_HASHREF> as the sole argument.  If
+C<CODE>'s return value is defined, the function will execute C<git credential
+approve> (if return value yields true) or C<git credential reject> (if return
+value is false).  If the return value is undef, nothing at all is executed;
+this is useful, for example, if the credential could neither be verified nor
+rejected due to an unrelated network error.  The return value is the same as
+what C<CODE> returns.  With this form, the usage might look as follows:
+
+       if (Git::credential {
+               'protocol' => 'https',
+               'host' => 'example.com',
+               'username' => 'bob'
+       }, sub {
+               my $cred = shift;
+               return !!try_to_authenticate($cred->{'username'},
+                                            $cred->{'password'});
+       }) {
+               ... do more stuff ...
+       }
+
+=cut
+
+sub credential {
+       my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill');
+
+       if ('CODE' eq ref $op_or_code) {
+               _credential_run $credential, 'fill';
+               my $ret = $op_or_code->($credential);
+               if (defined $ret) {
+                       _credential_run $credential, $ret ? 'approve' : 'reject';
+               }
+               return $ret;
+       } else {
+               _credential_run $credential, $op_or_code;
+       }
+}
+
 { # %TEMP_* Lexical Context
 
 my (%TEMP_FILEMAP, %TEMP_FILES);
@@ -1375,9 +1529,11 @@ sub _setup_git_cmd_env {
 
 # Close pipe to a subprocess.
 sub _cmd_close {
-       my ($fh, $ctx) = @_;
-       if (not close $fh) {
-               if ($!) {
+       my $ctx = shift @_;
+       foreach my $fh (@_) {
+               if (close $fh) {
+                       # nop
+               } elsif ($!) {
                        # It's just close, no point in fatalities
                        carp "error closing pipe: $!";
                } elsif ($? >> 8) {
index 049c97bfafac143e552f2e8ddb988292a6328df2..6a212eb7a8182acc00eb597d2100310b7855738a 100644 (file)
@@ -295,7 +295,7 @@ sub gs_do_switch {
        my $full_url = add_path_to_url( $self->url, $path );
        my ($ra, $reparented);
 
-       if ($old_url =~ m#^svn(\+ssh)?://# ||
+       if ($old_url =~ m#^svn(\+\w+)?://# ||
            ($full_url =~ m#^https?://# &&
             canonicalize_url($full_url) ne $full_url)) {
                $_[0] = undef;
index eaba15f124b5d8247d39bd0844967cb5a9101ba8..70f19501d0e0c0dd625976519fd039d5c7a8b314 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "pkt-line.h"
 
+char packet_buffer[LARGE_PACKET_MAX];
 static const char *packet_trace_prefix = "git";
 static const char trace_key[] = "GIT_TRACE_PACKET";
 
@@ -46,38 +47,6 @@ static void packet_trace(const char *buf, unsigned int len, int write)
        strbuf_release(&out);
 }
 
-/*
- * Write a packetized stream, where each line is preceded by
- * its length (including the header) as a 4-byte hex number.
- * A length of 'zero' means end of stream (and a length of 1-3
- * would be an error).
- *
- * This is all pretty stupid, but we use this packetized line
- * format to make a streaming format possible without ever
- * over-running the read buffers. That way we'll never read
- * into what might be the pack data (which should go to another
- * process entirely).
- *
- * The writing side could use stdio, but since the reading
- * side can't, we stay with pure read/write interfaces.
- */
-ssize_t safe_write(int fd, const void *buf, ssize_t n)
-{
-       ssize_t nn = n;
-       while (n) {
-               int ret = xwrite(fd, buf, n);
-               if (ret > 0) {
-                       buf = (char *) buf + ret;
-                       n -= ret;
-                       continue;
-               }
-               if (!ret)
-                       die("write error (disk full?)");
-               die_errno("write error");
-       }
-       return nn;
-}
-
 /*
  * If we buffered things up above (we don't, but we should),
  * we'd flush it here
@@ -85,7 +54,7 @@ ssize_t safe_write(int fd, const void *buf, ssize_t n)
 void packet_flush(int fd)
 {
        packet_trace("0000", 4, 1);
-       safe_write(fd, "0000", 4);
+       write_or_die(fd, "0000", 4);
 }
 
 void packet_buf_flush(struct strbuf *buf)
@@ -121,7 +90,7 @@ void packet_write(int fd, const char *fmt, ...)
        va_start(args, fmt);
        n = format_packet(fmt, args);
        va_end(args);
-       safe_write(fd, buffer, n);
+       write_or_die(fd, buffer, n);
 }
 
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
@@ -135,13 +104,29 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
        strbuf_add(buf, buffer, n);
 }
 
-static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail)
+static int get_packet_data(int fd, char **src_buf, size_t *src_size,
+                          void *dst, unsigned size, int options)
 {
-       ssize_t ret = read_in_full(fd, buffer, size);
-       if (ret < 0)
-               die_errno("read error");
-       else if (ret < size) {
-               if (return_line_fail)
+       ssize_t ret;
+
+       if (fd >= 0 && src_buf && *src_buf)
+               die("BUG: multiple sources given to packet_read");
+
+       /* Read up to "size" bytes from our source, whatever it is. */
+       if (src_buf && *src_buf) {
+               ret = size < *src_size ? size : *src_size;
+               memcpy(dst, *src_buf, ret);
+               *src_buf += ret;
+               *src_size -= ret;
+       } else {
+               ret = read_in_full(fd, dst, size);
+               if (ret < 0)
+                       die_errno("read error");
+       }
+
+       /* And complain if we didn't get enough bytes to satisfy the read. */
+       if (ret < size) {
+               if (options & PACKET_READ_GENTLE_ON_EOF)
                        return -1;
 
                die("The remote end hung up unexpectedly");
@@ -175,13 +160,14 @@ static int packet_length(const char *linelen)
        return len;
 }
 
-static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail)
+int packet_read(int fd, char **src_buf, size_t *src_len,
+               char *buffer, unsigned size, int options)
 {
        int len, ret;
        char linelen[4];
 
-       ret = safe_read(fd, linelen, 4, return_line_fail);
-       if (return_line_fail && ret < 0)
+       ret = get_packet_data(fd, src_buf, src_len, linelen, 4, options);
+       if (ret < 0)
                return ret;
        len = packet_length(linelen);
        if (len < 0)
@@ -193,50 +179,37 @@ static int packet_read_internal(int fd, char *buffer, unsigned size, int return_
        len -= 4;
        if (len >= size)
                die("protocol error: bad line length %d", len);
-       ret = safe_read(fd, buffer, len, return_line_fail);
-       if (return_line_fail && ret < 0)
+       ret = get_packet_data(fd, src_buf, src_len, buffer, len, options);
+       if (ret < 0)
                return ret;
+
+       if ((options & PACKET_READ_CHOMP_NEWLINE) &&
+           len && buffer[len-1] == '\n')
+               len--;
+
        buffer[len] = 0;
        packet_trace(buffer, len, 0);
        return len;
 }
 
-int packet_read(int fd, char *buffer, unsigned size)
+static char *packet_read_line_generic(int fd,
+                                     char **src, size_t *src_len,
+                                     int *dst_len)
 {
-       return packet_read_internal(fd, buffer, size, 1);
+       int len = packet_read(fd, src, src_len,
+                             packet_buffer, sizeof(packet_buffer),
+                             PACKET_READ_CHOMP_NEWLINE);
+       if (dst_len)
+               *dst_len = len;
+       return len ? packet_buffer : NULL;
 }
 
-int packet_read_line(int fd, char *buffer, unsigned size)
+char *packet_read_line(int fd, int *len_p)
 {
-       return packet_read_internal(fd, buffer, size, 0);
+       return packet_read_line_generic(fd, NULL, NULL, len_p);
 }
 
-int packet_get_line(struct strbuf *out,
-       char **src_buf, size_t *src_len)
+char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)
 {
-       int len;
-
-       if (*src_len < 4)
-               return -1;
-       len = packet_length(*src_buf);
-       if (len < 0)
-               return -1;
-       if (!len) {
-               *src_buf += 4;
-               *src_len -= 4;
-               packet_trace("0000", 4, 0);
-               return 0;
-       }
-       if (*src_len < len)
-               return -2;
-
-       *src_buf += 4;
-       *src_len -= 4;
-       len -= 4;
-
-       strbuf_add(out, *src_buf, len);
-       *src_buf += len;
-       *src_len -= len;
-       packet_trace(out->buf, out->len, 0);
-       return len;
+       return packet_read_line_generic(-1, src, src_len, dst_len);
 }
index 8cfeb0c31c8661bf11ce78d3679a21cd030ffb75..0a838d1656171019c07fe802d615ecaf78412003 100644 (file)
@@ -5,16 +5,78 @@
 #include "strbuf.h"
 
 /*
- * Silly packetized line writing interface
+ * Write a packetized stream, where each line is preceded by
+ * its length (including the header) as a 4-byte hex number.
+ * A length of 'zero' means end of stream (and a length of 1-3
+ * would be an error).
+ *
+ * This is all pretty stupid, but we use this packetized line
+ * format to make a streaming format possible without ever
+ * over-running the read buffers. That way we'll never read
+ * into what might be the pack data (which should go to another
+ * process entirely).
+ *
+ * The writing side could use stdio, but since the reading
+ * side can't, we stay with pure read/write interfaces.
  */
 void packet_flush(int fd);
 void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 void packet_buf_flush(struct strbuf *buf);
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 
-int packet_read_line(int fd, char *buffer, unsigned size);
-int packet_read(int fd, char *buffer, unsigned size);
-int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len);
-ssize_t safe_write(int, const void *, ssize_t);
+/*
+ * Read a packetized line into the buffer, which must be at least size bytes
+ * long. The return value specifies the number of bytes read into the buffer.
+ *
+ * If src_buffer is not NULL (and nor is *src_buffer), it should point to a
+ * buffer containing the packet data to parse, of at least *src_len bytes.
+ * After the function returns, src_buf will be incremented and src_len
+ * decremented by the number of bytes consumed.
+ *
+ * If src_buffer (or *src_buffer) is NULL, then data is read from the
+ * descriptor "fd".
+ *
+ * If options does not contain PACKET_READ_GENTLE_ON_EOF, we will die under any
+ * of the following conditions:
+ *
+ *   1. Read error from descriptor.
+ *
+ *   2. Protocol error from the remote (e.g., bogus length characters).
+ *
+ *   3. Receiving a packet larger than "size" bytes.
+ *
+ *   4. Truncated output from the remote (e.g., we expected a packet but got
+ *      EOF, or we got a partial packet followed by EOF).
+ *
+ * If options does contain PACKET_READ_GENTLE_ON_EOF, we will not die on
+ * condition 4 (truncated input), but instead return -1. However, we will still
+ * die for the other 3 conditions.
+ *
+ * If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if
+ * present) is removed from the buffer before returning.
+ */
+#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
+#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+int packet_read(int fd, char **src_buffer, size_t *src_len, char
+               *buffer, unsigned size, int options);
+
+/*
+ * Convenience wrapper for packet_read that is not gentle, and sets the
+ * CHOMP_NEWLINE option. The return value is NULL for a flush packet,
+ * and otherwise points to a static buffer (that may be overwritten by
+ * subsequent calls). If the size parameter is not NULL, the length of the
+ * packet is written to it.
+ */
+char *packet_read_line(int fd, int *size);
+
+/*
+ * Same as packet_read_line, but read from a buf rather than a descriptor;
+ * see packet_read for details on how src_* is used.
+ */
+char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);
+
+#define DEFAULT_PACKET_MAX 1000
+#define LARGE_PACKET_MAX 65520
+extern char packet_buffer[LARGE_PACKET_MAX];
 
 #endif
index eae57ad9d7f3b06a5a76f9d934825f9824def477..41f04e669d3b9c38f49e0853053a6ba27b97357a 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -345,7 +345,7 @@ static int needs_rfc2047_encoding(const char *line, int len,
        return 0;
 }
 
-static void add_rfc2047(struct strbuf *sb, const char *line, int len,
+static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,
                       const char *encoding, enum rfc2047_type type)
 {
        static const int max_encoded_length = 76; /* per rfc2047 */
@@ -355,9 +355,22 @@ static void add_rfc2047(struct strbuf *sb, const char *line, int len,
        strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
        strbuf_addf(sb, "=?%s?q?", encoding);
        line_len += strlen(encoding) + 5; /* 5 for =??q? */
-       for (i = 0; i < len; i++) {
-               unsigned ch = line[i] & 0xFF;
-               int is_special = is_rfc2047_special(ch, type);
+
+       while (len) {
+               /*
+                * RFC 2047, section 5 (3):
+                *
+                * Each 'encoded-word' MUST represent an integral number of
+                * characters.  A multi-octet character may not be split across
+                * adjacent 'encoded- word's.
+                */
+               const unsigned char *p = (const unsigned char *)line;
+               int chrlen = mbs_chrlen(&line, &len, encoding);
+               int is_special = (chrlen > 1) || is_rfc2047_special(*p, type);
+
+               /* "=%02X" * chrlen, or the byte itself */
+               const char *encoded_fmt = is_special ? "=%02X"    : "%c";
+               int         encoded_len = is_special ? 3 * chrlen : 1;
 
                /*
                 * According to RFC 2047, we could encode the special character
@@ -367,18 +380,15 @@ static void add_rfc2047(struct strbuf *sb, const char *line, int len,
                 * causes ' ' to be encoded as '=20', avoiding this problem.
                 */
 
-               if (line_len + 2 + (is_special ? 3 : 1) > max_encoded_length) {
+               if (line_len + encoded_len + 2 > max_encoded_length) {
+                       /* It won't fit with trailing "?=" --- break the line */
                        strbuf_addf(sb, "?=\n =?%s?q?", encoding);
                        line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
                }
 
-               if (is_special) {
-                       strbuf_addf(sb, "=%02X", ch);
-                       line_len += 3;
-               } else {
-                       strbuf_addch(sb, ch);
-                       line_len++;
-               }
+               for (i = 0; i < chrlen; i++)
+                       strbuf_addf(sb, encoded_fmt, p[i]);
+               line_len += encoded_len;
        }
        strbuf_addstr(sb, "?=");
 }
@@ -759,8 +769,10 @@ struct format_commit_context {
        unsigned commit_signature_parsed:1;
        struct {
                char *gpg_output;
+               char *gpg_status;
                char good_bad;
                char *signer;
+               char *key;
        } signature;
        char *message;
        size_t width, indent1, indent2;
@@ -948,13 +960,13 @@ static struct {
        char result;
        const char *check;
 } signature_check[] = {
-       { 'G', ": Good signature from " },
-       { 'B', ": BAD signature from " },
+       { 'G', "\n[GNUPG:] GOODSIG " },
+       { 'B', "\n[GNUPG:] BADSIG " },
 };
 
 static void parse_signature_lines(struct format_commit_context *ctx)
 {
-       const char *buf = ctx->signature.gpg_output;
+       const char *buf = ctx->signature.gpg_status;
        int i;
 
        for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
@@ -964,6 +976,8 @@ static void parse_signature_lines(struct format_commit_context *ctx)
                        continue;
                ctx->signature.good_bad = signature_check[i].result;
                found += strlen(signature_check[i].check);
+               ctx->signature.key = xmemdupz(found, 16);
+               found += 17;
                next = strchrnul(found, '\n');
                ctx->signature.signer = xmemdupz(found, next - found);
                break;
@@ -975,6 +989,7 @@ static void parse_commit_signature(struct format_commit_context *ctx)
        struct strbuf payload = STRBUF_INIT;
        struct strbuf signature = STRBUF_INIT;
        struct strbuf gpg_output = STRBUF_INIT;
+       struct strbuf gpg_status = STRBUF_INIT;
        int status;
 
        ctx->commit_signature_parsed = 1;
@@ -984,13 +999,15 @@ static void parse_commit_signature(struct format_commit_context *ctx)
                goto out;
        status = verify_signed_buffer(payload.buf, payload.len,
                                      signature.buf, signature.len,
-                                     &gpg_output);
+                                     &gpg_output, &gpg_status);
        if (status && !gpg_output.len)
                goto out;
        ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+       ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
        parse_signature_lines(ctx);
 
  out:
+       strbuf_release(&gpg_status);
        strbuf_release(&gpg_output);
        strbuf_release(&payload);
        strbuf_release(&signature);
@@ -1200,6 +1217,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                        if (c->signature.signer)
                                strbuf_addstr(sb, c->signature.signer);
                        break;
+               case 'K':
+                       if (c->signature.key)
+                               strbuf_addstr(sb, c->signature.key);
+                       break;
                }
                return 2;
        }
index bf7970661f26664e461ed5d6fd7a1982f7e8fadd..e7e6a1e342200bbf4c37bd561e6a68222349418e 100644 (file)
@@ -152,11 +152,9 @@ static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 
 static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *object = parse_object(sha1);
+       struct object *object = parse_object_or_die(sha1, path);
        struct rev_info *revs = (struct rev_info *)cb_data;
 
-       if (!object)
-               die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
        add_pending_object(revs, object, "");
 
        return 0;
index 670a06bc7996da83650d47a72685315f918e26b2..5a9704f4e5b974a46bc5486373a26816c5b3b4bd 100644 (file)
@@ -46,7 +46,7 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
 {
        struct cache_entry *old = istate->cache[nr];
 
-       remove_name_hash(old);
+       remove_name_hash(istate, old);
        set_index_entry(istate, nr, ce);
        istate->cache_changed = 1;
 }
@@ -460,7 +460,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
        struct cache_entry *ce = istate->cache[pos];
 
        record_resolve_undo(istate, ce);
-       remove_name_hash(ce);
+       remove_name_hash(istate, ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
        if (pos >= istate->cache_nr)
@@ -483,7 +483,7 @@ void remove_marked_cache_entries(struct index_state *istate)
 
        for (i = j = 0; i < istate->cache_nr; i++) {
                if (ce_array[i]->ce_flags & CE_REMOVE)
-                       remove_name_hash(ce_array[i]);
+                       remove_name_hash(istate, ce_array[i]);
                else
                        ce_array[j++] = ce_array[i];
        }
@@ -1515,8 +1515,7 @@ int discard_index(struct index_state *istate)
        istate->cache_changed = 0;
        istate->timestamp.sec = 0;
        istate->timestamp.nsec = 0;
-       istate->name_hash_initialized = 0;
-       free_hash(&istate->name_hash);
+       free_name_hash(istate);
        cache_tree_free(&(istate->cache_tree));
        istate->initialized = 0;
 
diff --git a/refs.c b/refs.c
index 175b9fcaa25eba2ad02564b32eba04c3351978c5..de2d8eb866062649a0d0f23a77e350bca1a182cd 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -803,11 +803,38 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        return line;
 }
 
+/*
+ * Read f, which is a packed-refs file, into dir.
+ *
+ * A comment line of the form "# pack-refs with: " may contain zero or
+ * more traits. We interpret the traits as follows:
+ *
+ *   No traits:
+ *
+ *      Probably no references are peeled. But if the file contains a
+ *      peeled value for a reference, we will use it.
+ *
+ *   peeled:
+ *
+ *      References under "refs/tags/", if they *can* be peeled, *are*
+ *      peeled in this file. References outside of "refs/tags/" are
+ *      probably not peeled even if they could have been, but if we find
+ *      a peeled value for such a reference we will use it.
+ *
+ *   fully-peeled:
+ *
+ *      All references in the file that can be peeled are peeled.
+ *      Inversely (and this is more important), any references in the
+ *      file for which no peeled value is recorded is not peelable. This
+ *      trait should typically be written alongside "peeled" for
+ *      compatibility with older clients, but we do not require it
+ *      (i.e., "peeled" is a no-op if "fully-peeled" is set).
+ */
 static void read_packed_refs(FILE *f, struct ref_dir *dir)
 {
        struct ref_entry *last = NULL;
        char refline[PATH_MAX];
-       int flag = REF_ISPACKED;
+       enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
 
        while (fgets(refline, sizeof(refline), f)) {
                unsigned char sha1[20];
@@ -816,15 +843,20 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
 
                if (!strncmp(refline, header, sizeof(header)-1)) {
                        const char *traits = refline + sizeof(header) - 1;
-                       if (strstr(traits, " peeled "))
-                               flag |= REF_KNOWS_PEELED;
+                       if (strstr(traits, " fully-peeled "))
+                               peeled = PEELED_FULLY;
+                       else if (strstr(traits, " peeled "))
+                               peeled = PEELED_TAGS;
                        /* perhaps other traits later as well */
                        continue;
                }
 
                refname = parse_ref_line(refline, sha1);
                if (refname) {
-                       last = create_ref_entry(refname, sha1, flag, 1);
+                       last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
+                       if (peeled == PEELED_FULLY ||
+                           (peeled == PEELED_TAGS && !prefixcmp(refname, "refs/tags/")))
+                               last->flag |= REF_KNOWS_PEELED;
                        add_ref(dir, last);
                        continue;
                }
@@ -832,8 +864,15 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
                    refline[0] == '^' &&
                    strlen(refline) == 42 &&
                    refline[41] == '\n' &&
-                   !get_sha1_hex(refline + 1, sha1))
+                   !get_sha1_hex(refline + 1, sha1)) {
                        hashcpy(last->u.value.peeled, sha1);
+                       /*
+                        * Regardless of what the file header said,
+                        * we definitely know the value of *this*
+                        * reference:
+                        */
+                       last->flag |= REF_KNOWS_PEELED;
+               }
        }
 }
 
@@ -2293,59 +2332,117 @@ int read_ref_at(const char *refname, unsigned long at_time, int cnt,
        return 1;
 }
 
-int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
+static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
+{
+       unsigned char osha1[20], nsha1[20];
+       char *email_end, *message;
+       unsigned long timestamp;
+       int tz;
+
+       /* old SP new SP name <email> SP time TAB msg LF */
+       if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' ||
+           get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' ||
+           get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' ||
+           !(email_end = strchr(sb->buf + 82, '>')) ||
+           email_end[1] != ' ' ||
+           !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+           !message || message[0] != ' ' ||
+           (message[1] != '+' && message[1] != '-') ||
+           !isdigit(message[2]) || !isdigit(message[3]) ||
+           !isdigit(message[4]) || !isdigit(message[5]))
+               return 0; /* corrupt? */
+       email_end[1] = '\0';
+       tz = strtol(message + 1, NULL, 10);
+       if (message[6] != '\t')
+               message += 6;
+       else
+               message += 7;
+       return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data);
+}
+
+static char *find_beginning_of_line(char *bob, char *scan)
+{
+       while (bob < scan && *(--scan) != '\n')
+               ; /* keep scanning backwards */
+       /*
+        * Return either beginning of the buffer, or LF at the end of
+        * the previous line.
+        */
+       return scan;
+}
+
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)
 {
-       const char *logfile;
-       FILE *logfp;
        struct strbuf sb = STRBUF_INIT;
-       int ret = 0;
+       FILE *logfp;
+       long pos;
+       int ret = 0, at_tail = 1;
 
-       logfile = git_path("logs/%s", refname);
-       logfp = fopen(logfile, "r");
+       logfp = fopen(git_path("logs/%s", refname), "r");
        if (!logfp)
                return -1;
 
-       if (ofs) {
-               struct stat statbuf;
-               if (fstat(fileno(logfp), &statbuf) ||
-                   statbuf.st_size < ofs ||
-                   fseek(logfp, -ofs, SEEK_END) ||
-                   strbuf_getwholeline(&sb, logfp, '\n')) {
-                       fclose(logfp);
-                       strbuf_release(&sb);
-                       return -1;
+       /* Jump to the end */
+       if (fseek(logfp, 0, SEEK_END) < 0)
+               return error("cannot seek back reflog for %s: %s",
+                            refname, strerror(errno));
+       pos = ftell(logfp);
+       while (!ret && 0 < pos) {
+               int cnt;
+               size_t nread;
+               char buf[BUFSIZ];
+               char *endp, *scanp;
+
+               /* Fill next block from the end */
+               cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos;
+               if (fseek(logfp, pos - cnt, SEEK_SET))
+                       return error("cannot seek back reflog for %s: %s",
+                                    refname, strerror(errno));
+               nread = fread(buf, cnt, 1, logfp);
+               if (nread != 1)
+                       return error("cannot read %d bytes from reflog for %s: %s",
+                                    cnt, refname, strerror(errno));
+               pos -= cnt;
+
+               scanp = endp = buf + cnt;
+               if (at_tail && scanp[-1] == '\n')
+                       /* Looking at the final LF at the end of the file */
+                       scanp--;
+               at_tail = 0;
+
+               while (buf < scanp) {
+                       /*
+                        * terminating LF of the previous line, or the beginning
+                        * of the buffer.
+                        */
+                       char *bp;
+
+                       bp = find_beginning_of_line(buf, scanp);
+
+                       if (*bp != '\n') {
+                               strbuf_splice(&sb, 0, 0, buf, endp - buf);
+                               if (pos)
+                                       break; /* need to fill another block */
+                               scanp = buf - 1; /* leave loop */
+                       } else {
+                               /*
+                                * (bp + 1) thru endp is the beginning of the
+                                * current line we have in sb
+                                */
+                               strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
+                               scanp = bp;
+                               endp = bp + 1;
+                       }
+                       ret = show_one_reflog_ent(&sb, fn, cb_data);
+                       strbuf_reset(&sb);
+                       if (ret)
+                               break;
                }
-       }
 
-       while (!strbuf_getwholeline(&sb, logfp, '\n')) {
-               unsigned char osha1[20], nsha1[20];
-               char *email_end, *message;
-               unsigned long timestamp;
-               int tz;
-
-               /* old SP new SP name <email> SP time TAB msg LF */
-               if (sb.len < 83 || sb.buf[sb.len - 1] != '\n' ||
-                   get_sha1_hex(sb.buf, osha1) || sb.buf[40] != ' ' ||
-                   get_sha1_hex(sb.buf + 41, nsha1) || sb.buf[81] != ' ' ||
-                   !(email_end = strchr(sb.buf + 82, '>')) ||
-                   email_end[1] != ' ' ||
-                   !(timestamp = strtoul(email_end + 2, &message, 10)) ||
-                   !message || message[0] != ' ' ||
-                   (message[1] != '+' && message[1] != '-') ||
-                   !isdigit(message[2]) || !isdigit(message[3]) ||
-                   !isdigit(message[4]) || !isdigit(message[5]))
-                       continue; /* corrupt? */
-               email_end[1] = '\0';
-               tz = strtol(message + 1, NULL, 10);
-               if (message[6] != '\t')
-                       message += 6;
-               else
-                       message += 7;
-               ret = fn(osha1, nsha1, sb.buf + 82, timestamp, tz, message,
-                        cb_data);
-               if (ret)
-                       break;
        }
+       if (!ret && sb.len)
+               ret = show_one_reflog_ent(&sb, fn, cb_data);
+
        fclose(logfp);
        strbuf_release(&sb);
        return ret;
@@ -2353,9 +2450,20 @@ int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long
 
 int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
 {
-       return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
-}
+       FILE *logfp;
+       struct strbuf sb = STRBUF_INIT;
+       int ret = 0;
 
+       logfp = fopen(git_path("logs/%s", refname), "r");
+       if (!logfp)
+               return -1;
+
+       while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
+               ret = show_one_reflog_ent(&sb, fn, cb_data);
+       fclose(logfp);
+       strbuf_release(&sb);
+       return ret;
+}
 /*
  * Call fn for each reflog in the namespace indicated by name.  name
  * must be empty or end with '/'.  Name will be used as a scratch
diff --git a/refs.h b/refs.h
index 1b2e2d3a98dea86bbe54dd21782ce58681636c32..a35eafc4ee15493c1473d71b5c2e83a1f9137c1a 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -103,7 +103,7 @@ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
-int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
index 933c69ac2627c1903276b92b457843e12649f39e..93a09a64c3b1689b0f29a26c6cdd601ef298b975 100644 (file)
@@ -76,21 +76,82 @@ struct discovery {
        char *buf_alloc;
        char *buf;
        size_t len;
+       struct ref *refs;
        unsigned proto_git : 1;
 };
 static struct discovery *last_discovery;
 
+static struct ref *parse_git_refs(struct discovery *heads, int for_push)
+{
+       struct ref *list = NULL;
+       get_remote_heads(-1, heads->buf, heads->len, &list,
+                        for_push ? REF_NORMAL : 0, NULL);
+       return list;
+}
+
+static struct ref *parse_info_refs(struct discovery *heads)
+{
+       char *data, *start, *mid;
+       char *ref_name;
+       int i = 0;
+
+       struct ref *refs = NULL;
+       struct ref *ref = NULL;
+       struct ref *last_ref = NULL;
+
+       data = heads->buf;
+       start = NULL;
+       mid = data;
+       while (i < heads->len) {
+               if (!start) {
+                       start = &data[i];
+               }
+               if (data[i] == '\t')
+                       mid = &data[i];
+               if (data[i] == '\n') {
+                       if (mid - start != 40)
+                               die("%sinfo/refs not valid: is this a git repository?", url);
+                       data[i] = 0;
+                       ref_name = mid + 1;
+                       ref = xmalloc(sizeof(struct ref) +
+                                     strlen(ref_name) + 1);
+                       memset(ref, 0, sizeof(struct ref));
+                       strcpy(ref->name, ref_name);
+                       get_sha1_hex(start, ref->old_sha1);
+                       if (!refs)
+                               refs = ref;
+                       if (last_ref)
+                               last_ref->next = ref;
+                       last_ref = ref;
+                       start = NULL;
+               }
+               i++;
+       }
+
+       ref = alloc_ref("HEAD");
+       if (!http_fetch_ref(url, ref) &&
+           !resolve_remote_symref(ref, refs)) {
+               ref->next = refs;
+               refs = ref;
+       } else {
+               free(ref);
+       }
+
+       return refs;
+}
+
 static void free_discovery(struct discovery *d)
 {
        if (d) {
                if (d == last_discovery)
                        last_discovery = NULL;
                free(d->buf_alloc);
+               free_refs(d->refs);
                free(d);
        }
 }
 
-static struct discovery* discover_refs(const char *service)
+static struct discovery* discover_refs(const char *service, int for_push)
 {
        struct strbuf exp = STRBUF_INIT;
        struct strbuf type = STRBUF_INIT;
@@ -138,32 +199,35 @@ static struct discovery* discover_refs(const char *service)
        if (maybe_smart &&
            (5 <= last->len && last->buf[4] == '#') &&
            !strbuf_cmp(&exp, &type)) {
+               char *line;
+
                /*
                 * smart HTTP response; validate that the service
                 * pkt-line matches our request.
                 */
-               if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
-                       die("%s has invalid packet header", refs_url);
-               if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
-                       strbuf_setlen(&buffer, buffer.len - 1);
+               line = packet_read_line_buf(&last->buf, &last->len, NULL);
 
                strbuf_reset(&exp);
                strbuf_addf(&exp, "# service=%s", service);
-               if (strbuf_cmp(&exp, &buffer))
-                       die("invalid server response; got '%s'", buffer.buf);
+               if (strcmp(line, exp.buf))
+                       die("invalid server response; got '%s'", line);
                strbuf_release(&exp);
 
                /* The header can include additional metadata lines, up
                 * until a packet flush marker.  Ignore these now, but
                 * in the future we might start to scan them.
                 */
-               strbuf_reset(&buffer);
-               while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
-                       strbuf_reset(&buffer);
+               while (packet_read_line_buf(&last->buf, &last->len, NULL))
+                       ;
 
                last->proto_git = 1;
        }
 
+       if (last->proto_git)
+               last->refs = parse_git_refs(last, for_push);
+       else
+               last->refs = parse_info_refs(last);
+
        free(refs_url);
        strbuf_release(&exp);
        strbuf_release(&type);
@@ -172,99 +236,16 @@ static struct discovery* discover_refs(const char *service)
        return last;
 }
 
-static int write_discovery(int in, int out, void *data)
-{
-       struct discovery *heads = data;
-       int err = 0;
-       if (write_in_full(out, heads->buf, heads->len) != heads->len)
-               err = 1;
-       close(out);
-       return err;
-}
-
-static struct ref *parse_git_refs(struct discovery *heads, int for_push)
-{
-       struct ref *list = NULL;
-       struct async async;
-
-       memset(&async, 0, sizeof(async));
-       async.proc = write_discovery;
-       async.data = heads;
-       async.out = -1;
-
-       if (start_async(&async))
-               die("cannot start thread to parse advertised refs");
-       get_remote_heads(async.out, &list,
-                       for_push ? REF_NORMAL : 0, NULL);
-       close(async.out);
-       if (finish_async(&async))
-               die("ref parsing thread failed");
-       return list;
-}
-
-static struct ref *parse_info_refs(struct discovery *heads)
-{
-       char *data, *start, *mid;
-       char *ref_name;
-       int i = 0;
-
-       struct ref *refs = NULL;
-       struct ref *ref = NULL;
-       struct ref *last_ref = NULL;
-
-       data = heads->buf;
-       start = NULL;
-       mid = data;
-       while (i < heads->len) {
-               if (!start) {
-                       start = &data[i];
-               }
-               if (data[i] == '\t')
-                       mid = &data[i];
-               if (data[i] == '\n') {
-                       if (mid - start != 40)
-                               die("%sinfo/refs not valid: is this a git repository?", url);
-                       data[i] = 0;
-                       ref_name = mid + 1;
-                       ref = xmalloc(sizeof(struct ref) +
-                                     strlen(ref_name) + 1);
-                       memset(ref, 0, sizeof(struct ref));
-                       strcpy(ref->name, ref_name);
-                       get_sha1_hex(start, ref->old_sha1);
-                       if (!refs)
-                               refs = ref;
-                       if (last_ref)
-                               last_ref->next = ref;
-                       last_ref = ref;
-                       start = NULL;
-               }
-               i++;
-       }
-
-       ref = alloc_ref("HEAD");
-       if (!http_fetch_ref(url, ref) &&
-           !resolve_remote_symref(ref, refs)) {
-               ref->next = refs;
-               refs = ref;
-       } else {
-               free(ref);
-       }
-
-       return refs;
-}
-
 static struct ref *get_refs(int for_push)
 {
        struct discovery *heads;
 
        if (for_push)
-               heads = discover_refs("git-receive-pack");
+               heads = discover_refs("git-receive-pack", for_push);
        else
-               heads = discover_refs("git-upload-pack");
+               heads = discover_refs("git-upload-pack", for_push);
 
-       if (heads->proto_git)
-               return parse_git_refs(heads, for_push);
-       return parse_info_refs(heads);
+       return heads->refs;
 }
 
 static void output_refs(struct ref *refs)
@@ -278,7 +259,6 @@ static void output_refs(struct ref *refs)
        }
        printf("\n");
        fflush(stdout);
-       free_refs(refs);
 }
 
 struct rpc_state {
@@ -308,7 +288,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
 
        if (!avail) {
                rpc->initial_buffer = 0;
-               avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+               avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
                if (!avail)
                        return 0;
                rpc->pos = 0;
@@ -425,7 +405,7 @@ static int post_rpc(struct rpc_state *rpc)
                        break;
                }
 
-               n = packet_read_line(rpc->out, buf, left);
+               n = packet_read(rpc->out, NULL, NULL, buf, left, 0);
                if (!n)
                        break;
                rpc->len += n;
@@ -579,7 +559,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
        rpc->hdr_accept = strbuf_detach(&buf, NULL);
 
        while (!err) {
-               int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+               int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
                if (!n)
                        break;
                rpc->pos = 0;
@@ -685,7 +665,7 @@ static int fetch_git(struct discovery *heads,
 
        err = rpc_service(&rpc, heads);
        if (rpc.result.len)
-               safe_write(1, rpc.result.buf, rpc.result.len);
+               write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
        strbuf_release(&preamble);
        free(depth_arg);
@@ -694,7 +674,7 @@ static int fetch_git(struct discovery *heads,
 
 static int fetch(int nr_heads, struct ref **to_fetch)
 {
-       struct discovery *d = discover_refs("git-upload-pack");
+       struct discovery *d = discover_refs("git-upload-pack", 0);
        if (d->proto_git)
                return fetch_git(d, nr_heads, to_fetch);
        else
@@ -805,7 +785,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
 
        err = rpc_service(&rpc, heads);
        if (rpc.result.len)
-               safe_write(1, rpc.result.buf, rpc.result.len);
+               write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
        free(argv);
        return err;
@@ -813,7 +793,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
 
 static int push(int nr_spec, char **specs)
 {
-       struct discovery *heads = discover_refs("git-receive-pack");
+       struct discovery *heads = discover_refs("git-receive-pack", 1);
        int ret;
 
        if (heads->proto_git)
index e53a6eb7769e2884f77819c73423c31e49b0114e..ca1edd901e4e64cb497b790f675537283c4ee1c0 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -15,6 +15,7 @@ static struct refspec s_tag_refspec = {
        0,
        1,
        0,
+       0,
        "refs/tags/*",
        "refs/tags/*"
 };
@@ -538,7 +539,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
                /*
                 * Before going on, special case ":" (or "+:") as a refspec
-                * for matching refs.
+                * for pushing matching refs.
                 */
                if (!fetch && rhs == lhs && rhs[1] == '\0') {
                        rs[i].matching = 1;
@@ -565,26 +566,25 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
 
                if (fetch) {
-                       /*
-                        * LHS
-                        * - empty is allowed; it means HEAD.
-                        * - otherwise it must be a valid looking ref.
-                        */
+                       unsigned char unused[40];
+
+                       /* LHS */
                        if (!*rs[i].src)
-                               ; /* empty is ok */
-                       else if (check_refname_format(rs[i].src, flags))
+                               ; /* empty is ok; it means "HEAD" */
+                       else if (llen == 40 && !get_sha1_hex(rs[i].src, unused))
+                               rs[i].exact_sha1 = 1; /* ok */
+                       else if (!check_refname_format(rs[i].src, flags))
+                               ; /* valid looking ref is ok */
+                       else
                                goto invalid;
-                       /*
-                        * RHS
-                        * - missing is ok, and is same as empty.
-                        * - empty is ok; it means not to store.
-                        * - otherwise it must be a valid looking ref.
-                        */
+                       /* RHS */
                        if (!rs[i].dst)
-                               ; /* ok */
+                               ; /* missing is ok; it is the same as empty */
                        else if (!*rs[i].dst)
-                               ; /* ok */
-                       else if (check_refname_format(rs[i].dst, flags))
+                               ; /* empty is ok; it means "do not store" */
+                       else if (!check_refname_format(rs[i].dst, flags))
+                               ; /* valid looking ref is ok */
+                       else
                                goto invalid;
                } else {
                        /*
@@ -1195,6 +1195,101 @@ static struct ref **tail_ref(struct ref **head)
        return tail;
 }
 
+struct tips {
+       struct commit **tip;
+       int nr, alloc;
+};
+
+static void add_to_tips(struct tips *tips, const unsigned char *sha1)
+{
+       struct commit *commit;
+
+       if (is_null_sha1(sha1))
+               return;
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit || (commit->object.flags & TMP_MARK))
+               return;
+       commit->object.flags |= TMP_MARK;
+       ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
+       tips->tip[tips->nr++] = commit;
+}
+
+static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
+{
+       struct string_list dst_tag = STRING_LIST_INIT_NODUP;
+       struct string_list src_tag = STRING_LIST_INIT_NODUP;
+       struct string_list_item *item;
+       struct ref *ref;
+       struct tips sent_tips;
+
+       /*
+        * Collect everything we know they would have at the end of
+        * this push, and collect all tags they have.
+        */
+       memset(&sent_tips, 0, sizeof(sent_tips));
+       for (ref = *dst; ref; ref = ref->next) {
+               if (ref->peer_ref &&
+                   !is_null_sha1(ref->peer_ref->new_sha1))
+                       add_to_tips(&sent_tips, ref->peer_ref->new_sha1);
+               else
+                       add_to_tips(&sent_tips, ref->old_sha1);
+               if (!prefixcmp(ref->name, "refs/tags/"))
+                       string_list_append(&dst_tag, ref->name);
+       }
+       clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
+
+       sort_string_list(&dst_tag);
+
+       /* Collect tags they do not have. */
+       for (ref = src; ref; ref = ref->next) {
+               if (prefixcmp(ref->name, "refs/tags/"))
+                       continue; /* not a tag */
+               if (string_list_has_string(&dst_tag, ref->name))
+                       continue; /* they already have it */
+               if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG)
+                       continue; /* be conservative */
+               item = string_list_append(&src_tag, ref->name);
+               item->util = ref;
+       }
+       string_list_clear(&dst_tag, 0);
+
+       /*
+        * At this point, src_tag lists tags that are missing from
+        * dst, and sent_tips lists the tips we are pushing or those
+        * that we know they already have. An element in the src_tag
+        * that is an ancestor of any of the sent_tips needs to be
+        * sent to the other side.
+        */
+       if (sent_tips.nr) {
+               for_each_string_list_item(item, &src_tag) {
+                       struct ref *ref = item->util;
+                       struct ref *dst_ref;
+                       struct commit *commit;
+
+                       if (is_null_sha1(ref->new_sha1))
+                               continue;
+                       commit = lookup_commit_reference_gently(ref->new_sha1, 1);
+                       if (!commit)
+                               /* not pushing a commit, which is not an error */
+                               continue;
+
+                       /*
+                        * Is this tag, which they do not have, reachable from
+                        * any of the commits we are sending?
+                        */
+                       if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip))
+                               continue;
+
+                       /* Add it in */
+                       dst_ref = make_linked_ref(ref->name, dst_tail);
+                       hashcpy(dst_ref->new_sha1, ref->new_sha1);
+                       dst_ref->peer_ref = copy_ref(ref);
+               }
+       }
+       string_list_clear(&src_tag, 0);
+       free(sent_tips.tip);
+}
+
 /*
  * Given the set of refs the local repository has, the set of refs the
  * remote repository has, and the refspec used for push, determine
@@ -1227,9 +1322,6 @@ int match_push_refs(struct ref *src, struct ref **dst,
                const struct refspec *pat = NULL;
                char *dst_name;
 
-               if (ref->peer_ref)
-                       continue;
-
                dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
                if (!dst_name)
                        continue;
@@ -1257,6 +1349,10 @@ int match_push_refs(struct ref *src, struct ref **dst,
        free_name:
                free(dst_name);
        }
+
+       if (flags & MATCH_REFS_FOLLOW_TAGS)
+               add_missing_tags(src, dst, &dst_tail);
+
        if (send_prune) {
                /* check for missing refs on the remote */
                for (ref = *dst; ref; ref = ref->next) {
@@ -1466,7 +1562,12 @@ int get_fetch_map(const struct ref *remote_refs,
        } else {
                const char *name = refspec->src[0] ? refspec->src : "HEAD";
 
-               ref_map = get_remote_ref(remote_refs, name);
+               if (refspec->exact_sha1) {
+                       ref_map = alloc_ref(name);
+                       get_sha1_hex(name, ref_map->old_sha1);
+               } else {
+                       ref_map = get_remote_ref(remote_refs, name);
+               }
                if (!missing_ok && !ref_map)
                        die("Couldn't find remote ref %s", name);
                if (ref_map) {
index 251d8fd9654f23e7a131765742803492fb0d9041..8743d6ef9df69167c08f3ea9b79b91fbd6ab09d3 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -62,6 +62,7 @@ struct refspec {
        unsigned force : 1;
        unsigned pattern : 1;
        unsigned matching : 1;
+       unsigned exact_sha1 : 1;
 
        char *src;
        char *dst;
@@ -148,7 +149,8 @@ enum match_refs_flags {
        MATCH_REFS_NONE         = 0,
        MATCH_REFS_ALL          = (1 << 0),
        MATCH_REFS_MIRROR       = (1 << 1),
-       MATCH_REFS_PRUNE        = (1 << 2)
+       MATCH_REFS_PRUNE        = (1 << 2),
+       MATCH_REFS_FOLLOW_TAGS  = (1 << 3)
 };
 
 /* Reporting of tracking info */
index ef6020541282770b9edc4c921cadb7ab1506da56..71e62d831229773a9af2c4a81d72be5b33d3c921 100644 (file)
@@ -709,7 +709,7 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
         * Does the destination list contain entries with a date
         * before the source list? Definitely _not_ done.
         */
-       if (date < src->item->date)
+       if (date <= src->item->date)
                return SLOP;
 
        /*
@@ -1970,6 +1970,22 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs,
        return st;
 }
 
+static void remove_treesame_parents(struct commit *commit)
+{
+       struct commit_list **pp, *p;
+
+       pp = &commit->parents;
+       while ((p = *pp) != NULL) {
+               struct commit *parent = p->item;
+               if (parent->object.flags & TREESAME) {
+                       *pp = p->next;
+                       free(p);
+                       continue;
+               }
+               pp = &p->next;
+       }
+}
+
 static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
 {
        struct commit_list *p;
@@ -2022,10 +2038,18 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
                if (revs->first_parent_only)
                        break;
        }
-       if (!revs->first_parent_only)
-               cnt = remove_duplicate_parents(commit);
-       else
+
+       if (revs->first_parent_only) {
                cnt = 1;
+       } else {
+               /*
+                * A merge with a tree-same parent is useless
+                */
+               if (commit->parents && commit->parents->next)
+                       remove_treesame_parents(commit);
+
+               cnt = remove_duplicate_parents(commit);
+       }
 
        /*
         * It is possible that we are a merge and one side branch
index 5da09ee3efa976b503cba5d13e347aad0f6c764c..01bd2b7c07719c9628bba13e34b581dc1fdbc0af 100644 (file)
@@ -138,7 +138,7 @@ struct rev_info {
        int             reroll_count;
        char            *message_id;
        struct string_list *ref_message_ids;
-       const char      *add_signoff;
+       int             add_signoff;
        const char      *extra_headers;
        const char      *log_reencode;
        const char      *subject_prefix;
index 97ab336097bfb45c63aec739a6eef2df26cc4790..7d172ef37f7b185f9586ce9015a08481d0bd259c 100644 (file)
@@ -106,15 +106,11 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
 static int receive_status(int in, struct ref *refs)
 {
        struct ref *hint;
-       char line[1000];
        int ret = 0;
-       int len = packet_read_line(in, line, sizeof(line));
-       if (len < 10 || memcmp(line, "unpack ", 7))
+       char *line = packet_read_line(in, NULL);
+       if (prefixcmp(line, "unpack "))
                return error("did not receive remote status");
-       if (memcmp(line, "unpack ok\n", 10)) {
-               char *p = line + strlen(line) - 1;
-               if (*p == '\n')
-                       *p = '\0';
+       if (strcmp(line, "unpack ok")) {
                error("unpack failed: %s", line + 7);
                ret = -1;
        }
@@ -122,17 +118,15 @@ static int receive_status(int in, struct ref *refs)
        while (1) {
                char *refname;
                char *msg;
-               len = packet_read_line(in, line, sizeof(line));
-               if (!len)
+               line = packet_read_line(in, NULL);
+               if (!line)
                        break;
-               if (len < 3 ||
-                   (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
-                       fprintf(stderr, "protocol error: %s\n", line);
+               if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) {
+                       error("invalid ref status from remote: %s", line);
                        ret = -1;
                        break;
                }
 
-               line[strlen(line)-1] = '\0';
                refname = line + 3;
                msg = strchr(refname, ' ');
                if (msg)
@@ -281,7 +275,7 @@ int send_pack(struct send_pack_args *args,
                        send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
                }
        } else {
-               safe_write(out, req_buf.buf, req_buf.len);
+               write_or_die(out, req_buf.buf, req_buf.len);
                packet_flush(out);
        }
        strbuf_release(&req_buf);
index aef5e8a0170c5b337f1aa3ac7a35be3b54957729..baa031052e4e3f77bb6d4a257a5669ca5a5d617e 100644 (file)
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
 const char sign_off_header[] = "Signed-off-by: ";
+static const char cherry_picked_prefix[] = "(cherry picked from commit ";
+
+static int is_rfc2822_line(const char *buf, int len)
+{
+       int i;
+
+       for (i = 0; i < len; i++) {
+               int ch = buf[i];
+               if (ch == ':')
+                       return 1;
+               if (!isalnum(ch) && ch != '-')
+                       break;
+       }
+
+       return 0;
+}
+
+static int is_cherry_picked_from_line(const char *buf, int len)
+{
+       /*
+        * We only care that it looks roughly like (cherry picked from ...)
+        */
+       return len > strlen(cherry_picked_prefix) + 1 &&
+               !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')';
+}
+
+/*
+ * Returns 0 for non-conforming footer
+ * Returns 1 for conforming footer
+ * Returns 2 when sob exists within conforming footer
+ * Returns 3 when sob exists within conforming footer as last entry
+ */
+static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
+       int ignore_footer)
+{
+       char prev;
+       int i, k;
+       int len = sb->len - ignore_footer;
+       const char *buf = sb->buf;
+       int found_sob = 0;
+
+       /* footer must end with newline */
+       if (!len || buf[len - 1] != '\n')
+               return 0;
+
+       prev = '\0';
+       for (i = len - 1; i > 0; i--) {
+               char ch = buf[i];
+               if (prev == '\n' && ch == '\n') /* paragraph break */
+                       break;
+               prev = ch;
+       }
+
+       /* require at least one blank line */
+       if (prev != '\n' || buf[i] != '\n')
+               return 0;
+
+       /* advance to start of last paragraph */
+       while (i < len - 1 && buf[i] == '\n')
+               i++;
+
+       for (; i < len; i = k) {
+               int found_rfc2822;
+
+               for (k = i; k < len && buf[k] != '\n'; k++)
+                       ; /* do nothing */
+               k++;
+
+               found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
+               if (found_rfc2822 && sob &&
+                   !strncmp(buf + i, sob->buf, sob->len))
+                       found_sob = k;
+
+               if (!(found_rfc2822 ||
+                     is_cherry_picked_from_line(buf + i, k - i - 1)))
+                       return 0;
+       }
+       if (found_sob == i)
+               return 3;
+       if (found_sob)
+               return 2;
+       return 1;
+}
 
 static void remove_sequencer_state(void)
 {
@@ -237,7 +320,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        rollback_lock_file(&index_lock);
 
        if (opts->signoff)
-               append_signoff(msgbuf, 0);
+               append_signoff(msgbuf, 0, 0);
 
        if (!clean) {
                int i;
@@ -496,7 +579,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                }
 
                if (opts->record_origin) {
-                       strbuf_addstr(&msgbuf, "(cherry picked from commit ");
+                       if (!has_conforming_footer(&msgbuf, NULL, 0))
+                               strbuf_addch(&msgbuf, '\n');
+                       strbuf_addstr(&msgbuf, cherry_picked_prefix);
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
@@ -1021,62 +1106,67 @@ int sequencer_pick_revisions(struct replay_opts *opts)
        return pick_commits(todo_list, opts);
 }
 
-static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
-{
-       int ch;
-       int hit = 0;
-       int i, j, k;
-       int len = sb->len - ignore_footer;
-       int first = 1;
-       const char *buf = sb->buf;
-
-       for (i = len - 1; i > 0; i--) {
-               if (hit && buf[i] == '\n')
-                       break;
-               hit = (buf[i] == '\n');
-       }
-
-       while (i < len - 1 && buf[i] == '\n')
-               i++;
-
-       for (; i < len; i = k) {
-               for (k = i; k < len && buf[k] != '\n'; k++)
-                       ; /* do nothing */
-               k++;
-
-               if ((buf[k] == ' ' || buf[k] == '\t') && !first)
-                       continue;
-
-               first = 0;
-
-               for (j = 0; i + j < len; j++) {
-                       ch = buf[i + j];
-                       if (ch == ':')
-                               break;
-                       if (isalnum(ch) ||
-                           (ch == '-'))
-                               continue;
-                       return 0;
-               }
-       }
-       return 1;
-}
-
-void append_signoff(struct strbuf *msgbuf, int ignore_footer)
+void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
 {
+       unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
        struct strbuf sob = STRBUF_INIT;
-       int i;
+       int has_footer;
 
        strbuf_addstr(&sob, sign_off_header);
        strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
                                getenv("GIT_COMMITTER_EMAIL")));
        strbuf_addch(&sob, '\n');
-       for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--)
-               ; /* do nothing */
-       if (prefixcmp(msgbuf->buf + i, sob.buf)) {
-               if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer))
-                       strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1);
-               strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len);
+
+       /*
+        * If the whole message buffer is equal to the sob, pretend that we
+        * found a conforming footer with a matching sob
+        */
+       if (msgbuf->len - ignore_footer == sob.len &&
+           !strncmp(msgbuf->buf, sob.buf, sob.len))
+               has_footer = 3;
+       else
+               has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
+
+       if (!has_footer) {
+               const char *append_newlines = NULL;
+               size_t len = msgbuf->len - ignore_footer;
+
+               if (!len) {
+                       /*
+                        * The buffer is completely empty.  Leave foom for
+                        * the title and body to be filled in by the user.
+                        */
+                       append_newlines = "\n\n";
+               } else if (msgbuf->buf[len - 1] != '\n') {
+                       /*
+                        * Incomplete line.  Complete the line and add a
+                        * blank one so that there is an empty line between
+                        * the message body and the sob.
+                        */
+                       append_newlines = "\n\n";
+               } else if (len == 1) {
+                       /*
+                        * Buffer contains a single newline.  Add another
+                        * so that we leave room for the title and body.
+                        */
+                       append_newlines = "\n";
+               } else if (msgbuf->buf[len - 2] != '\n') {
+                       /*
+                        * Buffer ends with a single newline.  Add another
+                        * so that there is an empty line between the message
+                        * body and the sob.
+                        */
+                       append_newlines = "\n";
+               } /* else, the buffer already ends with two newlines. */
+
+               if (append_newlines)
+                       strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
+                               append_newlines, strlen(append_newlines));
        }
+
+       if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
+               strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
+                               sob.buf, sob.len);
+
        strbuf_release(&sob);
 }
index 9d57d57524d411c54d55346a5ecff67bc256595c..1fc22dcabe132cd437e7b0d3c56588c8cd7e63f2 100644 (file)
@@ -6,6 +6,8 @@
 #define SEQ_TODO_FILE  "sequencer/todo"
 #define SEQ_OPTS_FILE  "sequencer/opts"
 
+#define APPEND_SIGNOFF_DEDUP (1u << 0)
+
 enum replay_action {
        REPLAY_REVERT,
        REPLAY_PICK
@@ -48,6 +50,6 @@ int sequencer_pick_revisions(struct replay_opts *opts);
 
 extern const char sign_off_header[];
 
-void append_signoff(struct strbuf *msgbuf, int ignore_footer);
+void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
 
 #endif
diff --git a/setup.c b/setup.c
index 1dee47e0850369c87408c540ce2b2251db17be3d..94c1e61bda747ed5c664bbbf8431234dc8276d29 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -207,10 +207,11 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char
                     *copyfrom && *copyfrom != ')';
                     copyfrom = nextat) {
                        size_t len = strcspn(copyfrom, ",)");
-                       if (copyfrom[len] == ')')
-                               nextat = copyfrom + len;
-                       else
+                       if (copyfrom[len] == ',')
                                nextat = copyfrom + len + 1;
+                       else
+                               /* handle ')' and '\0' */
+                               nextat = copyfrom + len;
                        if (!len)
                                continue;
                        for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
@@ -223,8 +224,9 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char
                                die("Invalid pathspec magic '%.*s' in '%s'",
                                    (int) len, copyfrom, elt);
                }
-               if (*copyfrom == ')')
-                       copyfrom++;
+               if (*copyfrom != ')')
+                       die("Missing ')' at the end of pathspec magic in '%s'", elt);
+               copyfrom++;
        } else {
                /* shorthand */
                for (copyfrom = elt + 1;
@@ -523,6 +525,12 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                        set_git_work_tree(core_worktree);
                }
        }
+       else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
+               /* #16d */
+               set_git_dir(gitdirenv);
+               free(gitfile);
+               return NULL;
+       }
        else /* #2, #10 */
                set_git_work_tree(".");
 
@@ -601,6 +609,8 @@ static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongi
        if (check_repository_format_gently(".", nongit_ok))
                return NULL;
 
+       setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
+
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
                const char *gitdir;
@@ -794,9 +804,9 @@ const char *setup_git_directory_gently(int *nongit_ok)
 
        prefix = setup_git_directory_gently_1(nongit_ok);
        if (prefix)
-               setenv("GIT_PREFIX", prefix, 1);
+               setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
        else
-               setenv("GIT_PREFIX", "", 1);
+               setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
 
        if (startup_info) {
                startup_info->have_repository = !nongit_ok || !*nongit_ok;
index 40b23297b2e1e60a3719e9c67256303e39456604..5f573d9b8107720b1a4f5c1da63a2d8e5b4369cf 100644 (file)
@@ -21,6 +21,7 @@
 #include "sha1-lookup.h"
 #include "bulk-checkin.h"
 #include "streaming.h"
+#include "dir.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -123,8 +124,13 @@ int safe_create_leading_directories(char *path)
                        }
                }
                else if (mkdir(path, 0777)) {
-                       *pos = '/';
-                       return -1;
+                       if (errno == EEXIST &&
+                           !stat(path, &st) && S_ISDIR(st.st_mode)) {
+                               ; /* somebody created it since we checked */
+                       } else {
+                               *pos = '/';
+                               return -1;
+                       }
                }
                else if (adjust_shared_perm(path)) {
                        *pos = '/';
@@ -1000,6 +1006,63 @@ void install_packed_git(struct packed_git *pack)
        packed_git = pack;
 }
 
+void (*report_garbage)(const char *desc, const char *path);
+
+static void report_helper(const struct string_list *list,
+                         int seen_bits, int first, int last)
+{
+       const char *msg;
+       switch (seen_bits) {
+       case 0:
+               msg = "no corresponding .idx nor .pack";
+               break;
+       case 1:
+               msg = "no corresponding .idx";
+               break;
+       case 2:
+               msg = "no corresponding .pack";
+               break;
+       default:
+               return;
+       }
+       for (; first < last; first++)
+               report_garbage(msg, list->items[first].string);
+}
+
+static void report_pack_garbage(struct string_list *list)
+{
+       int i, baselen = -1, first = 0, seen_bits = 0;
+
+       if (!report_garbage)
+               return;
+
+       sort_string_list(list);
+
+       for (i = 0; i < list->nr; i++) {
+               const char *path = list->items[i].string;
+               if (baselen != -1 &&
+                   strncmp(path, list->items[first].string, baselen)) {
+                       report_helper(list, seen_bits, first, i);
+                       baselen = -1;
+                       seen_bits = 0;
+               }
+               if (baselen == -1) {
+                       const char *dot = strrchr(path, '.');
+                       if (!dot) {
+                               report_garbage("garbage found", path);
+                               continue;
+                       }
+                       baselen = dot - path + 1;
+                       first = i;
+               }
+               if (!strcmp(path + baselen, "pack"))
+                       seen_bits |= 1;
+               else if (!strcmp(path + baselen, "idx"))
+                       seen_bits |= 2;
+       }
+       report_helper(list, seen_bits, first, list->nr);
+}
+
 static void prepare_packed_git_one(char *objdir, int local)
 {
        /* Ensure that this buffer is large enough so that we can
@@ -1009,6 +1072,7 @@ static void prepare_packed_git_one(char *objdir, int local)
        int len;
        DIR *dir;
        struct dirent *de;
+       struct string_list garbage = STRING_LIST_INIT_DUP;
 
        sprintf(path, "%s/pack", objdir);
        len = strlen(path);
@@ -1024,29 +1088,49 @@ static void prepare_packed_git_one(char *objdir, int local)
                int namelen = strlen(de->d_name);
                struct packed_git *p;
 
-               if (!has_extension(de->d_name, ".idx"))
+               if (len + namelen + 1 > sizeof(path)) {
+                       if (report_garbage) {
+                               struct strbuf sb = STRBUF_INIT;
+                               strbuf_addf(&sb, "%.*s/%s", len - 1, path, de->d_name);
+                               report_garbage("path too long", sb.buf);
+                               strbuf_release(&sb);
+                       }
                        continue;
+               }
 
-               if (len + namelen + 1 > sizeof(path))
+               if (is_dot_or_dotdot(de->d_name))
                        continue;
 
-               /* Don't reopen a pack we already have. */
                strcpy(path + len, de->d_name);
-               for (p = packed_git; p; p = p->next) {
-                       if (!memcmp(path, p->pack_name, len + namelen - 4))
-                               break;
+
+               if (has_extension(de->d_name, ".idx")) {
+                       /* Don't reopen a pack we already have. */
+                       for (p = packed_git; p; p = p->next) {
+                               if (!memcmp(path, p->pack_name, len + namelen - 4))
+                                       break;
+                       }
+                       if (p == NULL &&
+                           /*
+                            * See if it really is a valid .idx file with
+                            * corresponding .pack file that we can map.
+                            */
+                           (p = add_packed_git(path, len + namelen, local)) != NULL)
+                               install_packed_git(p);
                }
-               if (p)
-                       continue;
-               /* See if it really is a valid .idx file with corresponding
-                * .pack file that we can map.
-                */
-               p = add_packed_git(path, len + namelen, local);
-               if (!p)
+
+               if (!report_garbage)
                        continue;
-               install_packed_git(p);
+
+               if (has_extension(de->d_name, ".idx") ||
+                   has_extension(de->d_name, ".pack") ||
+                   has_extension(de->d_name, ".keep"))
+                       string_list_append(&garbage, path);
+               else
+                       report_garbage("garbage found", path);
        }
        closedir(dir);
+       report_pack_garbage(&garbage);
+       string_list_clear(&garbage, 0);
 }
 
 static int sort_pack(const void *a_, const void *b_)
index c50630a3ea793e0cfd2510b8813129a1685428f4..2fbda48e02f167d08aa19210951712b85f40b602 100644 (file)
@@ -856,8 +856,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
 }
 
 struct grab_nth_branch_switch_cbdata {
-       long cnt, alloc;
-       struct strbuf *buf;
+       int remaining;
+       struct strbuf buf;
 };
 
 static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -867,7 +867,6 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
        struct grab_nth_branch_switch_cbdata *cb = cb_data;
        const char *match = NULL, *target = NULL;
        size_t len;
-       int nth;
 
        if (!prefixcmp(message, "checkout: moving from ")) {
                match = message + strlen("checkout: moving from ");
@@ -876,11 +875,12 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 
        if (!match || !target)
                return 0;
-
-       len = target - match;
-       nth = cb->cnt++ % cb->alloc;
-       strbuf_reset(&cb->buf[nth]);
-       strbuf_add(&cb->buf[nth], match, len);
+       if (--(cb->remaining) == 0) {
+               len = target - match;
+               strbuf_reset(&cb->buf);
+               strbuf_add(&cb->buf, match, len);
+               return 1; /* we are done */
+       }
        return 0;
 }
 
@@ -891,7 +891,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 {
        long nth;
-       int i, retval;
+       int retval;
        struct grab_nth_branch_switch_cbdata cb;
        const char *brace;
        char *num_end;
@@ -901,34 +901,22 @@ static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
        brace = strchr(name, '}');
        if (!brace)
                return -1;
-       nth = strtol(name+3, &num_end, 10);
+       nth = strtol(name + 3, &num_end, 10);
        if (num_end != brace)
                return -1;
        if (nth <= 0)
                return -1;
-       cb.alloc = nth;
-       cb.buf = xmalloc(nth * sizeof(struct strbuf));
-       for (i = 0; i < nth; i++)
-               strbuf_init(&cb.buf[i], 20);
-       cb.cnt = 0;
+       cb.remaining = nth;
+       strbuf_init(&cb.buf, 20);
+
        retval = 0;
-       for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
-       if (cb.cnt < nth) {
-               cb.cnt = 0;
-               for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+       if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) {
+               strbuf_reset(buf);
+               strbuf_add(buf, cb.buf.buf, cb.buf.len);
+               retval = brace - name + 1;
        }
-       if (cb.cnt < nth)
-               goto release_return;
-       i = cb.cnt % nth;
-       strbuf_reset(buf);
-       strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
-       retval = brace-name+1;
-
-release_return:
-       for (i = 0; i < nth; i++)
-               strbuf_release(&cb.buf[i]);
-       free(cb.buf);
 
+       strbuf_release(&cb.buf);
        return retval;
 }
 
diff --git a/shell.c b/shell.c
index 84b237fef352e2f94f853897dbb5f2364d50962d..1429870a8f29d3d8b8e4d1d7ba3e228d15a73759 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -6,6 +6,7 @@
 
 #define COMMAND_DIR "git-shell-commands"
 #define HELP_COMMAND COMMAND_DIR "/help"
+#define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login"
 
 static int do_generic_cmd(const char *me, char *arg)
 {
@@ -65,6 +66,18 @@ static void run_shell(void)
 {
        int done = 0;
        static const char *help_argv[] = { HELP_COMMAND, NULL };
+
+       if (!access(NOLOGIN_COMMAND, F_OK)) {
+               /* Interactive login disabled. */
+               const char *argv[] = { NOLOGIN_COMMAND, NULL };
+               int status;
+
+               status = run_command_v_opt(argv, 0);
+               if (status < 0)
+                       exit(127);
+               exit(status);
+       }
+
        /* Print help if enabled */
        run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
 
index d5ffa1c8919a6db750606c78a1b44d8618fa35a5..d1125f5c528b680f915bb2fbf54231cc8c3b2ec0 100644 (file)
@@ -1,3 +1,4 @@
+#include "cache.h"
 #include "pkt-line.h"
 #include "sideband.h"
 
@@ -37,7 +38,7 @@ int recv_sideband(const char *me, int in_stream, int out)
 
        while (1) {
                int band, len;
-               len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX);
+               len = packet_read(in_stream, NULL, NULL, buf + pf, LARGE_PACKET_MAX, 0);
                if (len == 0)
                        break;
                if (len < 1) {
@@ -108,7 +109,7 @@ int recv_sideband(const char *me, int in_stream, int out)
                        } while (len);
                        continue;
                case 1:
-                       safe_write(out, buf + pf+1, len);
+                       write_or_die(out, buf + pf+1, len);
                        continue;
                default:
                        fprintf(stderr, "%s: protocol error: bad band #%d\n",
@@ -138,12 +139,12 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet
                if (0 <= band) {
                        sprintf(hdr, "%04x", n + 5);
                        hdr[4] = band;
-                       safe_write(fd, hdr, 5);
+                       write_or_die(fd, hdr, 5);
                } else {
                        sprintf(hdr, "%04x", n + 4);
-                       safe_write(fd, hdr, 4);
+                       write_or_die(fd, hdr, 4);
                }
-               safe_write(fd, p, n);
+               write_or_die(fd, p, n);
                p += n;
                sz -= n;
        }
index d72db35d1e0dc109f75b292762013c11b86426aa..e46bed0b0158c0253bacb2a3db028770ad221666 100644 (file)
@@ -4,9 +4,6 @@
 #define SIDEBAND_PROTOCOL_ERROR -2
 #define SIDEBAND_REMOTE_ERROR -1
 
-#define DEFAULT_PACKET_MAX 1000
-#define LARGE_PACKET_MAX 65520
-
 int recv_sideband(const char *me, int in_stream, int out);
 ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
 
index 9ba149654322840323cf2d8f4980fa09e56f4068..975bc87e48b33e70bd7c38e82b7f59e34bb81ca3 100644 (file)
@@ -261,7 +261,7 @@ void show_submodule_summary(FILE *f, const char *path,
                const char *del, const char *add, const char *reset)
 {
        struct rev_info rev;
-       struct commit *left = left, *right = right;
+       struct commit *left = NULL, *right = NULL;
        const char *message = NULL;
        struct strbuf sb = STRBUF_INIT;
        int fast_forward = 0, fast_backward = 0;
@@ -275,10 +275,8 @@ void show_submodule_summary(FILE *f, const char *path,
        else if (!(left = lookup_commit_reference(one)) ||
                 !(right = lookup_commit_reference(two)))
                message = "(commits not present)";
-
-       if (!message &&
-           prepare_submodule_summary(&rev, path, left, right,
-                                       &fast_forward, &fast_backward))
+       else if (prepare_submodule_summary(&rev, path, left, right,
+                                          &fast_forward, &fast_backward))
                message = "(revision walker failed)";
 
        if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
@@ -302,11 +300,12 @@ void show_submodule_summary(FILE *f, const char *path,
                strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset);
        fwrite(sb.buf, sb.len, 1, f);
 
-       if (!message) {
+       if (!message) /* only NULL if we succeeded in setting up the walk */
                print_submodule_summary(&rev, f, del, add, reset);
+       if (left)
                clear_commit_marks(left, ~0);
+       if (right)
                clear_commit_marks(right, ~0);
-       }
 
        strbuf_release(&sb);
 }
index 199f22c231b5ac1479929a89cdf988ac7aff4268..c5e55b190bfbb06eef5a03d701b956e32ad22048 100644 (file)
@@ -148,7 +148,7 @@ stop_httpd () {
 convert_to_rev_db () {
        "$PERL_PATH" -w -- - "$@" <<\EOF
 use strict;
-@ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>";
+@ARGV == 2 or die "usage: convert_to_rev_db <input> <output>";
 open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]";
 open my $rd, '<', $ARGV[0] or die "$!: couldn't open: $ARGV[0]";
 my $size = (stat($rd))[7];
index 5378787e1b23bf18796c28c33533f677b389289c..4e9fa3cd68426942357d8f89ffa97ed3099e1084 100755 (executable)
@@ -3,12 +3,6 @@
 test_description='respect crlf in git archive'
 
 . ./test-lib.sh
-GIT_UNZIP=${GIT_UNZIP:-unzip}
-
-test_lazy_prereq UNZIP '
-       "$GIT_UNZIP" -v
-       test $? -ne 127
-'
 
 test_expect_success setup '
 
index c5334a8fa416aedd66a12416c75e85dbd9b6ac70..62691172e34f0d7b5bdfff2920bab4261f9d3b5c 100755 (executable)
@@ -14,7 +14,7 @@ xmkdir() {
 
 R="$1"
 
-[ -n "$R" ] || die "Usage: prepare-chroot.sh <root>"
+[ -n "$R" ] || die "usage: prepare-chroot.sh <root>"
 [ -x git ] || die "This script needs to be executed at git source code's top directory"
 [ -x /bin/busybox ] || die "You need busybox"
 
index 80aedfca8c15bea1104b4985023f338059ae751c..cf2ee7885ac9c2172deccc27dfb994546593e842 100755 (executable)
@@ -517,6 +517,25 @@ test_expect_success '#16c: bare .git has no worktree' '
                "$here/16c/.git" "(null)" "$here/16c/sub" "(null)"
 '
 
+test_expect_success '#16d: bareness preserved across alias' '
+       setup_repo 16d unset "" unset &&
+       (
+               cd 16d/.git &&
+               test_must_fail git status &&
+               git config alias.st status &&
+               test_must_fail git st
+       )
+'
+
+test_expect_success '#16e: bareness preserved by --bare' '
+       setup_repo 16e unset "" unset &&
+       (
+               cd 16e/.git &&
+               test_must_fail git status &&
+               test_must_fail git --bare status
+       )
+'
+
 test_expect_success '#17: GIT_WORK_TREE without explicit GIT_DIR is accepted (bare case)' '
        # Just like #16.
        setup_repo 17a unset "" true &&
index 02a4fc5d36a08d1046b9384c799f697640da0c4e..ff163cf6750f6d82c36964eb694efbcfae06135f 100755 (executable)
@@ -12,85 +12,108 @@ the GIT controlled paths.
 
 . ./test-lib.sh
 
-test_expect_success \
-    'setup' \
-    'mkdir path1 &&
-    echo frotz >path0 &&
-    echo rezrov >path1/file1 &&
-    git update-index --add path0 path1/file1'
+test_expect_success 'setup' '
+       mkdir path1 &&
+       echo frotz >path0 &&
+       echo rezrov >path1/file1 &&
+       git update-index --add path0 path1/file1
+'
+
+test_expect_success SYMLINKS 'have symlink in place where dir is expected.' '
+       rm -fr path0 path1 &&
+       mkdir path2 &&
+       ln -s path2 path1 &&
+       git checkout-index -f -a &&
+       test ! -h path1 && test -d path1 &&
+       test -f path1/file1 && test ! -f path2/file1
+'
 
-test_expect_success SYMLINKS \
-    'have symlink in place where dir is expected.' \
-    'rm -fr path0 path1 &&
-     mkdir path2 &&
-     ln -s path2 path1 &&
-     git checkout-index -f -a &&
-     test ! -h path1 && test -d path1 &&
-     test -f path1/file1 && test ! -f path2/file1'
+test_expect_success 'use --prefix=path2/' '
+       rm -fr path0 path1 path2 &&
+       mkdir path2 &&
+       git checkout-index --prefix=path2/ -f -a &&
+       test -f path2/path0 &&
+       test -f path2/path1/file1 &&
+       test ! -f path0 &&
+       test ! -f path1/file1
+'
+
+test_expect_success 'use --prefix=tmp-' '
+       rm -fr path0 path1 path2 tmp* &&
+       git checkout-index --prefix=tmp- -f -a &&
+       test -f tmp-path0 &&
+       test -f tmp-path1/file1 &&
+       test ! -f path0 &&
+       test ! -f path1/file1
+'
 
-test_expect_success \
-    'use --prefix=path2/' \
-    'rm -fr path0 path1 path2 &&
-     mkdir path2 &&
-     git checkout-index --prefix=path2/ -f -a &&
-     test -f path2/path0 &&
-     test -f path2/path1/file1 &&
-     test ! -f path0 &&
-     test ! -f path1/file1'
+test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' '
+       rm -fr path0 path1 path2 tmp* &&
+       echo nitfol >tmp-path1 &&
+       mkdir tmp-path0 &&
+       git checkout-index --prefix=tmp- -f -a &&
+       test -f tmp-path0 &&
+       test -f tmp-path1/file1 &&
+       test ! -f path0 &&
+       test ! -f path1/file1
+'
 
-test_expect_success \
-    'use --prefix=tmp-' \
-    'rm -fr path0 path1 path2 tmp* &&
-     git checkout-index --prefix=tmp- -f -a &&
-     test -f tmp-path0 &&
-     test -f tmp-path1/file1 &&
-     test ! -f path0 &&
-     test ! -f path1/file1'
+test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' '
+       rm -fr path0 path1 path2 tmp* &&
+       mkdir tmp1 tmp1/orary &&
+       ln -s tmp1 tmp &&
+       git checkout-index --prefix=tmp/orary/ -f -a &&
+       test -d tmp1/orary &&
+       test -f tmp1/orary/path0 &&
+       test -f tmp1/orary/path1/file1 &&
+       test -h tmp
+'
 
-test_expect_success \
-    'use --prefix=tmp- but with a conflicting file and dir' \
-    'rm -fr path0 path1 path2 tmp* &&
-     echo nitfol >tmp-path1 &&
-     mkdir tmp-path0 &&
-     git checkout-index --prefix=tmp- -f -a &&
-     test -f tmp-path0 &&
-     test -f tmp-path1/file1 &&
-     test ! -f path0 &&
-     test ! -f path1/file1'
+test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' '
+       rm -fr path0 path1 path2 tmp* &&
+       mkdir tmp1 &&
+       ln -s tmp1 tmp &&
+       git checkout-index --prefix=tmp/orary- -f -a &&
+       test -f tmp1/orary-path0 &&
+       test -f tmp1/orary-path1/file1 &&
+       test -h tmp
+'
 
-# Linus fix #1
-test_expect_success SYMLINKS \
-    'use --prefix=tmp/orary/ where tmp is a symlink' \
-    'rm -fr path0 path1 path2 tmp* &&
-     mkdir tmp1 tmp1/orary &&
-     ln -s tmp1 tmp &&
-     git checkout-index --prefix=tmp/orary/ -f -a &&
-     test -d tmp1/orary &&
-     test -f tmp1/orary/path0 &&
-     test -f tmp1/orary/path1/file1 &&
-     test -h tmp'
+test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' '
+       rm -fr path0 path1 path2 tmp* &&
+       mkdir tmp1 &&
+       ln -s tmp1 tmp-path1 &&
+       git checkout-index --prefix=tmp- -f -a &&
+       test -f tmp-path0 &&
+       test ! -h tmp-path1 &&
+       test -d tmp-path1 &&
+       test -f tmp-path1/file1
+'
 
-# Linus fix #2
-test_expect_success SYMLINKS \
-    'use --prefix=tmp/orary- where tmp is a symlink' \
-    'rm -fr path0 path1 path2 tmp* &&
-     mkdir tmp1 &&
-     ln -s tmp1 tmp &&
-     git checkout-index --prefix=tmp/orary- -f -a &&
-     test -f tmp1/orary-path0 &&
-     test -f tmp1/orary-path1/file1 &&
-     test -h tmp'
+test_expect_success 'apply filter from working tree .gitattributes with --prefix' '
+       rm -fr path0 path1 path2 tmp* &&
+       mkdir path1 &&
+       mkdir tmp &&
+       git config filter.replace-all.smudge "sed -e s/./,/g" &&
+       git config filter.replace-all.clean cat &&
+       git config filter.replace-all.required true &&
+       echo "file1 filter=replace-all" >path1/.gitattributes &&
+       git checkout-index --prefix=tmp/ -f -a &&
+       echo frotz >expected &&
+       test_cmp expected tmp/path0 &&
+       echo ,,,,,, >expected &&
+       test_cmp expected tmp/path1/file1
+'
 
-# Linus fix #3
-test_expect_success SYMLINKS \
-    'use --prefix=tmp- where tmp-path1 is a symlink' \
-    'rm -fr path0 path1 path2 tmp* &&
-     mkdir tmp1 &&
-     ln -s tmp1 tmp-path1 &&
-     git checkout-index --prefix=tmp- -f -a &&
-     test -f tmp-path0 &&
-     test ! -h tmp-path1 &&
-     test -d tmp-path1 &&
-     test -f tmp-path1/file1'
+test_expect_success 'apply CRLF filter from working tree .gitattributes with --prefix' '
+       rm -fr path0 path1 path2 tmp* &&
+       mkdir path1 &&
+       mkdir tmp &&
+       echo "file1 eol=crlf" >path1/.gitattributes &&
+       git checkout-index --prefix=tmp/ -f -a &&
+       echo rezrovQ >expected &&
+       tr \\015 Q <tmp/path1/file1 >actual &&
+       test_cmp expected actual
+'
 
 test_done
index 4cdebda6a5c9b893f46e99b5b565cc4b70efb2db..c317254b9a82befd3400d300f85ecfd661d1a948 100755 (executable)
@@ -80,6 +80,22 @@ test_expect_success 'change gets noticed' '
 
 '
 
+# Note that this is scheduled to change in Git 2.0, when
+# "git add -u" will become full-tree by default.
+test_expect_success 'non-limited update in subdir leaves root alone' '
+       (
+               cd dir1 &&
+               echo even more >>sub2 &&
+               git add -u
+       ) &&
+       cat >expect <<-\EOF &&
+       check
+       top
+       EOF
+       git diff-files --name-only >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success SYMLINKS 'replace a file with a symlink' '
 
        rm foo &&
index 12f1e4a63c33b2f394ea37db71e1cd4467cee8f5..b08c9f22951bf447cc769c043a165d087fa5e38d 100755 (executable)
@@ -7,20 +7,18 @@ test_description='git branch assorted tests'
 
 . ./test-lib.sh
 
-test_expect_success \
-    'prepare a trivial repository' \
-    'echo Hello > A &&
-     git update-index --add A &&
-     git commit -m "Initial commit." &&
-     echo World >> A &&
-     git update-index --add A &&
-     git commit -m "Second commit." &&
-     HEAD=$(git rev-parse --verify HEAD)'
-
-test_expect_success \
-    'git branch --help should not have created a bogus branch' '
-     test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
-     test_path_is_missing .git/refs/heads/--help
+test_expect_success 'prepare a trivial repository' '
+       echo Hello >A &&
+       git update-index --add A &&
+       git commit -m "Initial commit." &&
+       echo World >>A &&
+       git update-index --add A &&
+       git commit -m "Second commit." &&
+       HEAD=$(git rev-parse --verify HEAD)'
+
+test_expect_success 'git branch --help should not have created a bogus branch' '
+       test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
+       test_path_is_missing .git/refs/heads/--help
 '
 
 test_expect_success 'branch -h in broken repository' '
@@ -34,67 +32,67 @@ test_expect_success 'branch -h in broken repository' '
        test_i18ngrep "[Uu]sage" broken/usage
 '
 
-test_expect_success \
-    'git branch abc should create a branch' \
-    'git branch abc && test_path_is_file .git/refs/heads/abc'
+test_expect_success 'git branch abc should create a branch' '
+       git branch abc && test_path_is_file .git/refs/heads/abc
+'
 
-test_expect_success \
-    'git branch a/b/c should create a branch' \
-    'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c'
+test_expect_success 'git branch a/b/c should create a branch' '
+       git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
+'
 
-test_expect_success \
-    'git branch HEAD should fail' \
-    'test_must_fail git branch HEAD'
+test_expect_success 'git branch HEAD should fail' '
+       test_must_fail git branch HEAD
+'
 
 cat >expect <<EOF
 $_z40 $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' \
-       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-     git branch -l d/e/f &&
-        test_path_is_file .git/refs/heads/d/e/f &&
-        test_path_is_file .git/logs/refs/heads/d/e/f &&
-        test_cmp expect .git/logs/refs/heads/d/e/f'
-
-test_expect_success \
-    'git branch -d d/e/f should delete a branch and a log' \
-       'git branch -d d/e/f &&
-        test_path_is_missing .git/refs/heads/d/e/f &&
-        test_path_is_missing .git/logs/refs/heads/d/e/f'
-
-test_expect_success \
-    'git branch j/k should work after branch j has been deleted' \
-       'git branch j &&
-        git branch -d j &&
-        git branch j/k'
-
-test_expect_success \
-    'git branch l should work after branch l/m has been deleted' \
-       'git branch l/m &&
-        git branch -d l/m &&
-        git branch l'
-
-test_expect_success \
-    'git branch -m dumps usage' \
-       'test_expect_code 128 git branch -m 2>err &&
-       test_i18ngrep "too many branches for a rename operation" err'
-
-test_expect_success \
-    'git branch -m m m/m should work' \
-       'git branch -l m &&
-        git branch -m m m/m &&
-       test_path_is_file .git/logs/refs/heads/m/m'
-
-test_expect_success \
-    'git branch -m n/n n should work' \
-       'git branch -l n/n &&
+test_expect_success 'git branch -l d/e/f should create a branch and a log' '
+       GIT_COMMITTER_DATE="2005-05-26 23:30" \
+       git branch -l d/e/f &&
+       test_path_is_file .git/refs/heads/d/e/f &&
+       test_path_is_file .git/logs/refs/heads/d/e/f &&
+       test_cmp expect .git/logs/refs/heads/d/e/f
+'
+
+test_expect_success 'git branch -d d/e/f should delete a branch and a log' '
+       git branch -d d/e/f &&
+       test_path_is_missing .git/refs/heads/d/e/f &&
+       test_path_is_missing .git/logs/refs/heads/d/e/f
+'
+
+test_expect_success 'git branch j/k should work after branch j has been deleted' '
+       git branch j &&
+       git branch -d j &&
+       git branch j/k
+'
+
+test_expect_success 'git branch l should work after branch l/m has been deleted' '
+       git branch l/m &&
+       git branch -d l/m &&
+       git branch l
+'
+
+test_expect_success 'git branch -m dumps usage' '
+       test_expect_code 128 git branch -m 2>err &&
+       test_i18ngrep "too many branches for a rename operation" err
+'
+
+test_expect_success 'git branch -m m m/m should work' '
+       git branch -l m &&
+       git branch -m m m/m &&
+       test_path_is_file .git/logs/refs/heads/m/m
+'
+
+test_expect_success 'git branch -m n/n n should work' '
+       git branch -l n/n &&
        git branch -m n/n n &&
-       test_path_is_file .git/logs/refs/heads/n'
+       test_path_is_file .git/logs/refs/heads/n
+'
 
 test_expect_success 'git branch -m o/o o should fail when o/p exists' '
        git branch o/o &&
-        git branch o/p &&
+       git branch o/p &&
        test_must_fail git branch -m o/o o
 '
 
@@ -252,19 +250,20 @@ mv .git/config-saved .git/config
 
 git config branch.s/s.dummy Hello
 
-test_expect_success \
-    'git branch -m s/s s should work when s/t is deleted' \
-       'git branch -l s/s &&
+test_expect_success 'git branch -m s/s s should work when s/t is deleted' '
+       git branch -l s/s &&
        test_path_is_file .git/logs/refs/heads/s/s &&
-        git branch -l s/t &&
+       git branch -l s/t &&
        test_path_is_file .git/logs/refs/heads/s/t &&
-        git branch -d s/t &&
-        git branch -m s/s s &&
-       test_path_is_file .git/logs/refs/heads/s'
+       git branch -d s/t &&
+       git branch -m s/s s &&
+       test_path_is_file .git/logs/refs/heads/s
+'
 
-test_expect_success 'config information was renamed, too' \
-       "test $(git config branch.s.dummy) = Hello &&
-        test_must_fail git config branch.s/s/dummy"
+test_expect_success 'config information was renamed, too' '
+       test $(git config branch.s.dummy) = Hello &&
+       test_must_fail git config branch.s/s/dummy
+'
 
 test_expect_success 'deleting a symref' '
        git branch target &&
@@ -285,8 +284,7 @@ test_expect_success 'deleting a dangling symref' '
        test_i18ncmp expect actual
 '
 
-test_expect_success 'renaming a symref is not allowed' \
-'
+test_expect_success 'renaming a symref is not allowed' '
        git symbolic-ref refs/heads/master2 refs/heads/master &&
        test_must_fail git branch -m master2 master3 &&
        git symbolic-ref refs/heads/master2 &&
@@ -294,163 +292,179 @@ test_expect_success 'renaming a symref is not allowed' \
        test_path_is_missing .git/refs/heads/master3
 '
 
-test_expect_success SYMLINKS \
-    'git branch -m u v should fail when the reflog for u is a symlink' '
-     git branch -l u &&
-     mv .git/logs/refs/heads/u real-u &&
-     ln -s real-u .git/logs/refs/heads/u &&
-     test_must_fail git branch -m u v
-'
-
-test_expect_success 'test tracking setup via --track' \
-    'git config remote.local.url . &&
-     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git show-ref -q refs/remotes/local/master || git fetch local) &&
-     git branch --track my1 local/master &&
-     test $(git config branch.my1.remote) = local &&
-     test $(git config branch.my1.merge) = refs/heads/master'
-
-test_expect_success 'test tracking setup (non-wildcard, matching)' \
-    'git config remote.local.url . &&
-     git config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
-     (git show-ref -q refs/remotes/local/master || git fetch local) &&
-     git branch --track my4 local/master &&
-     test $(git config branch.my4.remote) = local &&
-     test $(git config branch.my4.merge) = refs/heads/master'
-
-test_expect_success 'test tracking setup (non-wildcard, not matching)' \
-    'git config remote.local.url . &&
-     git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
-     (git show-ref -q refs/remotes/local/master || git fetch local) &&
-     git branch --track my5 local/master &&
-     ! test "$(git config branch.my5.remote)" = local &&
-     ! test "$(git config branch.my5.merge)" = refs/heads/master'
-
-test_expect_success 'test tracking setup via config' \
-    'git config branch.autosetupmerge true &&
-     git config remote.local.url . &&
-     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git show-ref -q refs/remotes/local/master || git fetch local) &&
-     git branch my3 local/master &&
-     test $(git config branch.my3.remote) = local &&
-     test $(git config branch.my3.merge) = refs/heads/master'
-
-test_expect_success 'test overriding tracking setup via --no-track' \
-    'git config branch.autosetupmerge true &&
-     git config remote.local.url . &&
-     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git show-ref -q refs/remotes/local/master || git fetch local) &&
-     git branch --no-track my2 local/master &&
-     git config branch.autosetupmerge false &&
-     ! test "$(git config branch.my2.remote)" = local &&
-     ! test "$(git config branch.my2.merge)" = refs/heads/master'
-
-test_expect_success 'no tracking without .fetch entries' \
-    'git config branch.autosetupmerge true &&
-     git branch my6 s &&
-     git config branch.automsetupmerge false &&
-     test -z "$(git config branch.my6.remote)" &&
-     test -z "$(git config branch.my6.merge)"'
-
-test_expect_success 'test tracking setup via --track but deeper' \
-    'git config remote.local.url . &&
-     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git show-ref -q refs/remotes/local/o/o || git fetch local) &&
-     git branch --track my7 local/o/o &&
-     test "$(git config branch.my7.remote)" = local &&
-     test "$(git config branch.my7.merge)" = refs/heads/o/o'
-
-test_expect_success 'test deleting branch deletes branch config' \
-    'git branch -d my7 &&
-     test -z "$(git config branch.my7.remote)" &&
-     test -z "$(git config branch.my7.merge)"'
-
-test_expect_success 'test deleting branch without config' \
-    'git branch my7 s &&
-     sha1=$(git rev-parse my7 | cut -c 1-7) &&
-     echo "Deleted branch my7 (was $sha1)." >expect &&
-     git branch -d my7 >actual 2>&1 &&
-     test_i18ncmp expect actual'
-
-test_expect_success 'test --track without .fetch entries' \
-    'git branch --track my8 &&
-     test "$(git config branch.my8.remote)" &&
-     test "$(git config branch.my8.merge)"'
-
-test_expect_success \
-    'branch from non-branch HEAD w/autosetupmerge=always' \
-    'git config branch.autosetupmerge always &&
-     git branch my9 HEAD^ &&
-     git config branch.autosetupmerge false'
-
-test_expect_success \
-    'branch from non-branch HEAD w/--track causes failure' \
-    'test_must_fail git branch --track my10 HEAD^'
-
-test_expect_success \
-    'branch from tag w/--track causes failure' \
-    'git tag foobar &&
-     test_must_fail git branch --track my11 foobar'
-
-test_expect_success '--set-upstream-to fails on multiple branches' \
-    'test_must_fail git branch --set-upstream-to master a b c'
-
-test_expect_success '--set-upstream-to fails on detached HEAD' \
-    'git checkout HEAD^{} &&
-     test_must_fail git branch --set-upstream-to master &&
-     git checkout -'
-
-test_expect_success 'use --set-upstream-to modify HEAD' \
-    'test_config branch.master.remote foo &&
-     test_config branch.master.merge foo &&
-     git branch my12
-     git branch --set-upstream-to my12 &&
-     test "$(git config branch.master.remote)" = "." &&
-     test "$(git config branch.master.merge)" = "refs/heads/my12"'
-
-test_expect_success 'use --set-upstream-to modify a particular branch' \
-    'git branch my13
-     git branch --set-upstream-to master my13 &&
-     test "$(git config branch.my13.remote)" = "." &&
-     test "$(git config branch.my13.merge)" = "refs/heads/master"'
-
-test_expect_success '--unset-upstream should fail if given a non-existent branch' \
-    'test_must_fail git branch --unset-upstream i-dont-exist'
-
-test_expect_success 'test --unset-upstream on HEAD' \
-    'git branch my14
-     test_config branch.master.remote foo &&
-     test_config branch.master.merge foo &&
-     git branch --set-upstream-to my14 &&
-     git branch --unset-upstream &&
-     test_must_fail git config branch.master.remote &&
-     test_must_fail git config branch.master.merge &&
-     # fail for a branch without upstream set
-     test_must_fail git branch --unset-upstream
-'
-
-test_expect_success '--unset-upstream should fail on multiple branches' \
-    'test_must_fail git branch --unset-upstream a b c'
-
-test_expect_success '--unset-upstream should fail on detached HEAD' \
-    'git checkout HEAD^{} &&
-     test_must_fail git branch --unset-upstream &&
-     git checkout -
-'
-
-test_expect_success 'test --unset-upstream on a particular branch' \
-    'git branch my15
-     git branch --set-upstream-to master my14 &&
-     git branch --unset-upstream my14 &&
-     test_must_fail git config branch.my14.remote &&
-     test_must_fail git config branch.my14.merge'
-
-test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' \
-    'git update-ref refs/remotes/origin/master HEAD &&
-     git branch --set-upstream origin/master 2>actual &&
-     test_when_finished git update-ref -d refs/remotes/origin/master &&
-     test_when_finished git branch -d origin/master &&
-     cat >expected <<EOF &&
+test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '
+       git branch -l u &&
+       mv .git/logs/refs/heads/u real-u &&
+       ln -s real-u .git/logs/refs/heads/u &&
+       test_must_fail git branch -m u v
+'
+
+test_expect_success 'test tracking setup via --track' '
+       git config remote.local.url . &&
+       git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+       (git show-ref -q refs/remotes/local/master || git fetch local) &&
+       git branch --track my1 local/master &&
+       test $(git config branch.my1.remote) = local &&
+       test $(git config branch.my1.merge) = refs/heads/master
+'
+
+test_expect_success 'test tracking setup (non-wildcard, matching)' '
+       git config remote.local.url . &&
+       git config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
+       (git show-ref -q refs/remotes/local/master || git fetch local) &&
+       git branch --track my4 local/master &&
+       test $(git config branch.my4.remote) = local &&
+       test $(git config branch.my4.merge) = refs/heads/master
+'
+
+test_expect_success 'test tracking setup (non-wildcard, not matching)' '
+       git config remote.local.url . &&
+       git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
+       (git show-ref -q refs/remotes/local/master || git fetch local) &&
+       git branch --track my5 local/master &&
+       ! test "$(git config branch.my5.remote)" = local &&
+       ! test "$(git config branch.my5.merge)" = refs/heads/master
+'
+
+test_expect_success 'test tracking setup via config' '
+       git config branch.autosetupmerge true &&
+       git config remote.local.url . &&
+       git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+       (git show-ref -q refs/remotes/local/master || git fetch local) &&
+       git branch my3 local/master &&
+       test $(git config branch.my3.remote) = local &&
+       test $(git config branch.my3.merge) = refs/heads/master
+'
+
+test_expect_success 'test overriding tracking setup via --no-track' '
+       git config branch.autosetupmerge true &&
+       git config remote.local.url . &&
+       git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+       (git show-ref -q refs/remotes/local/master || git fetch local) &&
+       git branch --no-track my2 local/master &&
+       git config branch.autosetupmerge false &&
+       ! test "$(git config branch.my2.remote)" = local &&
+       ! test "$(git config branch.my2.merge)" = refs/heads/master
+'
+
+test_expect_success 'no tracking without .fetch entries' '
+       git config branch.autosetupmerge true &&
+       git branch my6 s &&
+       git config branch.automsetupmerge false &&
+       test -z "$(git config branch.my6.remote)" &&
+       test -z "$(git config branch.my6.merge)"
+'
+
+test_expect_success 'test tracking setup via --track but deeper' '
+       git config remote.local.url . &&
+       git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+       (git show-ref -q refs/remotes/local/o/o || git fetch local) &&
+       git branch --track my7 local/o/o &&
+       test "$(git config branch.my7.remote)" = local &&
+       test "$(git config branch.my7.merge)" = refs/heads/o/o
+'
+
+test_expect_success 'test deleting branch deletes branch config' '
+       git branch -d my7 &&
+       test -z "$(git config branch.my7.remote)" &&
+       test -z "$(git config branch.my7.merge)"
+'
+
+test_expect_success 'test deleting branch without config' '
+       git branch my7 s &&
+       sha1=$(git rev-parse my7 | cut -c 1-7) &&
+       echo "Deleted branch my7 (was $sha1)." >expect &&
+       git branch -d my7 >actual 2>&1 &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'test --track without .fetch entries' '
+       git branch --track my8 &&
+       test "$(git config branch.my8.remote)" &&
+       test "$(git config branch.my8.merge)"
+'
+
+test_expect_success 'branch from non-branch HEAD w/autosetupmerge=always' '
+       git config branch.autosetupmerge always &&
+       git branch my9 HEAD^ &&
+       git config branch.autosetupmerge false
+'
+
+test_expect_success 'branch from non-branch HEAD w/--track causes failure' '
+       test_must_fail git branch --track my10 HEAD^
+'
+
+test_expect_success 'branch from tag w/--track causes failure' '
+       git tag foobar &&
+       test_must_fail git branch --track my11 foobar
+'
+
+test_expect_success '--set-upstream-to fails on multiple branches' '
+       test_must_fail git branch --set-upstream-to master a b c
+'
+
+test_expect_success '--set-upstream-to fails on detached HEAD' '
+       git checkout HEAD^{} &&
+       test_must_fail git branch --set-upstream-to master &&
+       git checkout -
+'
+
+test_expect_success 'use --set-upstream-to modify HEAD' '
+       test_config branch.master.remote foo &&
+       test_config branch.master.merge foo &&
+       git branch my12
+       git branch --set-upstream-to my12 &&
+       test "$(git config branch.master.remote)" = "." &&
+       test "$(git config branch.master.merge)" = "refs/heads/my12"
+'
+
+test_expect_success 'use --set-upstream-to modify a particular branch' '
+       git branch my13
+       git branch --set-upstream-to master my13 &&
+       test "$(git config branch.my13.remote)" = "." &&
+       test "$(git config branch.my13.merge)" = "refs/heads/master"
+'
+
+test_expect_success '--unset-upstream should fail if given a non-existent branch' '
+       test_must_fail git branch --unset-upstream i-dont-exist
+'
+
+test_expect_success 'test --unset-upstream on HEAD' '
+       git branch my14
+       test_config branch.master.remote foo &&
+       test_config branch.master.merge foo &&
+       git branch --set-upstream-to my14 &&
+       git branch --unset-upstream &&
+       test_must_fail git config branch.master.remote &&
+       test_must_fail git config branch.master.merge &&
+       # fail for a branch without upstream set
+       test_must_fail git branch --unset-upstream
+'
+
+test_expect_success '--unset-upstream should fail on multiple branches' '
+       test_must_fail git branch --unset-upstream a b c
+'
+
+test_expect_success '--unset-upstream should fail on detached HEAD' '
+       git checkout HEAD^{} &&
+       test_must_fail git branch --unset-upstream &&
+       git checkout -
+'
+
+test_expect_success 'test --unset-upstream on a particular branch' '
+       git branch my15
+       git branch --set-upstream-to master my14 &&
+       git branch --unset-upstream my14 &&
+       test_must_fail git config branch.my14.remote &&
+       test_must_fail git config branch.my14.merge
+'
+
+test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' '
+       git update-ref refs/remotes/origin/master HEAD &&
+       git branch --set-upstream origin/master 2>actual &&
+       test_when_finished git update-ref -d refs/remotes/origin/master &&
+       test_when_finished git branch -d origin/master &&
+       cat >expected <<EOF &&
 The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
 
 If you wanted to make '"'master'"' track '"'origin/master'"', do this:
@@ -458,38 +472,38 @@ If you wanted to make '"'master'"' track '"'origin/master'"', do this:
     git branch -d origin/master
     git branch --set-upstream-to origin/master
 EOF
-     test_cmp expected actual
+       test_cmp expected actual
 '
 
-test_expect_success '--set-upstream with two args only shows the deprecation message' \
-    'git branch --set-upstream master my13 2>actual &&
-     test_when_finished git branch --unset-upstream master &&
-     cat >expected <<EOF &&
+test_expect_success '--set-upstream with two args only shows the deprecation message' '
+       git branch --set-upstream master my13 2>actual &&
+       test_when_finished git branch --unset-upstream master &&
+       cat >expected <<EOF &&
 The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
 EOF
-     test_cmp expected actual
+       test_cmp expected actual
 '
 
-test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' \
-    'git branch --set-upstream my13 2>actual &&
-     test_when_finished git branch --unset-upstream my13 &&
-     cat >expected <<EOF &&
+test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' '
+       git branch --set-upstream my13 2>actual &&
+       test_when_finished git branch --unset-upstream my13 &&
+       cat >expected <<EOF &&
 The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
 EOF
-     test_cmp expected actual
+       test_cmp expected actual
 '
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
 $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000        branch: Created from master
 EOF
-test_expect_success \
-    'git checkout -b g/h/i -l should create a branch and a log' \
-       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-     git checkout -b g/h/i -l master &&
-        test_path_is_file .git/refs/heads/g/h/i &&
-        test_path_is_file .git/logs/refs/heads/g/h/i &&
-        test_cmp expect .git/logs/refs/heads/g/h/i'
+test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
+       GIT_COMMITTER_DATE="2005-05-26 23:30" \
+       git checkout -b g/h/i -l master &&
+       test_path_is_file .git/refs/heads/g/h/i &&
+       test_path_is_file .git/logs/refs/heads/g/h/i &&
+       test_cmp expect .git/logs/refs/heads/g/h/i
+'
 
 test_expect_success 'checkout -b makes reflog by default' '
        git checkout master &&
@@ -760,7 +774,7 @@ test_expect_success 'detect misconfigured autosetuprebase (bad value)' '
 
 test_expect_success 'detect misconfigured autosetuprebase (no value)' '
        git config --unset branch.autosetuprebase &&
-       echo "[branch] autosetuprebase" >> .git/config &&
+       echo "[branch] autosetuprebase" >>.git/config &&
        test_must_fail git branch &&
        git config --unset branch.autosetuprebase
 '
index 76fe7e0060c20507cb6eb317451b69a2a0c9146d..ba4f98e800f262242ef7925f82dc8d13272fe3ca 100755 (executable)
@@ -94,13 +94,13 @@ test_expect_success 'git branch -v pattern does not show branch summaries' '
        test_must_fail git branch -v branch*
 '
 
-cat >expect <<'EOF'
-* (no branch)
+test_expect_success 'git branch shows detached HEAD properly' '
+       cat >expect <<EOF &&
+* (detached from $(git rev-parse --short HEAD^0))
   branch-one
   branch-two
   master
 EOF
-test_expect_success 'git branch shows detached HEAD properly' '
        git checkout HEAD^0 &&
        git branch >actual &&
        test_i18ncmp expect actual
diff --git a/t/t3211-peel-ref.sh b/t/t3211-peel-ref.sh
new file mode 100755 (executable)
index 0000000..d4d7792
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='tests for the peel_ref optimization of packed-refs'
+. ./test-lib.sh
+
+test_expect_success 'create annotated tag in refs/tags' '
+       test_commit base &&
+       git tag -m annotated foo
+'
+
+test_expect_success 'create annotated tag outside of refs/tags' '
+       git update-ref refs/outside/foo refs/tags/foo
+'
+
+# This matches show-ref's output
+print_ref() {
+       echo "$(git rev-parse "$1") $1"
+}
+
+test_expect_success 'set up expected show-ref output' '
+       {
+               print_ref "refs/heads/master" &&
+               print_ref "refs/outside/foo" &&
+               print_ref "refs/outside/foo^{}" &&
+               print_ref "refs/tags/base" &&
+               print_ref "refs/tags/foo" &&
+               print_ref "refs/tags/foo^{}"
+       } >expect
+'
+
+test_expect_success 'refs are peeled outside of refs/tags (loose)' '
+       git show-ref -d >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'refs are peeled outside of refs/tags (packed)' '
+       git pack-refs --all &&
+       git show-ref -d >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create old-style pack-refs without fully-peeled' '
+       # Git no longer writes without fully-peeled, so we just write our own
+       # from scratch; we could also munge the existing file to remove the
+       # fully-peeled bits, but that seems even more prone to failure,
+       # especially if the format ever changes again. At least this way we
+       # know we are emulating exactly what an older git would have written.
+       {
+               echo "# pack-refs with: peeled " &&
+               print_ref "refs/heads/master" &&
+               print_ref "refs/outside/foo" &&
+               print_ref "refs/tags/base" &&
+               print_ref "refs/tags/foo" &&
+               echo "^$(git rev-parse "refs/tags/foo^{}")"
+       } >tmp &&
+       mv tmp .git/packed-refs
+'
+
+test_expect_success 'refs are peeled outside of refs/tags (old packed)' '
+       git show-ref -d >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 1de0ebda25c1034fc1b343a8f0e3da9c2c7f8c6e..f6cc10265700730999174347704a0df6989c7f1b 100755 (executable)
@@ -138,8 +138,7 @@ test_expect_success 'rebase a single mode change' '
 '
 
 test_expect_success 'rebase is not broken by diff.renames' '
-       git config diff.renames copies &&
-       test_when_finished "git config --unset diff.renames" &&
+       test_config diff.renames copies &&
        git checkout filemove &&
        GIT_TRACE=1 git rebase force-3way
 '
index 15dcbd42d367c3ef6b487e0f235ad201b6cf8413..a58406d12fc8dabf8f2e73e721d2f622a781cc9a 100755 (executable)
@@ -937,8 +937,7 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' '
 test_expect_success 'rebase -i respects core.commentchar' '
        git reset --hard &&
        git checkout E^0 &&
-       git config core.commentchar "\\" &&
-       test_when_finished "git config --unset core.commentchar" &&
+       test_config core.commentchar "\\" &&
        write_script remove-all-but-first.sh <<-\EOF &&
        sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" &&
        mv "$1.tmp" "$1"
diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh
new file mode 100755 (executable)
index 0000000..f977279
--- /dev/null
@@ -0,0 +1,219 @@
+#!/bin/sh
+
+test_description='Test cherry-pick -x and -s'
+
+. ./test-lib.sh
+
+pristine_detach () {
+       git cherry-pick --quit &&
+       git checkout -f "$1^0" &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x
+}
+
+mesg_one_line='base: commit message'
+
+mesg_no_footer="$mesg_one_line
+
+OneWordBodyThatsNotA-S-o-B"
+
+mesg_with_footer="$mesg_no_footer
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+Signed-off-by: A.U. Thor <author@example.com>
+Signed-off-by: B.U. Thor <buthor@example.com>"
+
+mesg_broken_footer="$mesg_no_footer
+
+The signed-off-by string should begin with the words Signed-off-by followed
+by a colon and space, and then the signers name and email address. e.g.
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+
+mesg_with_footer_sob="$mesg_with_footer
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+
+mesg_with_cherry_footer="$mesg_with_footer_sob
+(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+Tested-by: C.U. Thor <cuthor@example.com>"
+
+
+test_expect_success setup '
+       git config advice.detachedhead false &&
+       echo unrelated >unrelated &&
+       git add unrelated &&
+       test_commit initial foo a &&
+       test_commit "$mesg_one_line" foo b mesg-one-line &&
+       git reset --hard initial &&
+       test_commit "$mesg_no_footer" foo b mesg-no-footer &&
+       git reset --hard initial &&
+       test_commit "$mesg_broken_footer" foo b mesg-broken-footer &&
+       git reset --hard initial &&
+       test_commit "$mesg_with_footer" foo b mesg-with-footer &&
+       git reset --hard initial &&
+       test_commit "$mesg_with_footer_sob" foo b mesg-with-footer-sob &&
+       git reset --hard initial &&
+       test_commit "$mesg_with_cherry_footer" foo b mesg-with-cherry-footer &&
+       pristine_detach initial &&
+       test_commit conflicting unrelated
+'
+
+test_expect_success 'cherry-pick -x inserts blank line after one line subject' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-one-line^0` &&
+       git cherry-pick -x mesg-one-line &&
+       cat <<-EOF >expect &&
+               $mesg_one_line
+
+               (cherry picked from commit $sha1)
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s inserts blank line after one line subject' '
+       pristine_detach initial &&
+       git cherry-pick -s mesg-one-line &&
+       cat <<-EOF >expect &&
+               $mesg_one_line
+
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s inserts blank line after non-conforming footer' '
+       pristine_detach initial &&
+       git cherry-pick -s mesg-broken-footer &&
+       cat <<-EOF >expect &&
+               $mesg_broken_footer
+
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-no-footer^0` &&
+       git cherry-pick -x mesg-no-footer &&
+       cat <<-EOF >expect &&
+               $mesg_no_footer
+
+               (cherry picked from commit $sha1)
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s inserts blank line when conforming footer not found' '
+       pristine_detach initial &&
+       git cherry-pick -s mesg-no-footer &&
+       cat <<-EOF >expect &&
+               $mesg_no_footer
+
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s inserts blank line when conforming footer not found' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-no-footer^0` &&
+       git cherry-pick -x -s mesg-no-footer &&
+       cat <<-EOF >expect &&
+               $mesg_no_footer
+
+               (cherry picked from commit $sha1)
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committer' '
+       pristine_detach initial &&
+       git cherry-pick -s mesg-with-footer &&
+       cat <<-EOF >expect &&
+               $mesg_with_footer
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s adds sob when last sob doesnt match committer' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-with-footer^0` &&
+       git cherry-pick -x -s mesg-with-footer &&
+       cat <<-EOF >expect &&
+               $mesg_with_footer
+               (cherry picked from commit $sha1)
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob' '
+       pristine_detach initial &&
+       git cherry-pick -s mesg-with-footer-sob &&
+       cat <<-EOF >expect &&
+               $mesg_with_footer_sob
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists for committer' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-with-footer-sob^0` &&
+       git cherry-pick -x -s mesg-with-footer-sob &&
+       cat <<-EOF >expect &&
+               $mesg_with_footer_sob
+               (cherry picked from commit $sha1)
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x treats "(cherry picked from..." line as part of footer' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-with-cherry-footer^0` &&
+       git cherry-pick -x mesg-with-cherry-footer &&
+       cat <<-EOF >expect &&
+               $mesg_with_cherry_footer
+               (cherry picked from commit $sha1)
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part of footer' '
+       pristine_detach initial &&
+       git cherry-pick -s mesg-with-cherry-footer &&
+       cat <<-EOF >expect &&
+               $mesg_with_cherry_footer
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as part of footer' '
+       pristine_detach initial &&
+       sha1=`git rev-parse mesg-with-cherry-footer^0` &&
+       git cherry-pick -x -s mesg-with-cherry-footer &&
+       cat <<-EOF >expect &&
+               $mesg_with_cherry_footer
+               (cherry picked from commit $sha1)
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 844277cfa605f51cccd5d78a48a83fb75c03af9b..2f327b749588af7eda57187cba4d365233a25ef9 100755 (executable)
@@ -102,4 +102,58 @@ test_expect_success 'setup for many rename source candidates' '
        grep warning actual.err
 '
 
+test_expect_success 'rename pretty print with nothing in common' '
+       mkdir -p a/b/ &&
+       : >a/b/c &&
+       git add a/b/c &&
+       git commit -m "create a/b/c" &&
+       mkdir -p c/b/ &&
+       git mv a/b/c c/b/a &&
+       git commit -m "a/b/c -> c/b/a" &&
+       git diff -M --summary HEAD^ HEAD >output &&
+       test_i18ngrep " a/b/c => c/b/a " output &&
+       git diff -M --stat HEAD^ HEAD >output &&
+       test_i18ngrep " a/b/c => c/b/a " output
+'
+
+test_expect_success 'rename pretty print with common prefix' '
+       mkdir -p c/d &&
+       git mv c/b/a c/d/e &&
+       git commit -m "c/b/a -> c/d/e" &&
+       git diff -M --summary HEAD^ HEAD >output &&
+       test_i18ngrep " c/{b/a => d/e} " output &&
+       git diff -M --stat HEAD^ HEAD >output &&
+       test_i18ngrep " c/{b/a => d/e} " output
+'
+
+test_expect_success 'rename pretty print with common suffix' '
+       mkdir d &&
+       git mv c/d/e d/e &&
+       git commit -m "c/d/e -> d/e" &&
+       git diff -M --summary HEAD^ HEAD >output &&
+       test_i18ngrep " {c/d => d}/e " output &&
+       git diff -M --stat HEAD^ HEAD >output &&
+       test_i18ngrep " {c/d => d}/e " output
+'
+
+test_expect_success 'rename pretty print with common prefix and suffix' '
+       mkdir d/f &&
+       git mv d/e d/f/e &&
+       git commit -m "d/e -> d/f/e" &&
+       git diff -M --summary HEAD^ HEAD >output &&
+       test_i18ngrep " d/{ => f}/e " output &&
+       git diff -M --stat HEAD^ HEAD >output &&
+       test_i18ngrep " d/{ => f}/e " output
+'
+
+test_expect_success 'rename pretty print common prefix and suffix overlap' '
+       mkdir d/f/f &&
+       git mv d/f/e d/f/f/e &&
+       git commit -m "d/f/e d/f/f/e" &&
+       git diff -M --summary HEAD^ HEAD >output &&
+       test_i18ngrep " d/f/{ => f}/e " output &&
+       git diff -M --stat HEAD^ HEAD >output &&
+       test_i18ngrep " d/f/{ => f}/e " output
+'
+
 test_done
index 7fa3647514b616b1a40b233b23123a00c4d3ff6c..b993dae64574cd2828fc636d3afc15b2c0c2e5a0 100755 (executable)
@@ -837,25 +837,26 @@ Subject: [PATCH] =?UTF-8?q?f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
  =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
  =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
  =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
- =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?=
- =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
- =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?=
- =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
  =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
  =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
  =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
- =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?=
- =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
- =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?=
- =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
  =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
  =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
  =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
- =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?=
- =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
- =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?=
- =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
- =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+ =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
+ =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+ =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
+ =?UTF-8?q?bar?=
 EOF
 test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
        rm -rf patches/ &&
@@ -971,6 +972,268 @@ test_expect_success 'empty subject prefix does not have extra space' '
        test_cmp expect actual
 '
 
+append_signoff()
+{
+       C=$(git commit-tree HEAD^^{tree} -p HEAD) &&
+       git format-patch --stdout --signoff $C^..$C >append_signoff.patch &&
+       sed -n -e "1,/^---$/p" append_signoff.patch |
+               egrep -n "^Subject|Sign|^$"
+}
+
+test_expect_success 'signoff: commit with no body' '
+       append_signoff </dev/null >actual &&
+       cat <<\EOF | sed "s/EOL$//" >expected &&
+4:Subject: [PATCH] EOL
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: commit with only subject' '
+       echo subject | append_signoff >actual &&
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: commit with only subject that does not end with NL' '
+       printf subject | append_signoff >actual &&
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: no existing signoffs' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: no existing signoffs and no trailing NL' '
+       printf "subject\n\nbody" | append_signoff >actual &&
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: some random signoff' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: my@house
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: my@house
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: misc conforming footer elements' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: my@house
+(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+Tested-by: Some One <someone@example.com>
+Bug: 1234
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: my@house
+15:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: some random signoff-alike' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+Fooled-by-me: my@house
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+11:
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: not really a signoff' '
+       append_signoff <<\EOF >actual &&
+subject
+
+I want to mention about Signed-off-by: here.
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:I want to mention about Signed-off-by: here.
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: not really a signoff (2)' '
+       append_signoff <<\EOF >actual &&
+subject
+
+My unfortunate
+Signed-off-by: example happens to be wrapped here.
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:Signed-off-by: example happens to be wrapped here.
+11:
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
+       append_signoff <<\EOF >actual &&
+subject
+
+Signed-off-by: my@house
+Signed-off-by: your@house
+
+A lot of houses.
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: my@house
+10:Signed-off-by: your@house
+11:
+13:
+14:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: the same signoff at the end' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
+       printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
+               append_signoff >actual &&
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: the same signoff NOT at the end' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: C O Mitter <committer@example.com>
+Signed-off-by: my@house
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+12:Signed-off-by: my@house
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: detect garbage in non-conforming footer' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Tested-by: my@house
+Some Trash
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+13:Signed-off-by: C O Mitter <committer@example.com>
+14:
+15:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
+       append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Reviewed-id: Noone
+Tested-by: my@house
+Change-id: Ideadbeef
+Signed-off-by: C O Mitter <committer@example.com>
+Bug: 1234
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+14:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
 test_expect_success 'format patch ignores color.ui' '
        test_unconfig color.ui &&
        git format-patch --stdout -1 >expect &&
index 082d3e83bddf242fb02f5124fd52d125520af6b8..38a092a0dadbe09b903484c1d34e240a9d819c77 100755 (executable)
@@ -93,11 +93,6 @@ sed -e '
        s/song;/song();/
 ' <Beer.perl >Beer-correct.perl
 
-test_config () {
-       git config "$1" "$2" &&
-       test_when_finished "git config --unset $1"
-}
-
 test_expect_funcname () {
        lang=${2-java}
        test_expect_code 1 git diff --no-index -U1 \
index 40ab333a8a4849d620e1d41a75f611921f83055a..f2f55fc51ccd294194300d4bbf66307ac73f9ac6 100755 (executable)
@@ -230,7 +230,7 @@ test_expect_success '.gitattributes override config' '
 '
 
 test_expect_success 'setup: remove diff driver regex' '
-       test_might_fail git config --unset diff.testdriver.wordRegex
+       test_unconfig diff.testdriver.wordRegex
 '
 
 test_expect_success 'use configured regex' '
@@ -335,8 +335,7 @@ test_expect_success 'word-diff with diff.sbe' '
 
        c
        EOF
-       test_when_finished "git config --unset diff.suppress-blank-empty" &&
-       git config diff.suppress-blank-empty true &&
+       test_config diff.suppress-blank-empty true &&
        word_diff --word-diff=plain
 '
 
@@ -368,7 +367,7 @@ test_expect_success 'setup history with two files' '
 
 test_expect_success 'wordRegex for the first file does not apply to the second' '
        echo "*.tex diff=tex" >.gitattributes &&
-       git config diff.tex.wordRegex "[a-z]+|." &&
+       test_config diff.tex.wordRegex "[a-z]+|." &&
        cat >expect <<-\EOF &&
                diff --git a/a.tex b/a.tex
                --- a/a.tex
index 614425adac536d84e6835d9d707b86fe8f5f4d36..b7e16a7840afc32d36c6a118ed2fa1dbb579476a 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='combined diff'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
 
 setup_helper () {
        one=$1 branch=$2 side=$3 &&
@@ -113,4 +114,114 @@ test_expect_success 'check --cc --raw with forty trees' '
        grep "^::::::::::::::::::::::::::::::::::::::::[^:]" out
 '
 
+test_expect_success 'setup combined ignore spaces' '
+       git checkout master &&
+       >test &&
+       git add test &&
+       git commit -m initial &&
+
+       tr -d Q <<-\EOF >test &&
+       always coalesce
+       eol space coalesce Q
+       space  change coalesce
+       all spa ces coalesce
+       eol spaces Q
+       space  change
+       all spa ces
+       EOF
+       git commit -m "test space change" -a &&
+
+       git checkout -b side HEAD^ &&
+       tr -d Q <<-\EOF >test &&
+       always coalesce
+       eol space coalesce
+       space change coalesce
+       all spaces coalesce
+       eol spaces
+       space change
+       all spaces
+       EOF
+       git commit -m "test other space changes" -a &&
+
+       test_must_fail git merge master &&
+       tr -d Q <<-\EOF >test &&
+       eol spaces Q
+       space  change
+       all spa ces
+       EOF
+       git commit -m merged -a
+'
+
+test_expect_success 'check combined output (no ignore space)' '
+       git show >actual.tmp &&
+       sed -e "1,/^@@@/d" < actual.tmp >actual &&
+       tr -d Q <<-\EOF >expected &&
+       --always coalesce
+       - eol space coalesce
+       - space change coalesce
+       - all spaces coalesce
+       - eol spaces
+       - space change
+       - all spaces
+        -eol space coalesce Q
+        -space  change coalesce
+        -all spa ces coalesce
+       + eol spaces Q
+       + space  change
+       + all spa ces
+       EOF
+       compare_diff_patch expected actual
+'
+
+test_expect_success 'check combined output (ignore space at eol)' '
+       git show --ignore-space-at-eol >actual.tmp &&
+       sed -e "1,/^@@@/d" < actual.tmp >actual &&
+       tr -d Q <<-\EOF >expected &&
+       --always coalesce
+       --eol space coalesce
+       - space change coalesce
+       - all spaces coalesce
+        -space  change coalesce
+        -all spa ces coalesce
+         eol spaces Q
+       - space change
+       - all spaces
+       + space  change
+       + all spa ces
+       EOF
+       compare_diff_patch expected actual
+'
+
+test_expect_success 'check combined output (ignore space change)' '
+       git show -b >actual.tmp &&
+       sed -e "1,/^@@@/d" < actual.tmp >actual &&
+       tr -d Q <<-\EOF >expected &&
+       --always coalesce
+       --eol space coalesce
+       --space change coalesce
+       - all spaces coalesce
+        -all spa ces coalesce
+         eol spaces Q
+         space  change
+       - all spaces
+       + all spa ces
+       EOF
+       compare_diff_patch expected actual
+'
+
+test_expect_success 'check combined output (ignore all spaces)' '
+       git show -w >actual.tmp &&
+       sed -e "1,/^@@@/d" < actual.tmp >actual &&
+       tr -d Q <<-\EOF >expected &&
+       --always coalesce
+       --eol space coalesce
+       --space change coalesce
+       --all spaces coalesce
+         eol spaces Q
+         space  change
+         all spa ces
+       EOF
+       compare_diff_patch expected actual
+'
+
 test_done
index fa686b887d6b49ab8e6d30893501744645f72a91..9243a979933997286bb6004bd7b224bad1bbc5cc 100755 (executable)
@@ -419,8 +419,6 @@ test_expect_success 'log --graph with merge' '
 '
 
 test_expect_success 'log.decorate configuration' '
-       test_might_fail git config --unset-all log.decorate &&
-
        git log --oneline >expect.none &&
        git log --oneline --decorate >expect.short &&
        git log --oneline --decorate=full >expect.full &&
@@ -429,8 +427,7 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline >actual &&
        test_cmp expect.short actual &&
 
-       git config --unset-all log.decorate &&
-       git config log.decorate true &&
+       test_config log.decorate true &&
        git log --oneline >actual &&
        test_cmp expect.short actual &&
        git log --oneline --decorate=full >actual &&
@@ -438,8 +435,7 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate=no >actual &&
        test_cmp expect.none actual &&
 
-       git config --unset-all log.decorate &&
-       git config log.decorate no &&
+       test_config log.decorate no &&
        git log --oneline >actual &&
        test_cmp expect.none actual &&
        git log --oneline --decorate >actual &&
@@ -447,8 +443,7 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate=full >actual &&
        test_cmp expect.full actual &&
 
-       git config --unset-all log.decorate &&
-       git config log.decorate 1 &&
+       test_config log.decorate 1 &&
        git log --oneline >actual &&
        test_cmp expect.short actual &&
        git log --oneline --decorate=full >actual &&
@@ -456,8 +451,7 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate=no >actual &&
        test_cmp expect.none actual &&
 
-       git config --unset-all log.decorate &&
-       git config log.decorate short &&
+       test_config log.decorate short &&
        git log --oneline >actual &&
        test_cmp expect.short actual &&
        git log --oneline --no-decorate >actual &&
@@ -465,8 +459,7 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate=full >actual &&
        test_cmp expect.full actual &&
 
-       git config --unset-all log.decorate &&
-       git config log.decorate full &&
+       test_config log.decorate full &&
        git log --oneline >actual &&
        test_cmp expect.full actual &&
        git log --oneline --no-decorate >actual &&
@@ -474,16 +467,15 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate >actual &&
        test_cmp expect.short actual
 
-       git config --unset-all log.decorate &&
+       test_unconfig log.decorate &&
        git log --pretty=raw >expect.raw &&
-       git config log.decorate full &&
+       test_config log.decorate full &&
        git log --pretty=raw >actual &&
        test_cmp expect.raw actual
 
 '
 
 test_expect_success 'reflog is expected format' '
-       test_might_fail git config --remove-section log &&
        git log -g --abbrev-commit --pretty=oneline >expect &&
        git reflog >actual &&
        test_cmp expect actual
@@ -496,10 +488,6 @@ test_expect_success 'whatchanged is expected format' '
 '
 
 test_expect_success 'log.abbrevCommit configuration' '
-       test_when_finished "git config --unset log.abbrevCommit" &&
-
-       test_might_fail git config --unset log.abbrevCommit &&
-
        git log --abbrev-commit >expect.log.abbrev &&
        git log --no-abbrev-commit >expect.log.full &&
        git log --pretty=raw >expect.log.raw &&
@@ -508,7 +496,7 @@ test_expect_success 'log.abbrevCommit configuration' '
        git whatchanged --abbrev-commit >expect.whatchanged.abbrev &&
        git whatchanged --no-abbrev-commit >expect.whatchanged.full &&
 
-       git config log.abbrevCommit true &&
+       test_config log.abbrevCommit true &&
 
        git log >actual &&
        test_cmp expect.log.abbrev actual &&
index 6a33606d28eba61281252cf28f6d5820dc452f1d..4e7b05dd2391125b1c9e58404c552647107099cd 100755 (executable)
@@ -3,15 +3,9 @@
 test_description='git archive --format=zip test'
 
 . ./test-lib.sh
-GIT_UNZIP=${GIT_UNZIP:-unzip}
 
 SUBSTFORMAT=%H%n
 
-test_lazy_prereq UNZIP '
-       "$GIT_UNZIP" -v
-       test $? -ne 127
-'
-
 test_lazy_prereq UNZIP_SYMLINKS '
        (
                mkdir unzip-symlinks &&
diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh
new file mode 100755 (executable)
index 0000000..cdb7d7a
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='test corner cases of git-archive'
+. ./test-lib.sh
+
+test_expect_success 'create commit with empty tree' '
+       git commit --allow-empty -m foo
+'
+
+# Make a dir and clean it up afterwards
+make_dir() {
+       mkdir "$1" &&
+       test_when_finished "rm -rf '$1'"
+}
+
+# Check that the dir given in "$1" contains exactly the
+# set of paths given as arguments.
+check_dir() {
+       dir=$1; shift
+       {
+               echo "$dir" &&
+               for i in "$@"; do
+                       echo "$dir/$i"
+               done
+       } | sort >expect &&
+       find "$dir" -print | sort >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'tar archive of empty tree is empty' '
+       git archive --format=tar HEAD >empty.tar &&
+       make_dir extract &&
+       "$TAR" xf empty.tar -C extract &&
+       check_dir extract
+'
+
+test_expect_success 'tar archive of empty tree with prefix' '
+       git archive --format=tar --prefix=foo/ HEAD >prefix.tar &&
+       make_dir extract &&
+       "$TAR" xf prefix.tar -C extract &&
+       check_dir extract foo
+'
+
+test_expect_success UNZIP 'zip archive of empty tree is empty' '
+       # Detect the exit code produced when our particular flavor of unzip
+       # sees an empty archive. Infozip will generate a warning and exit with
+       # code 1. But in the name of sanity, we do not expect other unzip
+       # implementations to do the same thing (it would be perfectly
+       # reasonable to exit 0, for example).
+       #
+       # This makes our test less rigorous on some platforms (unzip may not
+       # handle the empty repo at all, making our later check of its exit code
+       # a no-op). But we cannot do anything reasonable except skip the test
+       # on such platforms anyway, and this is the moral equivalent.
+       "$GIT_UNZIP" "$TEST_DIRECTORY"/t5004/empty.zip
+       expect_code=$?
+
+       git archive --format=zip HEAD >empty.zip &&
+       make_dir extract &&
+       (
+               cd extract &&
+               test_expect_code $expect_code "$GIT_UNZIP" ../empty.zip
+       ) &&
+       check_dir extract
+'
+
+test_expect_success UNZIP 'zip archive of empty tree with prefix' '
+       # We do not have to play exit-code tricks here, because our
+       # result should not be empty; it has a directory in it.
+       git archive --format=zip --prefix=foo/ HEAD >prefix.zip &&
+       make_dir extract &&
+       (
+               cd extract &&
+               "$GIT_UNZIP" ../prefix.zip
+       ) &&
+       check_dir extract foo
+'
+
+test_expect_success 'archive complains about pathspec on empty tree' '
+       test_must_fail git archive --format=tar HEAD -- foo >/dev/null
+'
+
+test_expect_success 'create a commit with an empty subtree' '
+       empty_tree=$(git hash-object -t tree /dev/null) &&
+       root_tree=$(printf "040000 tree $empty_tree\tsub\n" | git mktree)
+'
+
+test_expect_success 'archive empty subtree with no pathspec' '
+       git archive --format=tar $root_tree >subtree-all.tar &&
+       make_dir extract &&
+       "$TAR" xf subtree-all.tar -C extract &&
+       check_dir extract sub
+'
+
+test_expect_success 'archive empty subtree by direct pathspec' '
+       git archive --format=tar $root_tree -- sub >subtree-path.tar &&
+       make_dir extract &&
+       "$TAR" xf subtree-path.tar -C extract &&
+       check_dir extract sub
+'
+
+test_done
diff --git a/t/t5004/empty.zip b/t/t5004/empty.zip
new file mode 100644 (file)
index 0000000..1a76bb6
Binary files /dev/null and b/t/t5004/empty.zip differ
index d645328609c9ec63782a0b9f80c31a73ef745802..e4bb3a14570780b41ce4ebea9e47870d4cbcb127 100755 (executable)
@@ -195,4 +195,30 @@ test_expect_success 'gc: prune old objects after local clone' '
        )
 '
 
+test_expect_success 'garbage report in count-objects -v' '
+       : >.git/objects/pack/foo &&
+       : >.git/objects/pack/foo.bar &&
+       : >.git/objects/pack/foo.keep &&
+       : >.git/objects/pack/foo.pack &&
+       : >.git/objects/pack/fake.bar &&
+       : >.git/objects/pack/fake.keep &&
+       : >.git/objects/pack/fake.pack &&
+       : >.git/objects/pack/fake.idx &&
+       : >.git/objects/pack/fake2.keep &&
+       : >.git/objects/pack/fake3.idx &&
+       git count-objects -v 2>stderr &&
+       grep "index file .git/objects/pack/fake.idx is too small" stderr &&
+       grep "^warning:" stderr | sort >actual &&
+       cat >expected <<\EOF &&
+warning: garbage found: .git/objects/pack/fake.bar
+warning: garbage found: .git/objects/pack/foo
+warning: garbage found: .git/objects/pack/foo.bar
+warning: no corresponding .idx nor .pack: .git/objects/pack/fake2.keep
+warning: no corresponding .idx: .git/objects/pack/foo.keep
+warning: no corresponding .idx: .git/objects/pack/foo.pack
+warning: no corresponding .pack: .git/objects/pack/fake3.idx
+EOF
+       test_cmp expected actual
+'
+
 test_done
index 354d32c584741daffa03891c5bee7fdc469a20da..d574085696e81522669119c9b7480ba4c682c968 100755 (executable)
@@ -364,6 +364,15 @@ EOF
        test_cmp count7.expected count7.actual
 '
 
+test_expect_success 'clone shallow with packed refs' '
+       git pack-refs --all &&
+       git clone --depth 1 --branch A "file://$(pwd)/." shallow8 &&
+       echo "in-pack: 4" > count8.expected &&
+       GIT_DIR=shallow8/.git git count-objects -v |
+               grep "^in-pack" > count8.actual &&
+       test_cmp count8.expected count8.actual
+'
+
 test_expect_success 'setup tests for the --stdin parameter' '
        for head in C D E F
        do
index 60de2d6ede958e713aebe85d73ee65ddbc10201d..f30c03885cda050f0bc704428ffa8a66932ff3fd 100755 (executable)
@@ -4,10 +4,6 @@ test_description='test automatic tag following'
 
 . ./test-lib.sh
 
-if ! test_have_prereq NOT_MINGW; then
-       say "GIT_DEBUG_SEND_PACK not supported - skipping tests"
-fi
-
 # End state of the repository:
 #
 #         T - tag1          S - tag2
@@ -17,7 +13,7 @@ fi
 #     \   C - origin/cat    \
 #      origin/master         master
 
-test_expect_success NOT_MINGW setup '
+test_expect_success setup '
        test_tick &&
        echo ichi >file &&
        git add file &&
@@ -39,28 +35,35 @@ test_expect_success NOT_MINGW setup '
 '
 
 U=UPLOAD_LOG
+UPATH="$(pwd)/$U"
 
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
 cat - <<EOF >expect
-#S
 want $A
-#E
 EOF
 '
 
-test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' '
+get_needs () {
+       test -s "$1" &&
+       perl -alne '
+               next unless $F[1] eq "upload-pack<";
+               last if $F[2] eq "0000";
+               print $F[2], " ", $F[3];
+       ' "$1"
+}
+
+test_expect_success 'fetch A (new commit : 1 connection)' '
        rm -f $U &&
        (
                cd cloned &&
-               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               GIT_TRACE_PACKET=$UPATH git fetch &&
                test $A = $(git rev-parse --verify origin/master)
        ) &&
-       test -s $U &&
-       cut -d" " -f1,2 $U >actual &&
+       get_needs $U >actual &&
        test_cmp expect actual
 '
 
-test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" '
+test_expect_success "create tag T on A, create C on branch cat" '
        git tag -a -m tag1 tag1 $A &&
        T=$(git rev-parse --verify tag1) &&
 
@@ -72,30 +75,27 @@ test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" '
        git checkout master
 '
 
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
 cat - <<EOF >expect
-#S
 want $C
 want $T
-#E
 EOF
 '
 
-test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' '
+test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
        rm -f $U &&
        (
                cd cloned &&
-               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               GIT_TRACE_PACKET=$UPATH git fetch &&
                test $C = $(git rev-parse --verify origin/cat) &&
                test $T = $(git rev-parse --verify tag1) &&
                test $A = $(git rev-parse --verify tag1^0)
        ) &&
-       test -s $U &&
-       cut -d" " -f1,2 $U >actual &&
+       get_needs $U >actual &&
        test_cmp expect actual
 '
 
-test_expect_success NOT_MINGW "create commits O, B, tag S on B" '
+test_expect_success "create commits O, B, tag S on B" '
        test_tick &&
        echo O >file &&
        git add file &&
@@ -111,39 +111,34 @@ test_expect_success NOT_MINGW "create commits O, B, tag S on B" '
        S=$(git rev-parse --verify tag2)
 '
 
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
 cat - <<EOF >expect
-#S
 want $B
 want $S
-#E
 EOF
 '
 
-test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' '
+test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
        rm -f $U &&
        (
                cd cloned &&
-               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               GIT_TRACE_PACKET=$UPATH git fetch &&
                test $B = $(git rev-parse --verify origin/master) &&
                test $B = $(git rev-parse --verify tag2^0) &&
                test $S = $(git rev-parse --verify tag2)
        ) &&
-       test -s $U &&
-       cut -d" " -f1,2 $U >actual &&
+       get_needs $U >actual &&
        test_cmp expect actual
 '
 
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
 cat - <<EOF >expect
-#S
 want $B
 want $S
-#E
 EOF
 '
 
-test_expect_success NOT_MINGW 'new clone fetch master and tags' '
+test_expect_success 'new clone fetch master and tags' '
        git branch -D cat
        rm -f $U
        (
@@ -151,15 +146,14 @@ test_expect_success NOT_MINGW 'new clone fetch master and tags' '
                cd clone2 &&
                git init &&
                git remote add origin .. &&
-               GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+               GIT_TRACE_PACKET=$UPATH git fetch &&
                test $B = $(git rev-parse --verify origin/master) &&
                test $S = $(git rev-parse --verify tag2) &&
                test $B = $(git rev-parse --verify tag2^0) &&
                test $T = $(git rev-parse --verify tag1) &&
                test $A = $(git rev-parse --verify tag1^0)
        ) &&
-       test -s $U &&
-       cut -d" " -f1,2 $U >actual &&
+       get_needs $U >actual &&
        test_cmp expect actual
 '
 
index c31e5c1c525eea5a9f4677b03c5d7314d70c6136..383a2eb1eaa46053fc3a91fb44921aea2f04637a 100755 (executable)
@@ -22,19 +22,16 @@ mk_test () {
        (
                for ref in "$@"
                do
-                       git push testrepo $the_first_commit:refs/$ref || {
-                               echo "Oops, push refs/$ref failure"
-                               exit 1
-                       }
+                       git push testrepo $the_first_commit:refs/$ref ||
+                       exit
                done &&
                cd testrepo &&
                for ref in "$@"
                do
-                       r=$(git show-ref -s --verify refs/$ref) &&
-                       test "z$r" = "z$the_first_commit" || {
-                               echo "Oops, refs/$ref is wrong"
-                               exit 1
-                       }
+                       echo "$the_first_commit" >expect &&
+                       git show-ref -s --verify refs/$ref >actual &&
+                       test_cmp expect actual ||
+                       exit
                done &&
                git fsck --full
        )
@@ -82,15 +79,13 @@ mk_child() {
 check_push_result () {
        (
                cd testrepo &&
-               it="$1" &&
-               shift
+               echo "$1" >expect &&
+               shift &&
                for ref in "$@"
                do
-                       r=$(git show-ref -s --verify refs/$ref) &&
-                       test "z$r" = "z$it" || {
-                               echo "Oops, refs/$ref is wrong"
-                               exit 1
-                       }
+                       git show-ref -s --verify refs/$ref >actual &&
+                       test_cmp expect actual ||
+                       exit
                done &&
                git fsck --full
        )
@@ -118,10 +113,9 @@ test_expect_success 'fetch without wildcard' '
                cd testrepo &&
                git fetch .. refs/heads/master:refs/remotes/origin/master &&
 
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -133,10 +127,9 @@ test_expect_success 'fetch with wildcard' '
                git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
                git fetch up &&
 
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -150,10 +143,9 @@ test_expect_success 'fetch with insteadOf' '
                git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
                git fetch up &&
 
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -167,10 +159,9 @@ test_expect_success 'fetch with pushInsteadOf (should not rewrite)' '
                git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
                git fetch up &&
 
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -180,10 +171,9 @@ test_expect_success 'push without wildcard' '
        git push testrepo refs/heads/master:refs/remotes/origin/master &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -193,54 +183,50 @@ test_expect_success 'push with wildcard' '
        git push testrepo "refs/heads/*:refs/remotes/origin/*" &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
 test_expect_success 'push with insteadOf' '
        mk_empty &&
        TRASH="$(pwd)/" &&
-       git config "url.$TRASH.insteadOf" trash/ &&
+       test_config "url.$TRASH.insteadOf" trash/ &&
        git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
 test_expect_success 'push with pushInsteadOf' '
        mk_empty &&
        TRASH="$(pwd)/" &&
-       git config "url.$TRASH.pushInsteadOf" trash/ &&
+       test_config "url.$TRASH.pushInsteadOf" trash/ &&
        git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
 test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' '
        mk_empty &&
        TRASH="$(pwd)/" &&
-       git config "url.trash2/.pushInsteadOf" trash/ &&
-       git config remote.r.url trash/wrong &&
-       git config remote.r.pushurl "$TRASH/testrepo" &&
+       test_config "url.trash2/.pushInsteadOf" trash/ &&
+       test_config remote.r.url trash/wrong &&
+       test_config remote.r.pushurl "$TRASH/testrepo" &&
        git push r refs/heads/master:refs/remotes/origin/master &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -340,13 +326,8 @@ test_expect_success 'push with weak ambiguity (2)' '
 test_expect_success 'push with ambiguity' '
 
        mk_test heads/frotz tags/frotz &&
-       if git push testrepo master:frotz
-       then
-               echo "Oops, should have failed"
-               false
-       else
-               check_push_result $the_first_commit heads/frotz tags/frotz
-       fi
+       test_must_fail git push testrepo master:frotz &&
+       check_push_result $the_first_commit heads/frotz tags/frotz
 
 '
 
@@ -489,31 +470,24 @@ test_expect_success 'push with config remote.*.push = HEAD' '
                git checkout local &&
                git reset --hard $the_first_commit
        ) &&
-       git config remote.there.url testrepo &&
-       git config remote.there.push HEAD &&
-       git config branch.master.remote there &&
+       test_config remote.there.url testrepo &&
+       test_config remote.there.push HEAD &&
+       test_config branch.master.remote there &&
        git push &&
        check_push_result $the_commit heads/master &&
        check_push_result $the_first_commit heads/local
 '
 
-# clean up the cruft left with the previous one
-git config --remove-section remote.there
-git config --remove-section branch.master
-
 test_expect_success 'push with config remote.*.pushurl' '
 
        mk_test heads/master &&
        git checkout master &&
-       git config remote.there.url test2repo &&
-       git config remote.there.pushurl testrepo &&
+       test_config remote.there.url test2repo &&
+       test_config remote.there.pushurl testrepo &&
        git push there &&
        check_push_result $the_commit heads/master
 '
 
-# clean up the cruft left with the previous one
-git config --remove-section remote.there
-
 test_expect_success 'push with dry-run' '
 
        mk_test heads/master &&
@@ -834,9 +808,9 @@ test_expect_success 'fetch with branches' '
        (
                cd testrepo &&
                git fetch branch1 &&
-               r=$(git show-ref -s --verify refs/heads/branch1) &&
-               test "z$r" = "z$the_commit" &&
-               test 1 = $(git for-each-ref refs/heads | wc -l)
+               echo "$the_commit commit        refs/heads/branch1" >expect &&
+               git for-each-ref refs/heads >actual &&
+               test_cmp expect actual
        ) &&
        git checkout master
 '
@@ -847,9 +821,9 @@ test_expect_success 'fetch with branches containing #' '
        (
                cd testrepo &&
                git fetch branch2 &&
-               r=$(git show-ref -s --verify refs/heads/branch2) &&
-               test "z$r" = "z$the_first_commit" &&
-               test 1 = $(git for-each-ref refs/heads | wc -l)
+               echo "$the_first_commit commit  refs/heads/branch2" >expect &&
+               git for-each-ref refs/heads >actual &&
+               test_cmp expect actual
        ) &&
        git checkout master
 '
@@ -861,9 +835,9 @@ test_expect_success 'push with branches' '
        git push branch1 &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/heads/master) &&
-               test "z$r" = "z$the_first_commit" &&
-               test 1 = $(git for-each-ref refs/heads | wc -l)
+               echo "$the_first_commit commit  refs/heads/master" >expect &&
+               git for-each-ref refs/heads >actual &&
+               test_cmp expect actual
        )
 '
 
@@ -873,9 +847,9 @@ test_expect_success 'push with branches containing #' '
        git push branch2 &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/heads/branch3) &&
-               test "z$r" = "z$the_first_commit" &&
-               test 1 = $(git for-each-ref refs/heads | wc -l)
+               echo "$the_first_commit commit  refs/heads/branch3" >expect &&
+               git for-each-ref refs/heads >actual &&
+               test_cmp expect actual
        ) &&
        git checkout master
 '
@@ -958,9 +932,9 @@ test_expect_success 'push --porcelain' '
        git push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&
        (
                cd testrepo &&
-               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
-               test "z$r" = "z$the_commit" &&
-               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+               echo "$the_commit commit        refs/remotes/origin/master" >expect &&
+               git for-each-ref refs/remotes/origin >actual &&
+               test_cmp expect actual
        ) &&
        test_cmp .git/foo .git/bar
 '
@@ -1043,4 +1017,111 @@ do
        '
 done
 
+test_expect_success 'fetch exact SHA1' '
+       mk_test heads/master hidden/one &&
+       git push testrepo master:refs/hidden/one &&
+       (
+               cd testrepo &&
+               git config transfer.hiderefs refs/hidden
+       ) &&
+       check_push_result $the_commit hidden/one &&
+
+       mk_child child &&
+       (
+               cd child &&
+
+               # make sure $the_commit does not exist here
+               git repack -a -d &&
+               git prune &&
+               test_must_fail git cat-file -t $the_commit &&
+
+               # fetching the hidden object should fail by default
+               test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy &&
+               test_must_fail git rev-parse --verify refs/heads/copy &&
+
+               # the server side can allow it to succeed
+               (
+                       cd ../testrepo &&
+                       git config uploadpack.allowtipsha1inwant true
+               ) &&
+
+               git fetch -v ../testrepo $the_commit:refs/heads/copy &&
+               result=$(git rev-parse --verify refs/heads/copy) &&
+               test "$the_commit" = "$result"
+       )
+'
+
+test_expect_success 'fetch follows tags by default' '
+       mk_test heads/master &&
+       rm -fr src dst &&
+       git init src &&
+       (
+               cd src &&
+               git pull ../testrepo master &&
+               git tag -m "annotated" tag &&
+               git for-each-ref >tmp1 &&
+               (
+                       cat tmp1
+                       sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1
+               ) |
+               sort -k 3 >../expect
+       ) &&
+       git init dst &&
+       (
+               cd dst &&
+               git remote add origin ../src &&
+               git config branch.master.remote origin &&
+               git config branch.master.merge refs/heads/master &&
+               git pull &&
+               git for-each-ref >../actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push does not follow tags by default' '
+       mk_test heads/master &&
+       rm -fr src dst &&
+       git init src &&
+       git init --bare dst &&
+       (
+               cd src &&
+               git pull ../testrepo master &&
+               git tag -m "annotated" tag &&
+               git checkout -b another &&
+               git commit --allow-empty -m "future commit" &&
+               git tag -m "future" future &&
+               git checkout master &&
+               git for-each-ref refs/heads/master >../expect &&
+               git push ../dst master
+       ) &&
+       (
+               cd dst &&
+               git for-each-ref >../actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --follow-tag only pushes relevant tags' '
+       mk_test heads/master &&
+       rm -fr src dst &&
+       git init src &&
+       git init --bare dst &&
+       (
+               cd src &&
+               git pull ../testrepo master &&
+               git tag -m "annotated" tag &&
+               git checkout -b another &&
+               git commit --allow-empty -m "future commit" &&
+               git tag -m "future" future &&
+               git checkout master &&
+               git for-each-ref refs/heads/master refs/tags/tag >../expect
+               git push --follow-tag ../dst master
+       ) &&
+       (
+               cd dst &&
+               git for-each-ref >../actual
+       ) &&
+       test_cmp expect actual
+'
+
 test_done
index 35304b41e9ce6222f7d713e3d310e47241d8e6e0..6af6c63350383e049082e61d3c8133af79d5a749 100755 (executable)
@@ -96,8 +96,7 @@ test_expect_success '--rebase' '
 '
 test_expect_success 'pull.rebase' '
        git reset --hard before-rebase &&
-       git config --bool pull.rebase true &&
-       test_when_finished "git config --unset pull.rebase" &&
+       test_config pull.rebase true &&
        git pull . copy &&
        test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
        test new = $(git show HEAD:file2)
@@ -105,8 +104,7 @@ test_expect_success 'pull.rebase' '
 
 test_expect_success 'branch.to-rebase.rebase' '
        git reset --hard before-rebase &&
-       git config --bool branch.to-rebase.rebase true &&
-       test_when_finished "git config --unset branch.to-rebase.rebase" &&
+       test_config branch.to-rebase.rebase true &&
        git pull . copy &&
        test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
        test new = $(git show HEAD:file2)
@@ -114,10 +112,8 @@ test_expect_success 'branch.to-rebase.rebase' '
 
 test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '
        git reset --hard before-rebase &&
-       git config --bool pull.rebase true &&
-       test_when_finished "git config --unset pull.rebase" &&
-       git config --bool branch.to-rebase.rebase false &&
-       test_when_finished "git config --unset branch.to-rebase.rebase" &&
+       test_config pull.rebase true &&
+       test_config branch.to-rebase.rebase false &&
        git pull . copy &&
        test $(git rev-parse HEAD^) != $(git rev-parse copy) &&
        test new = $(git show HEAD:file2)
@@ -171,9 +167,9 @@ test_expect_success 'pull --rebase dies early with dirty working directory' '
        git update-ref refs/remotes/me/copy copy^ &&
        COPY=$(git rev-parse --verify me/copy) &&
        git rebase --onto $COPY copy &&
-       git config branch.to-rebase.remote me &&
-       git config branch.to-rebase.merge refs/heads/copy &&
-       git config branch.to-rebase.rebase true &&
+       test_config branch.to-rebase.remote me &&
+       test_config branch.to-rebase.merge refs/heads/copy &&
+       test_config branch.to-rebase.rebase true &&
        echo dirty >> file &&
        git add file &&
        test_must_fail git pull &&
index 1b06691bb41586b3bc564841b720ce710f927a1c..aa31abe32b3abc1b43d8111f6503313aa8237455 100755 (executable)
@@ -19,6 +19,17 @@ test_expect_success 'git pull -q' '
        test ! -s out)
 '
 
+test_expect_success 'git pull -q --rebase' '
+       mkdir clonedqrb &&
+       (cd clonedqrb && git init &&
+       git pull -q --rebase "../parent" >out 2>err &&
+       test ! -s err &&
+       test ! -s out &&
+       git pull -q --rebase "../parent" >out 2>err &&
+       test ! -s err &&
+       test ! -s out)
+'
+
 test_expect_success 'git pull' '
        mkdir cloned &&
        (cd cloned && git init &&
@@ -27,6 +38,14 @@ test_expect_success 'git pull' '
        test ! -s out)
 '
 
+test_expect_success 'git pull --rebase' '
+       mkdir clonedrb &&
+       (cd clonedrb && git init &&
+       git pull --rebase "../parent" >out 2>err &&
+       test -s err &&
+       test ! -s out)
+'
+
 test_expect_success 'git pull -v' '
        mkdir clonedv &&
        (cd clonedv && git init &&
@@ -35,6 +54,14 @@ test_expect_success 'git pull -v' '
        test ! -s out)
 '
 
+test_expect_success 'git pull -v --rebase' '
+       mkdir clonedvrb &&
+       (cd clonedvrb && git init &&
+       git pull -v --rebase "../parent" >out 2>err &&
+       test -s err &&
+       test ! -s out)
+'
+
 test_expect_success 'git pull -v -q' '
        mkdir clonedvq &&
        (cd clonedvq && git init &&
index 4b4b4a604f3075dec1056b31da5cccc0c4ed5a9a..4086f02bc129b3628c724c7037fe60509fbef46c 100755 (executable)
@@ -181,8 +181,7 @@ test_expect_success 'push (chunked)' '
        git checkout master &&
        test_commit commit path3 &&
        HEAD=$(git rev-parse --verify HEAD) &&
-       git config http.postbuffer 4 &&
-       test_when_finished "git config --unset http.postbuffer" &&
+       test_config http.postbuffer 4 &&
        git push -v -v origin $BRANCH 2>err &&
        grep "POST git-receive-pack (chunked)" err &&
        (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
index c47d450cc3731cb471aa8485178f517bb0d6cbf5..60f1552adef417b8d37ca4215be1e66627c37258 100755 (executable)
@@ -54,11 +54,14 @@ cd "$base_dir"
 
 rm -f "$U.D"
 
-test_expect_success 'cloning with reference (no -l -s)' \
-'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"'
+test_expect_success 'cloning with reference (no -l -s)' '
+       GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
+'
 
-test_expect_success 'fetched no objects' \
-'! grep "^want" "$U.D"'
+test_expect_success 'fetched no objects' '
+       test -s "$U.D" &&
+       ! grep " want" "$U.D"
+'
 
 cd "$base_dir"
 
@@ -173,12 +176,13 @@ test_expect_success 'fetch with incomplete alternates' '
        (
                cd K &&
                git remote add J "file://$base_dir/J" &&
-               GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K"
+               GIT_TRACE_PACKET=$U.K git fetch J
        ) &&
        master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
-       ! grep "^want $master_object" "$U.K" &&
+       test -s "$U.K" &&
+       ! grep " want $master_object" "$U.K" &&
        tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
-       ! grep "^want $tag_object" "$U.K"
+       ! grep " want $tag_object" "$U.K"
 '
 
 test_done
index 30507407ff6375f96c632c7c3f62ae3d338ed5ea..66cda17ef342a2791ce7bac873eecfd4cf319faf 100755 (executable)
@@ -133,4 +133,17 @@ test_expect_success 'dodecapus' '
        check_revlist "--min-parents=13" &&
        check_revlist "--min-parents=4 --max-parents=11" tetrapus
 '
+
+test_expect_success 'ancestors with the same commit time' '
+
+       test_tick_keep=$test_tick &&
+       for i in 1 2 3 4 5 6 7 8; do
+               test_tick=$test_tick_keep
+               test_commit t$i
+       done &&
+       git rev-list t1^! --not t$i >result &&
+       >expect &&
+       test_cmp expect result
+'
+
 test_done
index 839ad97b791c6aa757d0b82eea7fc16369ad4586..8e2ff13423ba01095404ec49232760bc66c68978 100755 (executable)
@@ -56,7 +56,23 @@ test_expect_success setup '
 
        echo "Final change" >file &&
        test_tick && git commit -a -m "Final change" &&
-       note I
+       note I &&
+
+       git symbolic-ref HEAD refs/heads/unrelated &&
+       git rm -f "*" &&
+       echo "Unrelated branch" >side &&
+       git add side &&
+       test_tick && git commit -m "Side root" &&
+       note J &&
+
+       git checkout master &&
+       test_tick && git merge -m "Coolest" unrelated &&
+       note K &&
+
+       echo "Immaterial" >elif &&
+       git add elif &&
+       test_tick && git commit -m "Last" &&
+       note L
 '
 
 FMT='tformat:%P        %H | %s'
@@ -79,10 +95,10 @@ check_result () {
        '
 }
 
-check_result 'I H G F E D C B A' --full-history
-check_result 'I H E C B A' --full-history -- file
-check_result 'I H E C B A' --full-history --topo-order -- file
-check_result 'I H E C B A' --full-history --date-order -- file
+check_result 'L K J I H G F E D C B A' --full-history
+check_result 'I H E C B A' --full-history -- file
+check_result 'I H E C B A' --full-history --topo-order -- file
+check_result 'I H E C B A' --full-history --date-order -- file
 check_result 'I E C B A' --simplify-merges -- file
 check_result 'I B A' -- file
 check_result 'I B A' --topo-order -- file
index 3e0e15fb3e0b92ab7e95789d27db34d9a1fa423c..2fce99a0754481a36b3644d01f8577310e19d763 100755 (executable)
@@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if
        cp .git/BISECT_START saved &&
        test_must_fail git bisect start $HASH4 foo -- &&
        git branch > branch.output &&
-       test_i18ngrep "* (no branch)" branch.output > /dev/null &&
+       test_i18ngrep "* (no branch, bisect started on other)" branch.output > /dev/null &&
        test_cmp saved .git/BISECT_START
 '
 test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
index f5a79b13aecf4020b8a453f5f27568ce4f88bbbc..c8d6e9f88cc36f7e6a1208721ea6c45991452b9e 100755 (executable)
@@ -104,6 +104,18 @@ test_expect_success 'creating a tag using HEAD directly should succeed' '
        tag_exists myhead
 '
 
+test_expect_success '--force can create a tag with the name of one existing' '
+       tag_exists mytag &&
+       git tag --force mytag &&
+       tag_exists mytag'
+
+test_expect_success '--force is moot with a non-existing tag name' '
+       git tag newtag >expect &&
+       git tag --force forcetag >actual &&
+       test_cmp expect actual
+'
+git tag -d newtag forcetag
+
 # deleting tags:
 
 test_expect_success 'trying to delete an unknown tag should fail' '
index f4f38a5e7387694e16ff6f4a54020843630c2ac7..52ef06b0005aabf2a201d20dbb5f232e3a26766a 100755 (executable)
@@ -5,6 +5,7 @@ test_description='basic work tree status reporting'
 . ./test-lib.sh
 
 test_expect_success setup '
+       git config --global advice.statusuoption false &&
        test_commit A &&
        test_commit B oneside added &&
        git checkout A^0 &&
diff --git a/t/t7062-wtstatus-ignorecase.sh b/t/t7062-wtstatus-ignorecase.sh
new file mode 100755 (executable)
index 0000000..73709db
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='git-status with core.ignorecase=true'
+
+. ./test-lib.sh
+
+test_expect_success 'status with hash collisions' '
+       # note: "V/", "V/XQANY/" and "WURZAUP/" produce the same hash code
+       # in name-hash.c::hash_name
+       mkdir V &&
+       mkdir V/XQANY &&
+       mkdir WURZAUP &&
+       touch V/XQANY/test &&
+       git config core.ignorecase true &&
+       git add . &&
+       # test is successful if git status completes (no endless loop)
+       git status
+'
+
+test_done
index 2683cba7e3f0ba7fbb7ea1752a39a6068fc91718..5030f1f1bcc2442482ca923d03454fbc69f1a7ca 100755 (executable)
@@ -757,4 +757,104 @@ test_expect_success 'submodule add with an existing name fails unless forced' '
        )
 '
 
+test_expect_success 'set up a second submodule' '
+       git submodule add ./init2 example2 &&
+       git commit -m "submodule example2 added"
+'
+
+test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '
+       git config submodule.example.foo bar &&
+       git config submodule.example2.frotz nitfol &&
+       git submodule deinit init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -n "$(git config --get-regexp "submodule\.example2\.")" &&
+       test -f example2/.git &&
+       rmdir init
+'
+
+test_expect_success 'submodule deinit . deinits all initialized submodules' '
+       git submodule update --init &&
+       git config submodule.example.foo bar &&
+       git config submodule.example2.frotz nitfol &&
+       test_must_fail git submodule deinit &&
+       git submodule deinit . &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -z "$(git config --get-regexp "submodule\.example2\.")" &&
+       rmdir init example2
+'
+
+test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '
+       git submodule update --init &&
+       rm -rf init example2/* example2/.git &&
+       git submodule deinit init example2 &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -z "$(git config --get-regexp "submodule\.example2\.")" &&
+       rmdir init
+'
+
+test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '
+       git submodule update --init &&
+       echo X >>init/s &&
+       test_must_fail git submodule deinit init &&
+       test -n "$(git config --get-regexp "submodule\.example\.")" &&
+       test -f example2/.git &&
+       git submodule deinit -f init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       rmdir init
+'
+
+test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '
+       git submodule update --init &&
+       echo X >>init/untracked &&
+       test_must_fail git submodule deinit init &&
+       test -n "$(git config --get-regexp "submodule\.example\.")" &&
+       test -f example2/.git &&
+       git submodule deinit -f init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       rmdir init
+'
+
+test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '
+       git submodule update --init &&
+       (
+               cd init &&
+               git checkout HEAD^
+       ) &&
+       test_must_fail git submodule deinit init &&
+       test -n "$(git config --get-regexp "submodule\.example\.")" &&
+       test -f example2/.git &&
+       git submodule deinit -f init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       rmdir init
+'
+
+test_expect_success 'submodule deinit is silent when used on an uninitialized submodule' '
+       git submodule update --init &&
+       git submodule deinit init >actual &&
+       test_i18ngrep "Submodule .example. (.*) unregistered for path .init" actual &&
+       git submodule deinit init >actual &&
+       test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+       git submodule deinit . >actual &&
+       test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+       test_i18ngrep "Submodule .example2. (.*) unregistered for path .example2" actual &&
+       git submodule deinit . >actual &&
+       test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+       test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual &&
+       rmdir init example2
+'
+
+test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '
+       git submodule update --init &&
+       (
+               cd init &&
+               rm .git &&
+               cp -R ../.git/modules/example .git &&
+               GIT_WORK_TREE=. git config --unset core.worktree
+       ) &&
+       test_must_fail git submodule deinit init &&
+       test_must_fail git submodule deinit -f init &&
+       test -d init/.git &&
+       test -n "$(git config --get-regexp "submodule\.example\.")"
+'
+
 test_done
index 1a3d20bdc36c63c5261090d76858d43bd7f64113..2a0cfaac329c6c0974f205c81c45a0304a04897d 100755 (executable)
@@ -665,8 +665,10 @@ test_expect_success 'submodule add properly re-creates deeper level submodules'
 
 test_expect_success 'submodule update properly revives a moved submodule' '
        (cd super &&
+        H=$(git rev-parse --short HEAD) &&
         git commit -am "pre move" &&
-        git status >expect&&
+        H2=$(git rev-parse --short HEAD) &&
+        git status | sed "s/$H/XXX/" >expect &&
         H=$(cd submodule2; git rev-parse HEAD) &&
         git rm --cached submodule2 &&
         rm -rf submodule2 &&
@@ -675,7 +677,7 @@ test_expect_success 'submodule update properly revives a moved submodule' '
         git config -f .gitmodules submodule.submodule2.path "moved/sub module"
         git commit -am "post move" &&
         git submodule update &&
-        git status >actual &&
+        git status | sed "s/$H2/XXX/" >actual &&
         test_cmp expect actual
        )
 '
index 1c908f4d3966cb9a2769465652981bef831f312d..436b7b606e3eab09a0dd8c1b67ab90e43c24dffc 100755 (executable)
@@ -36,8 +36,7 @@ test_expect_success 'nonexistent template file should return error' '
 '
 
 test_expect_success 'nonexistent template file in config should return error' '
-       git config commit.template "$PWD"/notexist &&
-       test_when_finished "git config --unset commit.template" &&
+       test_config commit.template "$PWD"/notexist &&
        (
                GIT_EDITOR="echo hello >\"\$1\"" &&
                export GIT_EDITOR &&
@@ -93,14 +92,13 @@ test_expect_success '-t option should be short for --template' '
 
 test_expect_success 'config-specified template should commit' '
        echo "new template" > "$TEMPLATE" &&
-       git config commit.template "$TEMPLATE" &&
+       test_config commit.template "$TEMPLATE" &&
        echo "more content" >> foo &&
        git add foo &&
        (
                test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
                git commit
        ) &&
-       git config --unset commit.template &&
        commit_msg_is "new templatecommit message"
 '
 
index cbd7a45927eb2d4b04649f21f82192091782ce90..a4938b1e4549d5082362ec3f5513ffe91f210d39 100755 (executable)
@@ -171,18 +171,25 @@ test_expect_success 'verbose' '
 
 test_expect_success 'verbose respects diff config' '
 
-       git config color.diff always &&
+       test_config color.diff always &&
        git status -v >actual &&
-       grep "\[1mdiff --git" actual &&
-       git config --unset color.diff
+       grep "\[1mdiff --git" actual
+'
+
+mesg_with_comment_and_newlines='
+# text
+
+'
+
+test_expect_success 'prepare file with comment line and trailing newlines'  '
+       printf "%s" "$mesg_with_comment_and_newlines" >expect
 '
 
 test_expect_success 'cleanup commit messages (verbatim option,-t)' '
 
        echo >>negative &&
-       { echo;echo "# text";echo; } >expect &&
-       git commit --cleanup=verbatim -t expect -a &&
-       git cat-file -p HEAD |sed -e "1,/^\$/d" |head -n 3 >actual &&
+       git commit --cleanup=verbatim --no-status -t expect -a &&
+       git cat-file -p HEAD |sed -e "1,/^\$/d" >actual &&
        test_cmp expect actual
 
 '
@@ -199,7 +206,7 @@ test_expect_success 'cleanup commit messages (verbatim option,-F)' '
 test_expect_success 'cleanup commit messages (verbatim option,-m)' '
 
        echo >>negative &&
-       git commit --cleanup=verbatim -m "$(cat expect)" -a &&
+       git commit --cleanup=verbatim -m "$mesg_with_comment_and_newlines" -a &&
        git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
        test_cmp expect actual
 
@@ -255,32 +262,40 @@ test_expect_success 'cleanup commit message (fail on invalid cleanup mode config
 test_expect_success 'cleanup commit message (no config and no option uses default)' '
        echo content >>file &&
        git add file &&
-       test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
-       git commit --no-status &&
+       (
+         test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+         git commit --no-status
+       ) &&
        commit_msg_is "commit message"
 '
 
 test_expect_success 'cleanup commit message (option overrides default)' '
        echo content >>file &&
        git add file &&
-       test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
-       git commit --cleanup=whitespace --no-status &&
+       (
+         test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+         git commit --cleanup=whitespace --no-status
+       ) &&
        commit_msg_is "commit message # comment"
 '
 
 test_expect_success 'cleanup commit message (config overrides default)' '
        echo content >>file &&
        git add file &&
-       test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
-       git -c commit.cleanup=whitespace commit --no-status &&
+       (
+         test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+         git -c commit.cleanup=whitespace commit --no-status
+       ) &&
        commit_msg_is "commit message # comment"
 '
 
 test_expect_success 'cleanup commit message (option overrides config)' '
        echo content >>file &&
        git add file &&
-       test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
-       git -c commit.cleanup=whitespace commit --cleanup=default &&
+       (
+         test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+         git -c commit.cleanup=whitespace commit --cleanup=default
+       ) &&
        commit_msg_is "commit message"
 '
 
@@ -409,6 +424,18 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo
 
 '
 
+test_expect_success 'commit -s places sob on third line after two empty lines' '
+       git commit -s --allow-empty --allow-empty-message &&
+       cat <<-EOF >expect &&
+
+
+       Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+
+       EOF
+       sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual &&
+       test_cmp expect actual
+'
+
 write_script .git/FAKE_EDITOR <<\EOF
 mv "$1" "$1.orig"
 (
@@ -419,16 +446,6 @@ EOF
 
 echo '## Custom template' >template
 
-clear_config () {
-       (
-               git config --unset-all "$1"
-               case $? in
-               0|5)    exit 0 ;;
-               *)      exit 1 ;;
-               esac
-       )
-}
-
 try_commit () {
        git reset --hard &&
        echo >>negative &&
@@ -444,67 +461,57 @@ try_commit () {
 try_commit_status_combo () {
 
        test_expect_success 'commit' '
-               clear_config commit.status &&
                try_commit "" &&
                test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit' '
-               clear_config commit.status &&
                try_commit "" &&
                test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit --status' '
-               clear_config commit.status &&
                try_commit --status &&
                test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit --no-status' '
-               clear_config commit.status &&
                try_commit --no-status &&
                test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit with commit.status = yes' '
-               clear_config commit.status &&
-               git config commit.status yes &&
+               test_config commit.status yes &&
                try_commit "" &&
                test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit with commit.status = no' '
-               clear_config commit.status &&
-               git config commit.status no &&
+               test_config commit.status no &&
                try_commit "" &&
                test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit --status with commit.status = yes' '
-               clear_config commit.status &&
-               git config commit.status yes &&
+               test_config commit.status yes &&
                try_commit --status &&
                test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit --no-status with commit.status = yes' '
-               clear_config commit.status &&
-               git config commit.status yes &&
+               test_config commit.status yes &&
                try_commit --no-status &&
                test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit --status with commit.status = no' '
-               clear_config commit.status &&
-               git config commit.status no &&
+               test_config commit.status no &&
                try_commit --status &&
                test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
 
        test_expect_success 'commit --no-status with commit.status = no' '
-               clear_config commit.status &&
-               git config commit.status no &&
+               test_config commit.status no &&
                try_commit --no-status &&
                test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
        '
@@ -518,8 +525,7 @@ use_template="-t template"
 try_commit_status_combo
 
 test_expect_success 'commit --status with custom comment character' '
-       test_when_finished "git config --unset core.commentchar" &&
-       git config core.commentchar ";" &&
+       test_config core.commentchar ";" &&
        try_commit --status &&
        test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
 '
index a79c032ffd941a68e737b844ea5b28fffe58686c..e2ffdacc267c22e369247791e24338a20badf94d 100755 (executable)
@@ -8,6 +8,7 @@ test_description='git status'
 . ./test-lib.sh
 
 test_expect_success 'status -h in broken repository' '
+       git config --global advice.statusuoption false &&
        mkdir broken &&
        test_when_finished "rm -fr broken" &&
        (
@@ -130,8 +131,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'status (advice.statusHints false)' '
-       test_when_finished "git config --unset advice.statusHints" &&
-       git config advice.statusHints false &&
+       test_config advice.statusHints false &&
        git status >output &&
        test_i18ncmp expect output
 
@@ -331,8 +331,7 @@ test_expect_success 'status -uno' '
 '
 
 test_expect_success 'status (status.showUntrackedFiles no)' '
-       git config status.showuntrackedfiles no
-       test_when_finished "git config --unset status.showuntrackedfiles" &&
+       test_config status.showuntrackedfiles no &&
        git status >output &&
        test_i18ncmp expect output
 '
@@ -347,12 +346,11 @@ cat >expect <<EOF
 #
 # Untracked files not listed
 EOF
-git config advice.statusHints false
 test_expect_success 'status -uno (advice.statusHints false)' '
+       test_config advice.statusHints false &&
        git status -uno >output &&
        test_i18ncmp expect output
 '
-git config --unset advice.statusHints
 
 cat >expect << EOF
  M dir1/modified
@@ -399,8 +397,7 @@ test_expect_success 'status -unormal' '
 '
 
 test_expect_success 'status (status.showUntrackedFiles normal)' '
-       git config status.showuntrackedfiles normal
-       test_when_finished "git config --unset status.showuntrackedfiles" &&
+       test_config status.showuntrackedfiles normal
        git status >output &&
        test_i18ncmp expect output
 '
@@ -458,8 +455,7 @@ test_expect_success 'status -uall' '
 '
 
 test_expect_success 'status (status.showUntrackedFiles all)' '
-       git config status.showuntrackedfiles all
-       test_when_finished "git config --unset status.showuntrackedfiles" &&
+       test_config status.showuntrackedfiles all
        git status >output &&
        test_i18ncmp expect output
 '
@@ -484,10 +480,9 @@ test_expect_success 'status -s -uall' '
        test_cmp expect output
 '
 test_expect_success 'status -s (status.showUntrackedFiles all)' '
-       git config status.showuntrackedfiles all
+       test_config status.showuntrackedfiles all &&
        git status -s >output &&
        rm -rf dir3 &&
-       git config --unset status.showuntrackedfiles &&
        test_cmp expect output
 '
 
@@ -587,15 +582,13 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'status with color.ui' '
-       git config color.ui always &&
-       test_when_finished "git config --unset color.ui" &&
+       test_config color.ui always &&
        git status | test_decode_color >output &&
        test_i18ncmp expect output
 '
 
 test_expect_success 'status with color.status' '
-       git config color.status always &&
-       test_when_finished "git config --unset color.status" &&
+       test_config color.status always &&
        git status | test_decode_color >output &&
        test_i18ncmp expect output
 '
@@ -719,8 +712,7 @@ EOF
 
 test_expect_success 'status without relative paths' '
 
-       git config status.relativePaths false &&
-       test_when_finished "git config --unset status.relativePaths" &&
+       test_config status.relativePaths false &&
        (cd dir1 && git status) >output &&
        test_i18ncmp expect output
 
@@ -739,8 +731,7 @@ EOF
 
 test_expect_success 'status -s without relative paths' '
 
-       git config status.relativePaths false &&
-       test_when_finished "git config --unset status.relativePaths" &&
+       test_config status.relativePaths false &&
        (cd dir1 && git status -s) >output &&
        test_cmp expect output
 
@@ -1037,15 +1028,14 @@ test_expect_success '--ignore-submodules=untracked suppresses submodules with un
 '
 
 test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' '
-       git config diff.ignoreSubmodules dirty &&
+       test_config diff.ignoreSubmodules dirty &&
        git status >output &&
        test_i18ncmp expect output &&
        git config --add -f .gitmodules submodule.subname.ignore untracked &&
        git config --add -f .gitmodules submodule.subname.path sm &&
        git status >output &&
        test_i18ncmp expect output &&
-       git config -f .gitmodules  --remove-section submodule.subname &&
-       git config --unset diff.ignoreSubmodules
+       git config -f .gitmodules  --remove-section submodule.subname
 '
 
 test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' '
@@ -1065,15 +1055,14 @@ test_expect_success '--ignore-submodules=dirty suppresses submodules with untrac
 '
 
 test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' '
-       git config diff.ignoreSubmodules dirty &&
+       test_config diff.ignoreSubmodules dirty &&
        git status >output &&
        ! test -s actual &&
        git config --add -f .gitmodules submodule.subname.ignore dirty &&
        git config --add -f .gitmodules submodule.subname.path sm &&
        git status >output &&
        test_i18ncmp expect output &&
-       git config -f .gitmodules  --remove-section submodule.subname &&
-       git config --unset diff.ignoreSubmodules
+       git config -f .gitmodules  --remove-section submodule.subname
 '
 
 test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' '
@@ -1290,15 +1279,13 @@ cat > expect << EOF
 EOF
 
 test_expect_success "status (core.commentchar with submodule summary)" '
-       test_when_finished "git config --unset core.commentchar" &&
-       git config core.commentchar ";" &&
+       test_config core.commentchar ";" &&
        git status >output &&
        test_i18ncmp expect output
 '
 
 test_expect_success "status (core.commentchar with two chars with submodule summary)" '
-       test_when_finished "git config --unset core.commentchar" &&
-       git config core.commentchar ";;" &&
+       test_config core.commentchar ";;" &&
        git status >output &&
        test_i18ncmp expect output
 '
index d2da89a5f572134c55c690b1439ffde66ecdd6cb..06749a6aa07e40a5e5081c8912264d9c15b385ef 100755 (executable)
@@ -14,6 +14,7 @@ test_description='git status advice'
 set_fake_editor
 
 test_expect_success 'prepare for conflicts' '
+       git config --global advice.statusuoption false &&
        test_commit init main.txt init &&
        git branch conflicts &&
        test_commit on_master main.txt on_master &&
@@ -76,7 +77,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts'
        ONTO=$(git rev-parse --short HEAD^^) &&
        test_must_fail git rebase HEAD^ --onto HEAD^^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
        #   (fix conflicts and then run "git rebase --continue")
        #   (use "git rebase --skip" to skip this patch)
@@ -103,7 +104,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' '
        echo three >main.txt &&
        git add main.txt &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
        #   (all conflicts fixed: run "git rebase --continue")
        #
@@ -135,7 +136,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' '
        ONTO=$(git rev-parse --short rebase_i_conflicts) &&
        test_must_fail git rebase -i rebase_i_conflicts &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
        #   (fix conflicts and then run "git rebase --continue")
        #   (use "git rebase --skip" to skip this patch)
@@ -161,7 +162,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' '
        test_must_fail git rebase -i rebase_i_conflicts &&
        git add main.txt &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
        #   (all conflicts fixed: run "git rebase --continue")
        #
@@ -187,9 +188,10 @@ test_expect_success 'status when rebasing -i in edit mode' '
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short HEAD~2) &&
+       TGT=$(git rev-parse --short two_rebase_i) &&
        git rebase -i HEAD~2 &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $TGT
        # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -214,8 +216,9 @@ test_expect_success 'status when splitting a commit' '
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git reset HEAD^ &&
+       TGT=$(git rev-parse --short HEAD) &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $TGT
        # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@ -243,10 +246,11 @@ test_expect_success 'status after editing the last commit with --amend during a
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short HEAD~3) &&
+       TGT=$(git rev-parse --short three_amend) &&
        git rebase -i HEAD~3 &&
        git commit --amend -m "foo" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $TGT
        # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -276,7 +280,7 @@ test_expect_success 'status: (continue first edit) second edit' '
        git rebase -i HEAD~3 &&
        git rebase --continue &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -298,7 +302,7 @@ test_expect_success 'status: (continue first edit) second edit and split' '
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@ -325,7 +329,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' '
        git rebase --continue &&
        git commit --amend -m "foo" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -347,7 +351,7 @@ test_expect_success 'status: (amend first edit) second edit' '
        git commit --amend -m "a" &&
        git rebase --continue &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -370,7 +374,7 @@ test_expect_success 'status: (amend first edit) second edit and split' '
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@ -398,7 +402,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' '
        git rebase --continue &&
        git commit --amend -m "d" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -422,7 +426,7 @@ test_expect_success 'status: (split first edit) second edit' '
        git commit -m "e" &&
        git rebase --continue &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -447,7 +451,7 @@ test_expect_success 'status: (split first edit) second edit and split' '
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@ -477,7 +481,7 @@ test_expect_success 'status: (split first edit) second edit and amend' '
        git rebase --continue &&
        git commit --amend -m "h" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@ -572,9 +576,10 @@ test_expect_success 'status when bisecting' '
        git bisect start &&
        git bisect bad &&
        git bisect good one_bisect &&
-       cat >expected <<-\EOF &&
-       # Not currently on any branch.
-       # You are currently bisecting branch '\''bisect'\''.
+       TGT=$(git rev-parse --short two_bisect) &&
+       cat >expected <<-EOF &&
+       # HEAD detached at $TGT
+       # You are currently bisecting, started from branch '\''bisect'\''.
        #   (use "git bisect reset" to get back to the original branch)
        #
        nothing to commit (use -u to show untracked files)
@@ -596,7 +601,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
        ONTO=$(git rev-parse --short HEAD^^) &&
        test_must_fail git rebase HEAD^ --onto HEAD^^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
        #
        # Unmerged paths:
@@ -662,5 +667,15 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
        test_i18ncmp expected actual
 '
 
+test_expect_success 'status showing detached from a tag' '
+       test_commit atag tagging &&
+       git checkout atag &&
+       cat >expected <<-\EOF
+       # HEAD detached at atag
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
 
 test_done
index 5e19598fe72787e17d521980a3edfacb1dfa2024..2f70433568e3134b09d7e1eb10cc5c8995c1a106 100755 (executable)
@@ -56,7 +56,8 @@ create_merge_msgs () {
                echo &&
                git log --no-merges ^HEAD c2 c3
        } >squash.1-5-9 &&
-       echo >msg.nolog &&
+       : >msg.nologff &&
+       echo >msg.nolognoff &&
        {
                echo "* tag 'c3':" &&
                echo "  commit 3" &&
@@ -244,8 +245,7 @@ test_expect_success 'merges with --ff-only' '
 test_expect_success 'merges with merge.ff=only' '
        git reset --hard c1 &&
        test_tick &&
-       test_when_finished "git config --unset merge.ff" &&
-       git config merge.ff only &&
+       test_config merge.ff "only" &&
        test_must_fail git merge c2 &&
        test_must_fail git merge c3 &&
        test_must_fail git merge c2 c3 &&
@@ -336,7 +336,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (no-commit in config)' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "--no-commit" &&
+       test_config branch.master.mergeoptions "--no-commit" &&
        git merge c2 &&
        verify_merge file result.1-5 &&
        verify_head $c1 &&
@@ -346,12 +346,11 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' '
 test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (log in config)' '
-       git config branch.master.mergeoptions "" &&
        git reset --hard c1 &&
        git merge --log c2 &&
        git show -s --pretty=tformat:%s%n%b >expect &&
 
-       git config branch.master.mergeoptions --log &&
+       test_config branch.master.mergeoptions "--log" &&
        git reset --hard c1 &&
        git merge c2 &&
        git show -s --pretty=tformat:%s%n%b >actual &&
@@ -360,17 +359,12 @@ test_expect_success 'merge c1 with c2 (log in config)' '
 '
 
 test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
-       test_when_finished "git config --remove-section branch.master" &&
-       test_when_finished "git config --remove-section merge" &&
-       test_might_fail git config --remove-section branch.master &&
-       test_might_fail git config --remove-section merge &&
-
        git reset --hard c1 &&
        git merge c2 &&
        git show -s --pretty=tformat:%s%n%b >expect &&
 
-       git config branch.master.mergeoptions "--no-log" &&
-       git config merge.log true &&
+       test_config branch.master.mergeoptions "--no-log" &&
+       test_config merge.log "true" &&
        git reset --hard c1 &&
        git merge c2 &&
        git show -s --pretty=tformat:%s%n%b >actual &&
@@ -380,7 +374,7 @@ test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
 
 test_expect_success 'merge c1 with c2 (squash in config)' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "--squash" &&
+       test_config branch.master.mergeoptions "--squash" &&
        git merge c2 &&
        verify_merge file result.1-5 &&
        verify_head $c1 &&
@@ -392,7 +386,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'override config option -n with --summary' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "-n" &&
+       test_config branch.master.mergeoptions "-n" &&
        test_tick &&
        git merge --summary c2 >diffstat.txt &&
        verify_merge file result.1-5 msg.1-5 &&
@@ -406,7 +400,7 @@ test_expect_success 'override config option -n with --summary' '
 
 test_expect_success 'override config option -n with --stat' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "-n" &&
+       test_config branch.master.mergeoptions "-n" &&
        test_tick &&
        git merge --stat c2 >diffstat.txt &&
        verify_merge file result.1-5 msg.1-5 &&
@@ -422,7 +416,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'override config option --stat' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "--stat" &&
+       test_config branch.master.mergeoptions "--stat" &&
        test_tick &&
        git merge -n c2 >diffstat.txt &&
        verify_merge file result.1-5 msg.1-5 &&
@@ -438,7 +432,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (override --no-commit)' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "--no-commit" &&
+       test_config branch.master.mergeoptions "--no-commit" &&
        test_tick &&
        git merge --commit c2 &&
        verify_merge file result.1-5 msg.1-5 &&
@@ -449,7 +443,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (override --squash)' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "--squash" &&
+       test_config branch.master.mergeoptions "--squash" &&
        test_tick &&
        git merge --no-squash c2 &&
        verify_merge file result.1-5 msg.1-5 &&
@@ -460,7 +454,6 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (no-ff)' '
        git reset --hard c0 &&
-       git config branch.master.mergeoptions "" &&
        test_tick &&
        git merge --no-ff c1 &&
        verify_merge file result.1 &&
@@ -471,10 +464,9 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (merge.ff=false)' '
        git reset --hard c0 &&
-       git config merge.ff false &&
+       test_config merge.ff "false" &&
        test_tick &&
        git merge c1 &&
-       git config --remove-section merge &&
        verify_merge file result.1 &&
        verify_parents $c0 $c1
 '
@@ -482,22 +474,19 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
        git reset --hard c0 &&
-       git config branch.master.mergeoptions --ff &&
-       git config merge.ff false &&
+       test_config branch.master.mergeoptions "--ff" &&
+       test_config merge.ff "false" &&
        test_tick &&
        git merge c1 &&
-       git config --remove-section "branch.master" &&
-       git config --remove-section "merge" &&
        verify_merge file result.1 &&
        verify_parents "$c0"
 '
 
 test_expect_success 'tolerate unknown values for merge.ff' '
        git reset --hard c0 &&
-       git config merge.ff something-new &&
+       test_config merge.ff "something-new" &&
        test_tick &&
        git merge c1 2>message &&
-       git config --remove-section "merge" &&
        verify_head "$c1" &&
        test_cmp empty message
 '
@@ -515,7 +504,7 @@ test_expect_success 'combining --ff-only and --no-ff is refused' '
 
 test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
        git reset --hard c0 &&
-       git config branch.master.mergeoptions "--no-ff" &&
+       test_config branch.master.mergeoptions "--no-ff" &&
        git merge --ff c1 &&
        verify_merge file result.1 &&
        verify_head $c1
@@ -525,14 +514,20 @@ test_expect_success 'merge log message' '
        git reset --hard c0 &&
        git merge --no-log c2 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
-       test_cmp msg.nolog msg.act &&
+       test_cmp msg.nologff msg.act &&
+
+       git reset --hard c0 &&
+       test_config branch.master.mergeoptions "--no-ff" &&
+       git merge --no-log c2 &&
+       git show -s --pretty=format:%b HEAD >msg.act &&
+       test_cmp msg.nolognoff msg.act &&
 
        git merge --log c3 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
        test_cmp msg.log msg.act &&
 
        git reset --hard HEAD^ &&
-       git config merge.log yes &&
+       test_config merge.log "yes" &&
        git merge c3 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
        test_cmp msg.log msg.act
@@ -542,7 +537,6 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c0, c2, c0, and c1' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "" &&
        test_tick &&
        git merge c0 c2 c0 c1 &&
        verify_merge file result.1-5 &&
@@ -553,7 +547,6 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c0, c2, c0, and c1' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "" &&
        test_tick &&
        git merge c0 c2 c0 c1 &&
        verify_merge file result.1-5 &&
@@ -564,7 +557,6 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c1 and c2' '
        git reset --hard c1 &&
-       git config branch.master.mergeoptions "" &&
        test_tick &&
        git merge c1 c2 &&
        verify_merge file result.1-5 &&
index eb1d3f85b59b2e74231f0285fc4e90d95d0658fa..c6d6b1c99fe98b7dc692eaf3828a08696bfb5e10 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-# Copyright (c) 2009, 2010 David Aguilar
+# Copyright (c) 2009, 2010, 2012, 2013 David Aguilar
 #
 
 test_description='git-difftool
@@ -10,43 +10,25 @@ Testing basic diff tool invocation
 
 . ./test-lib.sh
 
-remove_config_vars()
+difftool_test_setup ()
 {
-       # Unset all config variables used by git-difftool
-       git config --unset diff.tool
-       git config --unset diff.guitool
-       git config --unset difftool.test-tool.cmd
-       git config --unset difftool.prompt
-       git config --unset merge.tool
-       git config --unset mergetool.test-tool.cmd
-       git config --unset mergetool.prompt
-       return 0
+       test_config diff.tool test-tool &&
+       test_config difftool.test-tool.cmd 'cat "$LOCAL"' &&
+       test_config difftool.bogus-tool.cmd false
 }
 
-restore_test_defaults()
-{
-       # Restores the test defaults used by several tests
-       remove_config_vars
-       unset GIT_DIFF_TOOL
-       unset GIT_DIFFTOOL_PROMPT
-       unset GIT_DIFFTOOL_NO_PROMPT
-       git config diff.tool test-tool &&
-       git config difftool.test-tool.cmd 'cat $LOCAL'
-       git config difftool.bogus-tool.cmd false
-}
-
-prompt_given()
+prompt_given ()
 {
        prompt="$1"
        test "$prompt" = "Launch 'test-tool' [Y/n]: branch"
 }
 
-stdin_contains()
+stdin_contains ()
 {
        grep >/dev/null "$1"
 }
 
-stdin_doesnot_contain()
+stdin_doesnot_contain ()
 {
        ! stdin_contains "$1"
 }
@@ -65,249 +47,237 @@ test_expect_success PERL 'setup' '
 
 # Configure a custom difftool.<tool>.cmd and use it
 test_expect_success PERL 'custom commands' '
-       restore_test_defaults &&
-       git config difftool.test-tool.cmd "cat \$REMOTE" &&
+       difftool_test_setup &&
+       test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" &&
+       echo master >expect &&
+       git difftool --no-prompt branch >actual &&
+       test_cmp expect actual &&
 
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "master" &&
-
-       restore_test_defaults &&
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch"
+       test_config difftool.test-tool.cmd "cat \"\$LOCAL\"" &&
+       echo branch >expect &&
+       git difftool --no-prompt branch >actual &&
+       test_cmp expect actual
 '
 
-# Ensures that a custom difftool.<tool>.cmd overrides built-ins
-test_expect_success PERL 'custom commands override built-ins' '
-       restore_test_defaults &&
-       git config difftool.defaults.cmd "cat \$REMOTE" &&
-
-       diff=$(git difftool --tool defaults --no-prompt branch) &&
-       test "$diff" = "master" &&
-
-       git config --unset difftool.defaults.cmd
+test_expect_success PERL 'custom tool commands override built-ins' '
+       test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" &&
+       echo master >expect &&
+       git difftool --tool vimdiff --no-prompt branch >actual &&
+       test_cmp expect actual
 '
 
-# Ensures that git-difftool ignores bogus --tool values
 test_expect_success PERL 'difftool ignores bad --tool values' '
-       diff=$(git difftool --no-prompt --tool=bad-tool branch)
-       test "$?" = 1 &&
-       test "$diff" = ""
+       : >expect &&
+       test_expect_code 1 \
+               git difftool --no-prompt --tool=bad-tool branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool forwards arguments to diff' '
+       difftool_test_setup &&
        >for-diff &&
        git add for-diff &&
        echo changes>for-diff &&
        git add for-diff &&
-       diff=$(git difftool --cached --no-prompt -- for-diff) &&
-       test "$diff" = "" &&
+       : >expect &&
+       git difftool --cached --no-prompt -- for-diff >actual &&
+       test_cmp expect actual &&
        git reset -- for-diff &&
        rm for-diff
 '
 
 test_expect_success PERL 'difftool honors --gui' '
-       git config merge.tool bogus-tool &&
-       git config diff.tool bogus-tool &&
-       git config diff.guitool test-tool &&
-
-       diff=$(git difftool --no-prompt --gui branch) &&
-       test "$diff" = "branch" &&
+       difftool_test_setup &&
+       test_config merge.tool bogus-tool &&
+       test_config diff.tool bogus-tool &&
+       test_config diff.guitool test-tool &&
 
-       restore_test_defaults
+       echo branch >expect &&
+       git difftool --no-prompt --gui branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool --gui last setting wins' '
-       git config diff.guitool bogus-tool &&
-       git difftool --no-prompt --gui --no-gui &&
+       difftool_test_setup &&
+       : >expect &&
+       git difftool --no-prompt --gui --no-gui >actual &&
+       test_cmp expect actual &&
 
-       git config merge.tool bogus-tool &&
-       git config diff.tool bogus-tool &&
-       git config diff.guitool test-tool &&
-       diff=$(git difftool --no-prompt --no-gui --gui branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       test_config merge.tool bogus-tool &&
+       test_config diff.tool bogus-tool &&
+       test_config diff.guitool test-tool &&
+       echo branch >expect &&
+       git difftool --no-prompt --no-gui --gui branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
-       git config diff.tool test-tool &&
-
-       diff=$(git difftool --no-prompt --gui branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       echo branch >expect &&
+       git difftool --no-prompt --gui branch >actual &&
+       test_cmp expect actual
 '
 
 # Specify the diff tool using $GIT_DIFF_TOOL
 test_expect_success PERL 'GIT_DIFF_TOOL variable' '
-       test_might_fail git config --unset diff.tool &&
-       GIT_DIFF_TOOL=test-tool &&
-       export GIT_DIFF_TOOL &&
-
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       git config --unset diff.tool &&
+       echo branch >expect &&
+       GIT_DIFF_TOOL=test-tool git difftool --no-prompt branch >actual &&
+       test_cmp expect actual
 '
 
 # Test the $GIT_*_TOOL variables and ensure
 # that $GIT_DIFF_TOOL always wins unless --tool is specified
 test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
-       git config diff.tool bogus-tool &&
-       git config merge.tool bogus-tool &&
-
-       GIT_DIFF_TOOL=test-tool &&
-       export GIT_DIFF_TOOL &&
-
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch" &&
+       difftool_test_setup &&
+       test_config diff.tool bogus-tool &&
+       test_config merge.tool bogus-tool &&
 
-       GIT_DIFF_TOOL=bogus-tool &&
-       export GIT_DIFF_TOOL &&
+       echo branch >expect &&
+       GIT_DIFF_TOOL=test-tool git difftool --no-prompt branch >actual &&
+       test_cmp expect actual &&
 
-       diff=$(git difftool --no-prompt --tool=test-tool branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       test_config diff.tool bogus-tool &&
+       test_config merge.tool bogus-tool &&
+       GIT_DIFF_TOOL=bogus-tool \
+               git difftool --no-prompt --tool=test-tool branch >actual &&
+       test_cmp expect actual
 '
 
 # Test that we don't have to pass --no-prompt to difftool
 # when $GIT_DIFFTOOL_NO_PROMPT is true
 test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
-       GIT_DIFFTOOL_NO_PROMPT=true &&
-       export GIT_DIFFTOOL_NO_PROMPT &&
-
-       diff=$(git difftool branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       echo branch >expect &&
+       GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&
+       test_cmp expect actual
 '
 
 # git-difftool supports the difftool.prompt variable.
 # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
 test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
-       git config difftool.prompt false &&
-       GIT_DIFFTOOL_PROMPT=true &&
-       export GIT_DIFFTOOL_PROMPT &&
-
-       prompt=$(echo | git difftool branch | tail -1) &&
-       prompt_given "$prompt" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       test_config difftool.prompt false &&
+       echo >input &&
+       GIT_DIFFTOOL_PROMPT=true git difftool branch <input >output &&
+       prompt=$(tail -1 <output) &&
+       prompt_given "$prompt"
 '
 
 # Test that we don't have to pass --no-prompt when difftool.prompt is false
 test_expect_success PERL 'difftool.prompt config variable is false' '
-       git config difftool.prompt false &&
-
-       diff=$(git difftool branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       test_config difftool.prompt false &&
+       echo branch >expect &&
+       git difftool branch >actual &&
+       test_cmp expect actual
 '
 
 # Test that we don't have to pass --no-prompt when mergetool.prompt is false
 test_expect_success PERL 'difftool merge.prompt = false' '
+       difftool_test_setup &&
        test_might_fail git config --unset difftool.prompt &&
-       git config mergetool.prompt false &&
-
-       diff=$(git difftool branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       test_config mergetool.prompt false &&
+       echo branch >expect &&
+       git difftool branch >actual &&
+       test_cmp expect actual
 '
 
 # Test that the -y flag can override difftool.prompt = true
 test_expect_success PERL 'difftool.prompt can overridden with -y' '
-       git config difftool.prompt true &&
-
-       diff=$(git difftool -y branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       test_config difftool.prompt true &&
+       echo branch >expect &&
+       git difftool -y branch >actual &&
+       test_cmp expect actual
 '
 
 # Test that the --prompt flag can override difftool.prompt = false
 test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
-       git config difftool.prompt false &&
-
-       prompt=$(echo | git difftool --prompt branch | tail -1) &&
-       prompt_given "$prompt" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       test_config difftool.prompt false &&
+       echo >input &&
+       git difftool --prompt branch <input >output &&
+       prompt=$(tail -1 <output) &&
+       prompt_given "$prompt"
 '
 
 # Test that the last flag passed on the command-line wins
 test_expect_success PERL 'difftool last flag wins' '
-       diff=$(git difftool --prompt --no-prompt branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults &&
-
-       prompt=$(echo | git difftool --no-prompt --prompt branch | tail -1) &&
-       prompt_given "$prompt" &&
-
-       restore_test_defaults
+       difftool_test_setup &&
+       echo branch >expect &&
+       git difftool --prompt --no-prompt branch >actual &&
+       test_cmp expect actual &&
+       echo >input &&
+       git difftool --no-prompt --prompt branch <input >output &&
+       prompt=$(tail -1 <output) &&
+       prompt_given "$prompt"
 '
 
 # git-difftool falls back to git-mergetool config variables
 # so test that behavior here
 test_expect_success PERL 'difftool + mergetool config variables' '
-       remove_config_vars &&
-       git config merge.tool test-tool &&
-       git config mergetool.test-tool.cmd "cat \$LOCAL" &&
-
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch" &&
+       test_config merge.tool test-tool &&
+       test_config mergetool.test-tool.cmd "cat \$LOCAL" &&
+       echo branch >expect &&
+       git difftool --no-prompt branch >actual &&
+       test_cmp expect actual &&
 
        # set merge.tool to something bogus, diff.tool to test-tool
-       git config merge.tool bogus-tool &&
-       git config diff.tool test-tool &&
-
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch" &&
-
-       restore_test_defaults
+       test_config merge.tool bogus-tool &&
+       test_config diff.tool test-tool &&
+       git difftool --no-prompt branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool.<tool>.path' '
-       git config difftool.tkdiff.path echo &&
-       diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
-       git config --unset difftool.tkdiff.path &&
-       lines=$(echo "$diff" | grep file | wc -l) &&
-       test "$lines" -eq 1 &&
-
-       restore_test_defaults
+       test_config difftool.tkdiff.path echo &&
+       git difftool --tool=tkdiff --no-prompt branch >output &&
+       lines=$(grep file output | wc -l) &&
+       test "$lines" -eq 1
 '
 
 test_expect_success PERL 'difftool --extcmd=cat' '
-       diff=$(git difftool --no-prompt --extcmd=cat branch) &&
-       test "$diff" = branch"$LF"master
+       echo branch >expect &&
+       echo master >>expect &&
+       git difftool --no-prompt --extcmd=cat branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool --extcmd cat' '
-       diff=$(git difftool --no-prompt --extcmd cat branch) &&
-       test "$diff" = branch"$LF"master
+       echo branch >expect &&
+       echo master >>expect &&
+       git difftool --no-prompt --extcmd=cat branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool -x cat' '
-       diff=$(git difftool --no-prompt -x cat branch) &&
-       test "$diff" = branch"$LF"master
+       echo branch >expect &&
+       echo master >>expect &&
+       git difftool --no-prompt -x cat branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool --extcmd echo arg1' '
-       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) &&
-       test "$diff" = file
+       echo file >expect &&
+       git difftool --no-prompt \
+               --extcmd sh\ -c\ \"echo\ \$1\" branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool --extcmd cat arg1' '
-       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) &&
-       test "$diff" = master
+       echo master >expect &&
+       git difftool --no-prompt \
+               --extcmd sh\ -c\ \"cat\ \$1\" branch >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success PERL 'difftool --extcmd cat arg2' '
-       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) &&
-       test "$diff" = branch
+       echo branch >expect &&
+       git difftool --no-prompt \
+               --extcmd sh\ -c\ \"cat\ \$2\" branch >actual &&
+       test_cmp expect actual
 '
 
 # Create a second file on master and a different version on branch
@@ -324,26 +294,26 @@ test_expect_success PERL 'setup with 2 files different' '
 '
 
 test_expect_success PERL 'say no to the first file' '
-       diff=$( (echo n; echo) | git difftool -x cat branch ) &&
-
-       echo "$diff" | stdin_contains m2 &&
-       echo "$diff" | stdin_contains br2 &&
-       echo "$diff" | stdin_doesnot_contain master &&
-       echo "$diff" | stdin_doesnot_contain branch
+       (echo n && echo) >input &&
+       git difftool -x cat branch <input >output &&
+       stdin_contains m2 <output &&
+       stdin_contains br2 <output &&
+       stdin_doesnot_contain master <output &&
+       stdin_doesnot_contain branch <output
 '
 
 test_expect_success PERL 'say no to the second file' '
-       diff=$( (echo; echo n) | git difftool -x cat branch ) &&
-
-       echo "$diff" | stdin_contains master &&
-       echo "$diff" | stdin_contains branch &&
-       echo "$diff" | stdin_doesnot_contain m2 &&
-       echo "$diff" | stdin_doesnot_contain br2
+       (echo && echo n) >input &&
+       git difftool -x cat branch <input >output &&
+       stdin_contains master <output &&
+       stdin_contains branch  <output &&
+       stdin_doesnot_contain m2 <output &&
+       stdin_doesnot_contain br2 <output
 '
 
 test_expect_success PERL 'difftool --tool-help' '
-       tool_help=$(git difftool --tool-help) &&
-       echo "$tool_help" | stdin_contains tool
+       git difftool --tool-help >output &&
+       stdin_contains tool <output
 '
 
 test_expect_success PERL 'setup change in subdirectory' '
@@ -359,29 +329,51 @@ test_expect_success PERL 'setup change in subdirectory' '
 '
 
 test_expect_success PERL 'difftool -d' '
-       diff=$(git difftool -d --extcmd ls branch) &&
-       echo "$diff" | stdin_contains sub &&
-       echo "$diff" | stdin_contains file
+       git difftool -d --extcmd ls branch >output &&
+       stdin_contains sub <output &&
+       stdin_contains file <output
 '
 
 test_expect_success PERL 'difftool --dir-diff' '
-       diff=$(git difftool --dir-diff --extcmd ls branch) &&
-       echo "$diff" | stdin_contains sub &&
-       echo "$diff" | stdin_contains file
+       git difftool --dir-diff --extcmd ls branch >output &&
+       stdin_contains sub <output &&
+       stdin_contains file <output
+'
+
+write_script .git/CHECK_SYMLINKS <<\EOF
+for f in file file2 sub/sub
+do
+       echo "$f"
+       readlink "$2/$f"
+done >actual
+EOF
+
+test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
+       cat >expect <<-EOF &&
+       file
+       $(pwd)/file
+       file2
+       $(pwd)/file2
+       sub/sub
+       $(pwd)/sub/sub
+       EOF
+       git difftool --dir-diff --symlink \
+               --extcmd "./.git/CHECK_SYMLINKS" branch HEAD &&
+       test_cmp actual expect
 '
 
 test_expect_success PERL 'difftool --dir-diff ignores --prompt' '
-       diff=$(git difftool --dir-diff --prompt --extcmd ls branch) &&
-       echo "$diff" | stdin_contains sub &&
-       echo "$diff" | stdin_contains file
+       git difftool --dir-diff --prompt --extcmd ls branch >output &&
+       stdin_contains sub <output &&
+       stdin_contains file <output
 '
 
 test_expect_success PERL 'difftool --dir-diff from subdirectory' '
        (
                cd sub &&
-               diff=$(git difftool --dir-diff --extcmd ls branch) &&
-               echo "$diff" | stdin_contains sub &&
-               echo "$diff" | stdin_contains file
+               git difftool --dir-diff --extcmd ls branch >output &&
+               stdin_contains sub <output &&
+               stdin_contains file <output
        )
 '
 
index a8957782cfb8fae4b7c171d4c9db24ce6cd5505e..e1951a5cbb759a0f233b624a1a1a7e9c55ff9640 100755 (executable)
@@ -125,11 +125,6 @@ test_expect_success 'modified file' '
        test_cmp empty out
 '
 
-test_config() {
-       git config "$1" "$2" &&
-       test_when_finished "git config --unset $1"
-}
-
 test_expect_success 'copes with color settings' '
        rm -f actual &&
        echo grep.h >expect &&
index 90bb6050c13ece02199b6978e43e3c67de563c84..6783c14c1ad9af0f5aa3d16e13250e9a1a292b28 100755 (executable)
@@ -539,8 +539,7 @@ test_expect_success \
         test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
         echo "ISO-8859-1" >> file &&
         git add file &&
-        git config i18n.commitencoding ISO-8859-1 &&
-        test_when_finished "git config --unset i18n.commitencoding" &&
+        test_config i18n.commitencoding ISO-8859-1 &&
         git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
         gitweb_run "p=.git;a=commit"'
 
index dc92e60cd6f09fa5e2610d491cf1c07d43c45dfd..11d2b5102cde384ad11540610a52f62c4732aa8c 100755 (executable)
@@ -42,6 +42,47 @@ test_expect_success 'P4CONFIG and relative dir clone' '
        )
 '
 
+# Common setup using .p4config to set P4CLIENT and P4PORT breaks
+# if clone destination is relative.  Make sure that chdir() expands
+# the relative path in --dest to absolute.
+test_expect_success 'p4 client root would be relative due to clone --dest' '
+       test_when_finished cleanup_git &&
+       (
+               echo P4PORT=$P4PORT >git/.p4config &&
+               P4CONFIG=.p4config &&
+               export P4CONFIG &&
+               unset P4PORT &&
+               git p4 clone --dest="git" //depot
+       )
+'
+
+# When the p4 client Root is a symlink, make sure chdir() does not use
+# getcwd() to convert it to a physical path.
+test_expect_success SYMLINKS 'p4 client root symlink should stay symbolic' '
+       physical="$TRASH_DIRECTORY/physical" &&
+       symbolic="$TRASH_DIRECTORY/symbolic" &&
+       test_when_finished "rm -rf \"$physical\"" &&
+       test_when_finished "rm \"$symbolic\"" &&
+       mkdir -p "$physical" &&
+       ln -s "$physical" "$symbolic" &&
+       test_when_finished cleanup_git &&
+       (
+               P4CLIENT=client-sym &&
+               p4 client -i <<-EOF &&
+               Client: $P4CLIENT
+               Description: $P4CLIENT
+               Root: $symbolic
+               LineEnd: unix
+               View: //depot/... //$P4CLIENT/...
+               EOF
+               git p4 clone --dest="$git" //depot &&
+               cd "$git" &&
+               test_commit file2 &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       )
+'
+
 test_expect_success 'kill p4d' '
        kill_p4d
 '
index fa62d010f68e3ee97e6754687ad4d08564d3c96b..61d0804435d3e8aed2f3b2c0308bc1a9143f1f5a 100644 (file)
@@ -135,12 +135,12 @@ test_pause () {
        fi
 }
 
-# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+# Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]"
 #
 # This will commit a file with the given contents and the given commit
-# message.  It will also add a tag with <message> as name.
+# message, and tag the resulting commit with the given tag name.
 #
-# Both <file> and <contents> default to <message>.
+# <file>, <contents>, and <tag> all default to <message>.
 
 test_commit () {
        notick= &&
@@ -168,7 +168,7 @@ test_commit () {
                test_tick
        fi &&
        git commit $signoff -m "$1" &&
-       git tag "$1"
+       git tag "${4:-$1}"
 }
 
 # Call test_merge with the arguments "<message> <commit>", where <commit>
index 9e7f6b424d77ee88c3cd36507cd421a437ec7bfa..1f510252ad7cb7a5afae67731bc37fccf941b654 100644 (file)
@@ -760,3 +760,9 @@ test_lazy_prereq AUTOIDENT '
 # When the tests are run as root, permission tests will report that
 # things are writable when they shouldn't be.
 test -w / || test_set_prereq SANITY
+
+GIT_UNZIP=${GIT_UNZIP:-unzip}
+test_lazy_prereq UNZIP '
+       "$GIT_UNZIP" -v
+       test $? -ne 127
+'
index 71ab04edc09be7aeefa1e8a0f609a974ffd55a9f..d84758373da1d3121ec907435ae5d598c5c14b26 100755 (executable)
@@ -38,7 +38,7 @@ if [ -z "$GIT_DIR" ]; then
 fi
 
 if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
-       echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+       echo "usage: $0 <ref> <oldrev> <newrev>" >&2
        exit 1
 fi
 
index 92713d16da5431e9c8395967ab0e116402dfed67..02b42badd550a4b775aab084b38a63c2f4f561a9 100644 (file)
@@ -114,6 +114,6 @@ int main(int argc, const char *argv[])
        return 0;
 
 usage:
-       fprintf(stderr, "Usage: %s %s\n", argv[0], usage_str);
+       fprintf(stderr, "usage: %s %s\n", argv[0], usage_str);
        return -1;
 }
index af40a3c49ee0df5f0caaebae8e9bc6e9ffdb092b..4595cd6433f9fd543791ee5a8a59a9112b50c046 100644 (file)
@@ -23,7 +23,7 @@ int main(int argc, char *argv[])
        unsigned long from_size, data_size, out_size;
 
        if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) {
-               fprintf(stderr, "Usage: %s\n", usage_str);
+               fprintf(stderr, "usage: %s\n", usage_str);
                return 1;
        }
 
index b3c28d9a1c1102f2862af68b0bda7e9b0424f5cc..54824d075421e792f337beb5cdce170a33b00e68 100644 (file)
@@ -12,7 +12,7 @@ int main(int argc, char *argv[])
        unsigned char *c;
 
        if (argc < 2 || argc > 3) {
-               fprintf(stderr, "Usage: %s <seed_string> [<size>]\n", argv[0]);
+               fprintf(stderr, "usage: %s <seed_string> [<size>]\n", argv[0]);
                return 1;
        }
 
index 0f2d9a4a3d8ea732c7c61e3818bd0bbe417c76b0..120ec96b0dbd94e7be9ffc81d0fb87ccbd30a7df 100644 (file)
@@ -24,7 +24,7 @@ static int apply_delta(int argc, char *argv[])
                die_errno("cannot open preimage");
        if (buffer_init(&delta, argv[3]))
                die_errno("cannot open delta");
-       if (svndiff0_apply(&delta, (off_t) strtoull(argv[4], NULL, 0),
+       if (svndiff0_apply(&delta, (off_t) strtoumax(argv[4], NULL, 0),
                                        &preimage_view, stdout))
                return 1;
        if (buffer_deinit(&preimage))
index e6f9346c762416b9c0e97f9023062f99e4542dbc..ba5d8afb1b04ba9d331c721fd9f730184fff2f23 100644 (file)
@@ -508,7 +508,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        struct ref *refs;
 
        connect_setup(transport, for_push, 0);
-       get_remote_heads(data->fd[0], &refs,
+       get_remote_heads(data->fd[0], NULL, 0, &refs,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
        data->got_remote_heads = 1;
 
@@ -519,11 +519,9 @@ static int fetch_refs_via_pack(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
-       struct string_list sought = STRING_LIST_INIT_DUP;
        const struct ref *refs;
        char *dest = xstrdup(transport->url);
        struct fetch_pack_args args;
-       int i;
        struct ref *refs_tmp = NULL;
 
        memset(&args, 0, sizeof(args));
@@ -537,18 +535,16 @@ static int fetch_refs_via_pack(struct transport *transport,
        args.no_progress = !transport->progress;
        args.depth = data->options.depth;
 
-       for (i = 0; i < nr_heads; i++)
-               string_list_append(&sought, to_fetch[i]->name);
-
        if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
-               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL);
+               get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, NULL);
                data->got_remote_heads = 1;
        }
 
        refs = fetch_pack(&args, data->fd, data->conn,
                          refs_tmp ? refs_tmp : transport->remote_refs,
-                         dest, &sought, &transport->pack_lockfile);
+                         dest, to_fetch, nr_heads,
+                         &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
        if (finish_connect(data->conn))
@@ -558,7 +554,6 @@ static int fetch_refs_via_pack(struct transport *transport,
 
        free_refs(refs_tmp);
 
-       string_list_clear(&sought, 0);
        free(dest);
        return (refs ? 0 : -1);
 }
@@ -800,7 +795,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
 
-               get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL);
+               get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL);
                data->got_remote_heads = 1;
        }
 
@@ -1133,6 +1128,8 @@ int transport_push(struct transport *transport,
                        match_flags |= MATCH_REFS_MIRROR;
                if (flags & TRANSPORT_PUSH_PRUNE)
                        match_flags |= MATCH_REFS_PRUNE;
+               if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
+                       match_flags |= MATCH_REFS_FOLLOW_TAGS;
 
                if (match_push_refs(local_refs, &remote_refs,
                                    refspec_nr, refspec, match_flags)) {
index a3450e97c0d0d94778a4b635efdd53b724eb70c2..93544627087bdaed1373efb2205fc6f94b89527c 100644 (file)
@@ -105,6 +105,7 @@ struct transport {
 #define TRANSPORT_PUSH_PRUNE 128
 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
 #define TRANSPORT_PUSH_NO_HOOK 512
+#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
index 30146a04f7a271ffaa033c0c6567ad1036f4bc49..bfa6279cc418278079bd95854adfe8d8301b6788 100644 (file)
@@ -26,6 +26,7 @@ static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<
 #define SHALLOW                (1u << 16)
 #define NOT_SHALLOW    (1u << 17)
 #define CLIENT_SHALLOW (1u << 18)
+#define HIDDEN_REF     (1u << 19)
 
 static unsigned long oldest_have;
 
@@ -33,6 +34,7 @@ static int multi_ack;
 static int no_done;
 static int use_thin_pack, use_ofs_delta, use_include_tag;
 static int no_progress, daemon_mode;
+static int allow_tip_sha1_in_want;
 static int shallow_nr;
 static struct object_array have_obj;
 static struct object_array want_obj;
@@ -42,7 +44,6 @@ static unsigned int timeout;
  * otherwise maximum packet size (up to 65520 bytes).
  */
 static int use_sideband;
-static int debug_fd;
 static int advertise_refs;
 static int stateless_rpc;
 
@@ -51,13 +52,6 @@ static void reset_timeout(void)
        alarm(timeout);
 }
 
-static int strip(char *line, int len)
-{
-       if (len && line[len-1] == '\n')
-               line[--len] = 0;
-       return len;
-}
-
 static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
 {
        if (use_sideband)
@@ -70,7 +64,8 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
                xwrite(fd, data, sz);
                return sz;
        }
-       return safe_write(fd, data, sz);
+       write_or_die(fd, data, sz);
+       return sz;
 }
 
 static FILE *pack_pipe = NULL;
@@ -325,9 +320,7 @@ static int got_sha1(char *hex, unsigned char *sha1)
        if (!has_sha1_file(sha1))
                return -1;
 
-       o = lookup_object(sha1);
-       if (!(o && o->parsed))
-               o = parse_object(sha1);
+       o = parse_object(sha1);
        if (!o)
                die("oops (%s)", sha1_to_hex(sha1));
        if (o->type == OBJ_COMMIT) {
@@ -415,7 +408,6 @@ static int ok_to_give_up(void)
 
 static int get_common_commits(void)
 {
-       static char line[1000];
        unsigned char sha1[20];
        char last_hex[41];
        int got_common = 0;
@@ -425,10 +417,10 @@ static int get_common_commits(void)
        save_commit_buffer = 0;
 
        for (;;) {
-               int len = packet_read_line(0, line, sizeof(line));
+               char *line = packet_read_line(0, NULL);
                reset_timeout();
 
-               if (!len) {
+               if (!line) {
                        if (multi_ack == 2 && got_common
                            && !got_other && ok_to_give_up()) {
                                sent_ready = 1;
@@ -447,7 +439,6 @@ static int get_common_commits(void)
                        got_other = 0;
                        continue;
                }
-               strip(line, len);
                if (!prefixcmp(line, "have ")) {
                        switch (got_sha1(line+5, sha1)) {
                        case -1: /* they have what we do not */
@@ -487,6 +478,12 @@ static int get_common_commits(void)
        }
 }
 
+static int is_our_ref(struct object *o)
+{
+       return o->flags &
+               ((allow_tip_sha1_in_want ? HIDDEN_REF : 0) | OUR_REF);
+}
+
 static void check_non_tip(void)
 {
        static const char *argv[] = {
@@ -523,7 +520,7 @@ static void check_non_tip(void)
                o = get_indexed_object(--i);
                if (!o)
                        continue;
-               if (!(o->flags & OUR_REF))
+               if (!is_our_ref(o))
                        continue;
                memcpy(namebuf + 1, sha1_to_hex(o->sha1), 40);
                if (write_in_full(cmd.in, namebuf, 42) < 0)
@@ -532,7 +529,7 @@ static void check_non_tip(void)
        namebuf[40] = '\n';
        for (i = 0; i < want_obj.nr; i++) {
                o = want_obj.objects[i].item;
-               if (o->flags & OUR_REF)
+               if (is_our_ref(o))
                        continue;
                memcpy(namebuf, sha1_to_hex(o->sha1), 40);
                if (write_in_full(cmd.in, namebuf, 41) < 0)
@@ -566,7 +563,7 @@ static void check_non_tip(void)
        /* Pick one of them (we know there at least is one) */
        for (i = 0; i < want_obj.nr; i++) {
                o = want_obj.objects[i].item;
-               if (!(o->flags & OUR_REF))
+               if (!is_our_ref(o))
                        die("git upload-pack: not our ref %s",
                            sha1_to_hex(o->sha1));
        }
@@ -575,36 +572,33 @@ static void check_non_tip(void)
 static void receive_needs(void)
 {
        struct object_array shallows = OBJECT_ARRAY_INIT;
-       static char line[1000];
-       int len, depth = 0;
+       int depth = 0;
        int has_non_tip = 0;
 
        shallow_nr = 0;
-       if (debug_fd)
-               write_str_in_full(debug_fd, "#S\n");
        for (;;) {
                struct object *o;
                const char *features;
                unsigned char sha1_buf[20];
-               len = packet_read_line(0, line, sizeof(line));
+               char *line = packet_read_line(0, NULL);
                reset_timeout();
-               if (!len)
+               if (!line)
                        break;
-               if (debug_fd)
-                       write_in_full(debug_fd, line, len);
 
                if (!prefixcmp(line, "shallow ")) {
                        unsigned char sha1[20];
                        struct object *object;
-                       if (get_sha1(line + 8, sha1))
+                       if (get_sha1_hex(line + 8, sha1))
                                die("invalid shallow line: %s", line);
                        object = parse_object(sha1);
                        if (!object)
                                die("did not find object for %s", line);
                        if (object->type != OBJ_COMMIT)
                                die("invalid shallow object %s", sha1_to_hex(sha1));
-                       object->flags |= CLIENT_SHALLOW;
-                       add_object_array(object, NULL, &shallows);
+                       if (!(object->flags & CLIENT_SHALLOW)) {
+                               object->flags |= CLIENT_SHALLOW;
+                               add_object_array(object, NULL, &shallows);
+                       }
                        continue;
                }
                if (!prefixcmp(line, "deepen ")) {
@@ -640,19 +634,17 @@ static void receive_needs(void)
                if (parse_feature_request(features, "include-tag"))
                        use_include_tag = 1;
 
-               o = lookup_object(sha1_buf);
+               o = parse_object(sha1_buf);
                if (!o)
                        die("git upload-pack: not our ref %s",
                            sha1_to_hex(sha1_buf));
                if (!(o->flags & WANTED)) {
                        o->flags |= WANTED;
-                       if (!(o->flags & OUR_REF))
+                       if (!is_our_ref(o))
                                has_non_tip = 1;
                        add_object_array(o, NULL, &want_obj);
                }
        }
-       if (debug_fd)
-               write_str_in_full(debug_fd, "#E\n");
 
        /*
         * We have sent all our refs already, and the other end
@@ -732,8 +724,10 @@ static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag
 {
        struct object *o = lookup_unknown_object(sha1);
 
-       if (ref_is_hidden(refname))
+       if (ref_is_hidden(refname)) {
+               o->flags |= HIDDEN_REF;
                return 1;
+       }
        if (!o)
                die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
        o->flags |= OUR_REF;
@@ -752,9 +746,10 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
                return 0;
 
        if (capabilities)
-               packet_write(1, "%s %s%c%s%s agent=%s\n",
+               packet_write(1, "%s %s%c%s%s%s agent=%s\n",
                             sha1_to_hex(sha1), refname_nons,
                             0, capabilities,
+                            allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
                             stateless_rpc ? " no-done" : "",
                             git_user_agent_sanitized());
        else
@@ -788,6 +783,8 @@ static void upload_pack(void)
 
 static int upload_pack_config(const char *var, const char *value, void *unused)
 {
+       if (!strcmp("uploadpack.allowtipsha1inwant", var))
+               allow_tip_sha1_in_want = git_config_bool(var, value);
        return parse_hide_refs_config(var, value, "uploadpack");
 }
 
@@ -843,8 +840,6 @@ int main(int argc, char **argv)
        if (is_repository_shallow())
                die("attempt to fetch/clone from a shallow repository");
        git_config(upload_pack_config, NULL);
-       if (getenv("GIT_DEBUG_SEND_PACK"))
-               debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK"));
        upload_pack();
        return 0;
 }
diff --git a/utf8.c b/utf8.c
index 8f6e84b7b3cf589f437a81ee81fd0228b2faf2f2..7f648574a550432e3160fcef178f894faf0bdd54 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -531,3 +531,42 @@ char *reencode_string(const char *in, const char *out_encoding, const char *in_e
        return out;
 }
 #endif
+
+/*
+ * Returns first character length in bytes for multi-byte `text` according to
+ * `encoding`.
+ *
+ * - The `text` pointer is updated to point at the next character.
+ * - When `remainder_p` is not NULL, on entry `*remainder_p` is how much bytes
+ *   we can consume from text, and on exit `*remainder_p` is reduced by returned
+ *   character length. Otherwise `text` is treated as limited by NUL.
+ */
+int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding)
+{
+       int chrlen;
+       const char *p = *text;
+       size_t r = (remainder_p ? *remainder_p : SIZE_MAX);
+
+       if (r < 1)
+               return 0;
+
+       if (is_encoding_utf8(encoding)) {
+               pick_one_utf8_char(&p, &r);
+
+               chrlen = p ? (p - *text)
+                          : 1 /* not valid UTF-8 -> raw byte sequence */;
+       }
+       else {
+               /*
+                * TODO use iconv to decode one char and obtain its chrlen
+                * for now, let's treat encodings != UTF-8 as one-byte
+                */
+               chrlen = 1;
+       }
+
+       *text += chrlen;
+       if (remainder_p)
+               *remainder_p -= chrlen;
+
+       return chrlen;
+}
diff --git a/utf8.h b/utf8.h
index 501b2bd9c4fd3fdce6dcbba1a3e3f7c0211d6c8f..1f8ecad1e88451c622de08ea14414021da64f402 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -22,4 +22,6 @@ char *reencode_string(const char *in, const char *out_encoding, const char *in_e
 #define reencode_string(a,b,c) NULL
 #endif
 
+int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
+
 #endif
index 960f448cffd9ffd4e53763dfed3669bc374b8620..b50f99a9361926d2116c85e3e90a132fb9dab742 100644 (file)
@@ -1,5 +1,15 @@
 #include "cache.h"
 
+static void check_pipe(int err)
+{
+       if (err == EPIPE) {
+               signal(SIGPIPE, SIG_DFL);
+               raise(SIGPIPE);
+               /* Should never happen, but just in case... */
+               exit(141);
+       }
+}
+
 /*
  * Some cases use stdio, but want to flush after the write
  * to get error handling (and to get better interactive
@@ -34,8 +44,7 @@ void maybe_flush_or_die(FILE *f, const char *desc)
                        return;
        }
        if (fflush(f)) {
-               if (errno == EPIPE)
-                       exit(0);
+               check_pipe(errno);
                die_errno("write failure on '%s'", desc);
        }
 }
@@ -50,8 +59,7 @@ void fsync_or_die(int fd, const char *msg)
 void write_or_die(int fd, const void *buf, size_t count)
 {
        if (write_in_full(fd, buf, count) < 0) {
-               if (errno == EPIPE)
-                       exit(0);
+               check_pipe(errno);
                die_errno("write error");
        }
 }
@@ -59,8 +67,7 @@ void write_or_die(int fd, const void *buf, size_t count)
 int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg)
 {
        if (write_in_full(fd, buf, count) < 0) {
-               if (errno == EPIPE)
-                       exit(0);
+               check_pipe(errno);
                fprintf(stderr, "%s: write error (%s)\n",
                        msg, strerror(errno));
                return 0;
index 75558177860d14120636859e2dcc29b48b72a721..cea8e55d8bc1d9117a6104c4181be9ff875c3ffa 100644 (file)
@@ -499,9 +499,14 @@ static void wt_status_collect_untracked(struct wt_status *s)
 {
        int i;
        struct dir_struct dir;
+       struct timeval t_begin;
 
        if (!s->show_untracked_files)
                return;
+
+       if (advice_status_u_option)
+               gettimeofday(&t_begin, NULL);
+
        memset(&dir, 0, sizeof(dir));
        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
                dir.flags |=
@@ -533,6 +538,14 @@ static void wt_status_collect_untracked(struct wt_status *s)
        }
 
        free(dir.entries);
+
+       if (advice_status_u_option) {
+               struct timeval t_end;
+               gettimeofday(&t_end, NULL);
+               s->untracked_in_ms =
+                       (uint64_t)t_end.tv_sec * 1000 + t_end.tv_usec / 1000 -
+                       ((uint64_t)t_begin.tv_sec * 1000 + t_begin.tv_usec / 1000);
+       }
 }
 
 void wt_status_collect(struct wt_status *s)
@@ -958,7 +971,7 @@ static void show_bisect_in_progress(struct wt_status *s,
 {
        if (state->branch)
                status_printf_ln(s, color,
-                                _("You are currently bisecting branch '%s'."),
+                                _("You are currently bisecting, started from branch '%s'."),
                                 state->branch);
        else
                status_printf_ln(s, color,
@@ -972,96 +985,164 @@ static void show_bisect_in_progress(struct wt_status *s,
 /*
  * Extract branch information from rebase/bisect
  */
-static void read_and_strip_branch(struct strbuf *sb,
-                                 const char **branch,
-                                 const char *path)
+static char *read_and_strip_branch(const char *path)
 {
+       struct strbuf sb = STRBUF_INIT;
        unsigned char sha1[20];
 
-       strbuf_reset(sb);
-       if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0)
-               return;
+       if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
+               goto got_nothing;
 
-       while (sb->len && sb->buf[sb->len - 1] == '\n')
-               strbuf_setlen(sb, sb->len - 1);
-       if (!sb->len)
-               return;
-       if (!prefixcmp(sb->buf, "refs/heads/"))
-               *branch = sb->buf + strlen("refs/heads/");
-       else if (!prefixcmp(sb->buf, "refs/"))
-               *branch = sb->buf;
-       else if (!get_sha1_hex(sb->buf, sha1)) {
+       while (&sb.len && sb.buf[sb.len - 1] == '\n')
+               strbuf_setlen(&sb, sb.len - 1);
+       if (!sb.len)
+               goto got_nothing;
+       if (!prefixcmp(sb.buf, "refs/heads/"))
+               strbuf_remove(&sb,0, strlen("refs/heads/"));
+       else if (!prefixcmp(sb.buf, "refs/"))
+               ;
+       else if (!get_sha1_hex(sb.buf, sha1)) {
                const char *abbrev;
                abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
-               strbuf_reset(sb);
-               strbuf_addstr(sb, abbrev);
-               *branch = sb->buf;
-       } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */
-               ;
+               strbuf_reset(&sb);
+               strbuf_addstr(&sb, abbrev);
+       } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
+               goto got_nothing;
        else                    /* bisect */
-               *branch = sb->buf;
+               ;
+       return strbuf_detach(&sb, NULL);
+
+got_nothing:
+       strbuf_release(&sb);
+       return NULL;
 }
 
-static void wt_status_print_state(struct wt_status *s)
+struct grab_1st_switch_cbdata {
+       int found;
+       struct strbuf buf;
+       unsigned char nsha1[20];
+};
+
+static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
+                          const char *email, unsigned long timestamp, int tz,
+                          const char *message, void *cb_data)
 {
-       const char *state_color = color(WT_STATUS_HEADER, s);
-       struct strbuf branch = STRBUF_INIT;
-       struct strbuf onto = STRBUF_INIT;
-       struct wt_status_state state;
-       struct stat st;
+       struct grab_1st_switch_cbdata *cb = cb_data;
+       const char *target = NULL, *end;
 
-       memset(&state, 0, sizeof(state));
+       if (prefixcmp(message, "checkout: moving from "))
+               return 0;
+       message += strlen("checkout: moving from ");
+       target = strstr(message, " to ");
+       if (!target)
+               return 0;
+       target += strlen(" to ");
+       strbuf_reset(&cb->buf);
+       hashcpy(cb->nsha1, nsha1);
+       for (end = target; *end && *end != '\n'; end++)
+               ;
+       strbuf_add(&cb->buf, target, end - target);
+       cb->found = 1;
+       return 1;
+}
+
+static void wt_status_get_detached_from(struct wt_status_state *state)
+{
+       struct grab_1st_switch_cbdata cb;
+       struct commit *commit;
+       unsigned char sha1[20];
+       char *ref = NULL;
+
+       strbuf_init(&cb.buf, 0);
+       if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
+               strbuf_release(&cb.buf);
+               return;
+       }
+
+       if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
+           /* sha1 is a commit? match without further lookup */
+           (!hashcmp(cb.nsha1, sha1) ||
+            /* perhaps sha1 is a tag, try to dereference to a commit */
+            ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
+             !hashcmp(cb.nsha1, commit->object.sha1)))) {
+               int ofs;
+               if (!prefixcmp(ref, "refs/tags/"))
+                       ofs = strlen("refs/tags/");
+               else if (!prefixcmp(ref, "refs/remotes/"))
+                       ofs = strlen("refs/remotes/");
+               else
+                       ofs = 0;
+               state->detached_from = xstrdup(ref + ofs);
+       } else
+               state->detached_from =
+                       xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
+       hashcpy(state->detached_sha1, cb.nsha1);
+
+       free(ref);
+       strbuf_release(&cb.buf);
+}
+
+void wt_status_get_state(struct wt_status_state *state,
+                        int get_detached_from)
+{
+       struct stat st;
 
        if (!stat(git_path("MERGE_HEAD"), &st)) {
-               state.merge_in_progress = 1;
+               state->merge_in_progress = 1;
        } else if (!stat(git_path("rebase-apply"), &st)) {
                if (!stat(git_path("rebase-apply/applying"), &st)) {
-                       state.am_in_progress = 1;
+                       state->am_in_progress = 1;
                        if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
-                               state.am_empty_patch = 1;
+                               state->am_empty_patch = 1;
                } else {
-                       state.rebase_in_progress = 1;
-                       read_and_strip_branch(&branch, &state.branch,
-                                             "rebase-apply/head-name");
-                       read_and_strip_branch(&onto, &state.onto,
-                                             "rebase-apply/onto");
+                       state->rebase_in_progress = 1;
+                       state->branch = read_and_strip_branch("rebase-apply/head-name");
+                       state->onto = read_and_strip_branch("rebase-apply/onto");
                }
        } else if (!stat(git_path("rebase-merge"), &st)) {
                if (!stat(git_path("rebase-merge/interactive"), &st))
-                       state.rebase_interactive_in_progress = 1;
+                       state->rebase_interactive_in_progress = 1;
                else
-                       state.rebase_in_progress = 1;
-               read_and_strip_branch(&branch, &state.branch,
-                                     "rebase-merge/head-name");
-               read_and_strip_branch(&onto, &state.onto,
-                                     "rebase-merge/onto");
+                       state->rebase_in_progress = 1;
+               state->branch = read_and_strip_branch("rebase-merge/head-name");
+               state->onto = read_and_strip_branch("rebase-merge/onto");
        } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
-               state.cherry_pick_in_progress = 1;
+               state->cherry_pick_in_progress = 1;
        }
        if (!stat(git_path("BISECT_LOG"), &st)) {
-               state.bisect_in_progress = 1;
-               read_and_strip_branch(&branch, &state.branch,
-                                     "BISECT_START");
+               state->bisect_in_progress = 1;
+               state->branch = read_and_strip_branch("BISECT_START");
        }
 
-       if (state.merge_in_progress)
-               show_merge_in_progress(s, &state, state_color);
-       else if (state.am_in_progress)
-               show_am_in_progress(s, &state, state_color);
-       else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
-               show_rebase_in_progress(s, &state, state_color);
-       else if (state.cherry_pick_in_progress)
-               show_cherry_pick_in_progress(s, &state, state_color);
-       if (state.bisect_in_progress)
-               show_bisect_in_progress(s, &state, state_color);
-       strbuf_release(&branch);
-       strbuf_release(&onto);
+       if (get_detached_from)
+               wt_status_get_detached_from(state);
+}
+
+static void wt_status_print_state(struct wt_status *s,
+                                 struct wt_status_state *state)
+{
+       const char *state_color = color(WT_STATUS_HEADER, s);
+       if (state->merge_in_progress)
+               show_merge_in_progress(s, state, state_color);
+       else if (state->am_in_progress)
+               show_am_in_progress(s, state, state_color);
+       else if (state->rebase_in_progress || state->rebase_interactive_in_progress)
+               show_rebase_in_progress(s, state, state_color);
+       else if (state->cherry_pick_in_progress)
+               show_cherry_pick_in_progress(s, state, state_color);
+       if (state->bisect_in_progress)
+               show_bisect_in_progress(s, state, state_color);
 }
 
 void wt_status_print(struct wt_status *s)
 {
        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
        const char *branch_status_color = color(WT_STATUS_HEADER, s);
+       struct wt_status_state state;
+
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state,
+                           s->branch && !strcmp(s->branch, "HEAD"));
 
        if (s->branch) {
                const char *on_what = _("On branch ");
@@ -1069,9 +1150,19 @@ void wt_status_print(struct wt_status *s)
                if (!prefixcmp(branch_name, "refs/heads/"))
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
-                       branch_name = "";
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
-                       on_what = _("Not currently on any branch.");
+                       if (state.detached_from) {
+                               unsigned char sha1[20];
+                               branch_name = state.detached_from;
+                               if (!get_sha1("HEAD", sha1) &&
+                                   !hashcmp(sha1, state.detached_sha1))
+                                       on_what = _("HEAD detached at ");
+                               else
+                                       on_what = _("HEAD detached from ");
+                       } else {
+                               branch_name = "";
+                               on_what = _("Not currently on any branch.");
+                       }
                }
                status_printf(s, color(WT_STATUS_HEADER, s), "");
                status_printf_more(s, branch_status_color, "%s", on_what);
@@ -1080,7 +1171,11 @@ void wt_status_print(struct wt_status *s)
                        wt_status_print_tracking(s);
        }
 
-       wt_status_print_state(s);
+       wt_status_print_state(s, &state);
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+
        if (s->is_initial) {
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
@@ -1100,6 +1195,18 @@ void wt_status_print(struct wt_status *s)
                wt_status_print_other(s, &s->untracked, _("Untracked files"), "add");
                if (s->show_ignored_files)
                        wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
+               if (advice_status_u_option && 2000 < s->untracked_in_ms) {
+                       status_printf_ln(s, GIT_COLOR_NORMAL, "");
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
+                                _("It took %.2f seconds to enumerate untracked files."
+                                  "  'status -uno'"),
+                                s->untracked_in_ms / 1000.0);
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
+                                _("may speed it up, but you have to be careful not"
+                                  " to forget to add"));
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
+                                _("new files yourself (see 'git help status')."));
+               }
        } else if (s->commitable)
                status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
                        advice_status_hints
index 81e1dcf84dbfbea76657d3b4c65e3e63890396e9..be7a016173487780fdd4d37e24b4a47fcb766454 100644 (file)
@@ -69,6 +69,7 @@ struct wt_status {
        struct string_list change;
        struct string_list untracked;
        struct string_list ignored;
+       uint32_t untracked_in_ms;
 };
 
 struct wt_status_state {
@@ -79,13 +80,16 @@ struct wt_status_state {
        int rebase_interactive_in_progress;
        int cherry_pick_in_progress;
        int bisect_in_progress;
-       const char *branch;
-       const char *onto;
+       char *branch;
+       char *onto;
+       char *detached_from;
+       unsigned char detached_sha1[20];
 };
 
 void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
+void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
diff --git a/zlib.c b/zlib.c
index 2b2c0c780e3fca6217c688298053a01aad724505..bbaa0815ed55a425e458773a58a0eb9aca8453eb 100644 (file)
--- a/zlib.c
+++ b/zlib.c
@@ -168,13 +168,8 @@ void git_deflate_init(git_zstream *strm, int level)
            strm->z.msg ? strm->z.msg : "no message");
 }
 
-void git_deflate_init_gzip(git_zstream *strm, int level)
+static void do_git_deflate_init(git_zstream *strm, int level, int windowBits)
 {
-       /*
-        * Use default 15 bits, +16 is to generate gzip header/trailer
-        * instead of the zlib wrapper.
-        */
-       const int windowBits = 15 + 16;
        int status;
 
        zlib_pre_call(strm);
@@ -188,6 +183,24 @@ void git_deflate_init_gzip(git_zstream *strm, int level)
            strm->z.msg ? strm->z.msg : "no message");
 }
 
+void git_deflate_init_gzip(git_zstream *strm, int level)
+{
+       /*
+        * Use default 15 bits, +16 is to generate gzip header/trailer
+        * instead of the zlib wrapper.
+        */
+       return do_git_deflate_init(strm, level, 15 + 16);
+}
+
+void git_deflate_init_raw(git_zstream *strm, int level)
+{
+       /*
+        * Use default 15 bits, negate the value to get raw compressed
+        * data without zlib header and trailer.
+        */
+       return do_git_deflate_init(strm, level, -15);
+}
+
 int git_deflate_abort(git_zstream *strm)
 {
        int status;