Merge branch 'js/re-running-failed-tests'
authorJunio C Hamano <gitster@pobox.com>
Fri, 3 Feb 2017 19:25:19 +0000 (11:25 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 3 Feb 2017 19:25:19 +0000 (11:25 -0800)
"make -C t failed" will now run only the tests that failed in the
previous run. This is usable only when prove is not use, and gives
a useless error message when run after "make clean", but otherwise
is serviceable.

* js/re-running-failed-tests:
t/Makefile: add a rule to re-run previously-failed tests

95 files changed:
.gitignore
.mailmap
.travis.yml
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/2.11.1.txt
Documentation/RelNotes/2.12.0.txt
Documentation/SubmittingPatches
Documentation/asciidoctor-extensions.rb [new file with mode: 0644]
Documentation/cat-texi.perl
Documentation/config.txt
Documentation/git-relink.txt [deleted file]
Documentation/git-tag.txt
Documentation/git-verify-tag.txt
Documentation/technical/api-hashmap.txt
Documentation/technical/api-in-core-index.txt [deleted file]
Documentation/texi.xsl [new file with mode: 0644]
Makefile
abspath.c
builtin.h
builtin/clone.c
builtin/commit.c
builtin/difftool.c [new file with mode: 0644]
builtin/fetch.c
builtin/fsck.c
builtin/push.c
builtin/read-tree.c
builtin/receive-pack.c
builtin/remote.c
builtin/show-ref.c
builtin/submodule--helper.c
builtin/tag.c
builtin/verify-tag.c
cache.h
color.c
command-list.txt
compat/qsort_s.c [new file with mode: 0644]
compat/winansi.c
contrib/coccinelle/xstrdup_or_null.cocci
contrib/convert-objects/convert-objects.c [deleted file]
contrib/convert-objects/git-convert-objects.txt [deleted file]
contrib/examples/git-difftool.perl [new file with mode: 0755]
exec_cmd.c
fsck.c
git-compat-util.h
git-difftool.perl [deleted file]
git-p4.py
git-relink.perl [deleted file]
git-submodule.sh
git.c
gpg-interface.h
graph.c
help.c
read-cache.c
ref-filter.c
ref-filter.h
remote.c
remote.h
run-command.c
sequencer.c
sequencer.h
setup.c
sha1_file.c
string-list.c
submodule-config.c
submodule.c
submodule.h
t/helper/test-string-list.c
t/perf/p0071-sort.sh [new file with mode: 0755]
t/t0001-init.sh
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1403-show-ref.sh
t/t1450-fsck.sh
t/t3404-rebase-interactive.sh
t/t3701-add-interactive.sh
t/t4026-color.sh
t/t4202-log.sh
t/t5505-remote.sh
t/t5531-deep-submodule-push.sh
t/t7004-tag.sh
t/t7030-verify-tag.sh
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7412-submodule-absorbgitdirs.sh
t/t7512-status-help.sh
t/t7800-difftool.sh
t/test-lib.sh
tag.c
transport.c
transport.h
unpack-trees.c
usage.c
worktree.c
wt-status.c
index 6722f78f9ab7e9647a3358a52e627f1c9e83f685..b1020b875fc006c81ed92ffb93e73876487c7c57 100644 (file)
 /git-rebase--merge
 /git-receive-pack
 /git-reflog
-/git-relink
 /git-remote
 /git-remote-http
 /git-remote-https
index 9c87a3840b240e9ac356eed1039b000b391ef19c..ab59b2fac613013ffcf5e041fc2c942f3f97cb1f 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -225,6 +225,7 @@ Steven Walter <stevenrwalter@gmail.com> <swalter@lexmark.com>
 Steven Walter <stevenrwalter@gmail.com> <swalter@lpdev.prtdev.lexmark.com>
 Sven Verdoolaege <skimo@kotnet.org> <Sven.Verdoolaege@cs.kuleuven.ac.be>
 Sven Verdoolaege <skimo@kotnet.org> <skimo@liacs.nl>
+SZEDER Gábor <szeder.dev@gmail.com> <szeder@ira.uka.de>
 Tay Ray Chuan <rctay89@gmail.com>
 Ted Percival <ted@midg3t.net> <ted.percival@quest.com>
 Theodore Ts'o <tytso@mit.edu>
index 3843967a692d1642e43f536d5e2652b566ca554d..9c63c8c3f6807841df13161f76d476deca0d94fd 100644 (file)
@@ -75,20 +75,12 @@ before_install:
       popd
       ;;
     osx)
-      brew_force_set_latest_binary_hash () {
-        FORMULA=$1
-        SHA=$(brew fetch --force $FORMULA 2>&1 | grep ^SHA256: | cut -d ' ' -f 2)
-        sed -E -i.bak "s/sha256 \"[0-9a-f]{64}\"/sha256 \"$SHA\"/g" \
-          "$(brew --repository homebrew/homebrew-binary)/$FORMULA.rb"
-      }
       brew update --quiet
-      brew tap homebrew/binary --quiet
-      brew_force_set_latest_binary_hash perforce
-      brew_force_set_latest_binary_hash perforce-server
       # Uncomment this if you want to run perf tests:
       # brew install gnu-time
-      brew install git-lfs perforce-server perforce gettext
+      brew install git-lfs gettext
       brew link --force gettext
+      brew install caskroom/cask/perforce
       ;;
     esac;
     echo "$(tput setaf 6)Perforce Server Version$(tput sgr0)";
index 4cd95da6b1e436c8764d51f17ea0446ca3bad73d..a4191aa3889000ed844678331e5fd7f9fc628ba4 100644 (file)
@@ -206,11 +206,38 @@ For C programs:
                x = 1;
        }
 
-   is frowned upon.  A gray area is when the statement extends
-   over a few lines, and/or you have a lengthy comment atop of
-   it.  Also, like in the Linux kernel, if there is a long list
-   of "else if" statements, it can make sense to add braces to
-   single line blocks.
+   is frowned upon. But there are a few exceptions:
+
+       - When the statement extends over a few lines (e.g., a while loop
+         with an embedded conditional, or a comment). E.g.:
+
+               while (foo) {
+                       if (x)
+                               one();
+                       else
+                               two();
+               }
+
+               if (foo) {
+                       /*
+                        * This one requires some explanation,
+                        * so we're better off with braces to make
+                        * it obvious that the indentation is correct.
+                        */
+                       doit();
+               }
+
+       - When there are multiple arms to a conditional and some of them
+         require braces, enclose even a single line block in braces for
+         consistency. E.g.:
+
+               if (foo) {
+                       doit();
+               } else {
+                       one();
+                       two();
+                       three();
+               }
 
  - We try to avoid assignments in the condition of an "if" statement.
 
index a9fb497b837dd39673ab7105774ad61ecbf67a6f..b5be2e2d3f5c8ade04fa81ddb5b24a8747838cdc 100644 (file)
@@ -120,6 +120,7 @@ INSTALL_INFO = install-info
 DOCBOOK2X_TEXI = docbook2x-texi
 DBLATEX = dblatex
 ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex
+DBLATEX_COMMON = -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
@@ -173,6 +174,16 @@ ifdef GNU_ROFF
 XMLTO_EXTRA += -m manpage-quote-apos.xsl
 endif
 
+ifdef USE_ASCIIDOCTOR
+ASCIIDOC = asciidoctor
+ASCIIDOC_CONF =
+ASCIIDOC_HTML = xhtml5
+ASCIIDOC_DOCBOOK = docbook45
+ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions
+ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
+DBLATEX_COMMON =
+endif
+
 SHELL_PATH ?= $(SHELL)
 # Shell quote;
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
@@ -368,13 +379,14 @@ user-manual.texi: user-manual.xml
 
 user-manual.pdf: user-manual.xml
        $(QUIET_DBLATEX)$(RM) $@+ $@ && \
-       $(DBLATEX) -o $@+ -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty $< && \
+       $(DBLATEX) -o $@+ $(DBLATEX_COMMON) $< && \
        mv $@+ $@
 
-gitman.texi: $(MAN_XML) cat-texi.perl
+gitman.texi: $(MAN_XML) cat-texi.perl texi.xsl
        $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
-       ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
-               --to-stdout $(xml) &&) true) > $@++ && \
+       ($(foreach xml,$(sort $(MAN_XML)),xsltproc -o $(xml)+ texi.xsl $(xml) && \
+               $(DOCBOOK2X_TEXI) --encoding=UTF-8 --to-stdout $(xml)+ && \
+               rm $(xml)+ &&) true) > $@++ && \
        $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
        rm $@++ && \
        mv $@+ $@
index 74b193f1a8169f559428cd5344a5a100d2faa8c1..9cd14c8197f9bb95e7c13ce7e243cb7a591ec000 100644 (file)
@@ -116,5 +116,53 @@ Fixes since v2.11
    will never come.  Teach the client side to notice this condition
    and abort the transfer.
 
+ * Some platforms no longer understand "latin-1" that is still seen in
+   the wild in e-mail headers; replace them with "iso-8859-1" that is
+   more widely known when conversion fails from/to it.
+
+ * Update the procedure to generate "tags" for developer support.
+
+ * Update the definition of the MacOSX test environment used by
+   TravisCI.
+
+ * A few git-svn updates.
+
+ * Compression setting for producing packfiles were spread across
+   three codepaths, one of which did not honor any configuration.
+   Unify these so that all of them honor core.compression and
+   pack.compression variables the same way.
+
+ * "git fast-import" sometimes mishandled while rebalancing notes
+   tree, which has been fixed.
+
+ * Recent update to the default abbreviation length that auto-scales
+   lacked documentation update, which has been corrected.
+
+ * Leakage of lockfiles in the config subsystem has been fixed.
+
+ * It is natural that "git gc --auto" may not attempt to pack
+   everything into a single pack, and there is no point in warning
+   when the user has configured the system to use the pack bitmap,
+   leading to disabling further "gc".
+
+ * "git archive" did not read the standard configuration files, and
+   failed to notice a file that is marked as binary via the userdiff
+   driver configuration.
+
+ * "git blame --porcelain" misidentified the "previous" <commit, path>
+   pair (aka "source") when contents came from two or more files.
+
+ * "git rebase -i" with a recent update started showing an incorrect
+   count when squashing more than 10 commits.
+
+ * "git <cmd> @{push}" on a detached HEAD used to segfault; it has
+   been corrected to error out with a message.
+
+ * Tighten a test to avoid mistaking an extended ERE regexp engine as
+   a PRE regexp engine.
+
+ * Typing ^C to pager, which usually does not kill it, killed Git and
+   took the pager down as a collateral damage in certain process-tree
+   structure.  This has been fixed.
 
 Also contains various documentation updates and code clean-ups.
index 17409fa14785e108aaf5a7f4ea9ed3f533e2b1cb..9c3f94ae2de8fab19acc430ebc0b491f2f425104 100644 (file)
@@ -11,8 +11,10 @@ Backward compatibility notes.
    is not scheduled to happen in the upcoming release (yet).
 
  * The historical argument order "git merge <msg> HEAD <commit>..."
-   has been deprecated for quite some time, and will be removed in the
-   upcoming release.
+   has been deprecated for quite some time, and will be removed in a
+   future release.
+
+ * An ancient script "git relink" has been removed.
 
 
 Updates since v2.11
@@ -64,7 +66,6 @@ UI, Workflows & Features
  * Some platforms no longer understand "latin-1" that is still seen in
    the wild in e-mail headers; replace them with "iso-8859-1" that is
    more widely known when conversion fails from/to it.
-   (merge df3755888b jc/latin-1 later to maint).
 
  * "git grep" has been taught to optionally recurse into submodules.
 
@@ -85,6 +86,30 @@ UI, Workflows & Features
    same release were present (e.g. when 2.0, 2.0-beta1, and 2.0-beta2
    are there and the code needs to compare 2.0-beta1 and 2.0-beta2).
 
+ * "git submodule push" learned "--recurse-submodules=only option to
+   push submodules out without pushing the top-level superproject.
+
+ * "git tag" and "git verify-tag" learned to put GPG verification
+   status in their "--format=<placeholders>" output format.
+
+ * An ancient repository conversion tool left in contrib/ has been
+   removed.
+
+ * "git show-ref HEAD" used with "--verify" because the user is not
+   interested in seeing refs/remotes/origin/HEAD, and used with
+   "--head" because the user does not want HEAD to be filtered out,
+   i.e. "git show-ref --head --verify HEAD", did not work as expected.
+
+ * "git submodule add" used to be confused and refused to add a
+   locally created repository; users can now use "--force" option
+   to add them.
+   (merge 619acfc78c sb/submodule-add-force later to maint).
+
+ * Some people feel the default set of colors used by "git log --graph"
+   rather limiting.  A mechanism to customize the set of colors has
+   been introduced.
+   (merge 512aba261a nd/log-graph-configurable-colors later to maint).
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -101,7 +126,6 @@ Performance, Internal Implementation, Development Support etc.
  * The character width table has been updated to match Unicode 9.0
 
  * Update the procedure to generate "tags" for developer support.
-   (merge 046e4c1c09 jk/make-tags-find-sources-tweak later to maint).
 
  * The codeflow of setting NOATIME and CLOEXEC on file descriptors Git
    opens has been simplified.
@@ -131,6 +155,15 @@ Performance, Internal Implementation, Development Support etc.
  * Adjust documentation to help AsciiDoctor render better while not
    breaking the rendering done by AsciiDoc.
 
+ * The sequencer machinery has been further enhanced so that a later
+   set of patches can start using it to reimplement "rebase -i".
+
+ * Update the definition of the MacOSX test environment used by
+   TravisCI.
+
+ * Rewrite a scripted porcelain "git difftool" in C.
+   (merge 94d3997ecc js/difftool-builtin later to maint).
+
 
 Also contains various documentation updates and code clean-ups.
 
@@ -148,7 +181,6 @@ notes for details).
 
  * "git svn" did not work well with path components that are "0", and
    some configuration variable it uses were not documented.
-   (merge ea9a93dcc2 ew/svn-fixes later to maint).
 
  * "git rev-parse --symbolic" failed with a more recent notation like
    "HEAD^-1" and "HEAD^!".
@@ -271,41 +303,32 @@ notes for details).
    three codepaths, one of which did not honor any configuration.
    Unify these so that all of them honor core.compression and
    pack.compression variables the same way.
-   (merge 8de7eeb54b jc/compression-config later to maint).
 
  * "git fast-import" sometimes mishandled while rebalancing notes
    tree, which has been fixed.
-   (merge 405d7f4af6 mh/fast-import-notes-fix-new later to maint).
 
  * Recent update to the default abbreviation length that auto-scales
    lacked documentation update, which has been corrected.
-   (merge 48d5014dd4 jc/abbrev-autoscale-config later to maint).
 
  * Leakage of lockfiles in the config subsystem has been fixed.
-   (merge c06fa62dfc nd/config-misc-fixes later to maint).
 
  * It is natural that "git gc --auto" may not attempt to pack
    everything into a single pack, and there is no point in warning
    when the user has configured the system to use the pack bitmap,
    leading to disabling further "gc".
-   (merge 1c409a705c dt/disable-bitmap-in-auto-gc later to maint).
 
  * "git archive" did not read the standard configuration files, and
    failed to notice a file that is marked as binary via the userdiff
    driver configuration.
-   (merge 965cba2e7e jk/archive-zip-userdiff-config later to maint).
 
  * "git blame --porcelain" misidentified the "previous" <commit, path>
    pair (aka "source") when contents came from two or more files.
-   (merge 4e76832984 jk/blame-fixes later to maint).
 
  * "git rebase -i" with a recent update started showing an incorrect
    count when squashing more than 10 commits.
-   (merge 356b8ecff1 jk/rebase-i-squash-count-fix later to maint).
 
  * "git <cmd> @{push}" on a detached HEAD used to segfault; it has
    been corrected to error out with a message.
-   (merge b10731f43d km/branch-get-push-while-detached later to maint).
 
  * Running "git add a/b" when "a" is a submodule correctly errored
    out, but without a meaningful error message.
@@ -314,7 +337,6 @@ notes for details).
  * Typing ^C to pager, which usually does not kill it, killed Git and
    took the pager down as a collateral damage in certain process-tree
    structure.  This has been fixed.
-   (merge 46df6906f3 jk/execv-dashed-external later to maint).
 
  * "git mergetool" without any pathspec on the command line that is
    run from a subdirectory became no-op in Git v2.11 by mistake, which
@@ -325,11 +347,77 @@ notes for details).
 
  * Tighten a test to avoid mistaking an extended ERE regexp engine as
    a PRE regexp engine.
-   (merge 7675c7bd01 jk/grep-e-could-be-extended-beyond-posix later to maint).
+
+ * An error message with an ASCII control character like '\r' in it
+   can alter the message to hide its early part, which is problematic
+   when a remote side gives such an error message that the local side
+   will relay with a "remote: " prefix.
+   (merge f290089879 jk/vreport-sanitize later to maint).
+
+ * "git fsck" inspects loose objects more carefully now.
+   (merge cce044df7f jk/loose-object-fsck later to maint).
+
+ * A crashing bug introduced in v2.11 timeframe has been found (it is
+   triggerable only in fast-import) and fixed.
+   (merge abd5a00268 jk/clear-delta-base-cache-fix later to maint).
+
+ * With an anticipatory tweak for remotes defined in ~/.gitconfig
+   (e.g. "remote.origin.prune" set to true, even though there may or
+   may not actually be "origin" remote defined in a particular Git
+   repository), "git remote rename" and other commands misinterpreted
+   and behaved as if such a non-existing remote actually existed.
+   (merge e459b073fb js/remote-rename-with-half-configured-remote later to maint).
+
+ * A few codepaths had to rely on a global variable when sorting
+   elements of an array because sort(3) API does not allow extra data
+   to be passed to the comparison function.  Use qsort_s() when
+   natively available, and a fallback implementation of it when not,
+   to eliminate the need, which is a prerequisite for making the
+   codepath reentrant.
+   (merge 83fc4d64fe rs/qsort-s later to maint).
+
+ * "git fsck --connectivity-check" was not working at all.
+   (merge a2b22854bd jk/fsck-connectivity-check-fix later to maint).
+
+ * After starting "git rebase -i", which first opens the user's editor
+   to edit the series of patches to apply, but before saving the
+   contents of that file, "git status" failed to show the current
+   state (i.e. you are in an interactive rebase session, but you have
+   applied no steps yet) correctly.
+   (merge df9ded4984 js/status-pre-rebase-i later to maint).
+
+ * Test tweak for FreeBSD where /usr/bin/unzip is unsuitable to run
+   our tests but /usr/local/bin/unzip is usable.
+   (merge d98b2c5fce js/unzip-in-usr-bin-workaround later to maint).
+
+ * "git p4" did not work well with multiple git-p4.mapUser entries on
+   Windows.
+   (merge c3c2b05776 gv/mingw-p4-mapuser later to maint).
+
+ * "git help" enumerates executable files in $PATH; the implementation
+   of "is this file executable?" on Windows has been optimized.
+   (merge c755015f79 hv/mingw-help-is-executable later to maint).
+
+ * Test tweaks for those who have default ACL in their git source tree
+   that interfere with the umask test.
+   (merge d549d21307 mm/reset-facl-before-umask-test later to maint).
+
+ * Names of the various hook scripts must be spelled exactly, but on
+   Windows, an .exe binary must be named with .exe suffix; notice
+   $GIT_DIR/hooks/<hookname>.exe as a valid <hookname> hook.
+   (merge 235be51fbe js/mingw-hooks-with-exe-suffix later to maint).
+
+ * Asciidoctor, an alternative reimplementation of AsciiDoc, still
+   needs some changes to work with documents meant to be formatted
+   with AsciiDoc.  "make USE_ASCIIDOCTOR=YesPlease" to use it out of
+   the box to document our pages is getting closer to reality.
+   (merge 55d2d812e4 bc/use-asciidoctor-opt later to maint).
 
  * Other minor doc, test and build updates and code cleanups.
    (merge f2627d9b19 sb/submodule-config-cleanup later to maint).
    (merge 384f1a167b sb/unpack-trees-cleanup later to maint).
-   (merge 3f05402ac0 ad/bisect-terms later to maint).
    (merge 874444b704 rh/diff-orderfile-doc later to maint).
-   (merge c68d2d7c2b ws/request-pull-code-cleanup later to maint).
+   (merge eafd5d9483 cw/doc-sign-off later to maint).
+   (merge 0aaad415bc rs/absolute-pathdup later to maint).
+   (merge 4432dd6b5b rs/receive-pack-cleanup later to maint).
+   (merge 540a398e9c sg/mailmap-self later to maint).
index 08352deaae4763791b70bd172682c1fe5380f7b4..3faf7eb884bc497e0823fda39734d5f5d47824ba 100644 (file)
@@ -216,12 +216,11 @@ that it will be postponed.
 Exception:  If your mailer is mangling patches then someone may ask
 you to re-send them using MIME, that is OK.
 
-Do not PGP sign your patch, at least for now.  Most likely, your
-maintainer or other people on the list would not have your PGP
-key and would not bother obtaining it anyway.  Your patch is not
-judged by who you are; a good patch from an unknown origin has a
-far better chance of being accepted than a patch from a known,
-respected origin that is done poorly or does incorrect things.
+Do not PGP sign your patch. Most likely, your maintainer or other people on the
+list would not have your PGP key and would not bother obtaining it anyway.
+Your patch is not judged by who you are; a good patch from an unknown origin
+has a far better chance of being accepted than a patch from a known, respected
+origin that is done poorly or does incorrect things.
 
 If you really really really really want to do a PGP signed
 patch, format it as "multipart/signed", not a text/plain message
@@ -246,7 +245,7 @@ patch.
      *2* The mailing list: git@vger.kernel.org
 
 
-(5) Sign your work
+(5) Certify your work by adding your "Signed-off-by: " line
 
 To improve tracking of who did what, we've borrowed the
 "sign-off" procedure from the Linux kernel project on patches
diff --git a/Documentation/asciidoctor-extensions.rb b/Documentation/asciidoctor-extensions.rb
new file mode 100644 (file)
index 0000000..ec83b49
--- /dev/null
@@ -0,0 +1,28 @@
+require 'asciidoctor'
+require 'asciidoctor/extensions'
+
+module Git
+  module Documentation
+    class LinkGitProcessor < Asciidoctor::Extensions::InlineMacroProcessor
+      use_dsl
+
+      named :chrome
+
+      def process(parent, target, attrs)
+        if parent.document.basebackend? 'html'
+          prefix = parent.document.attr('git-relative-html-prefix')
+          %(<a href="#{prefix}#{target}.html">#{target}(#{attrs[1]})</a>\n)
+        elsif parent.document.basebackend? 'docbook'
+          "<citerefentry>\n" \
+            "<refentrytitle>#{target}</refentrytitle>" \
+            "<manvolnum>#{attrs[1]}</manvolnum>\n" \
+          "</citerefentry>\n"
+        end
+      end
+    end
+  end
+end
+
+Asciidoctor::Extensions.register do
+  inline_macro Git::Documentation::LinkGitProcessor, :linkgit
+end
index 87437f8a95768595e040b8c4c1d48e5c29ada087..14d2f834151712fa84e13209a65f0f7121b49dd7 100755 (executable)
@@ -1,9 +1,12 @@
 #!/usr/bin/perl -w
 
+use strict;
+use warnings;
+
 my @menu = ();
 my $output = $ARGV[0];
 
-open TMP, '>', "$output.tmp";
+open my $tmp, '>', "$output.tmp";
 
 while (<STDIN>) {
        next if (/^\\input texinfo/../\@node Top/);
        if (s/^\@top (.*)/\@node $1,,,Top/) {
                push @menu, $1;
        }
-       s/\(\@pxref{\[(URLS|REMOTES)\]}\)//;
+       s/\(\@pxref\{\[(URLS|REMOTES)\]}\)//;
        s/\@anchor\{[^{}]*\}//g;
-       print TMP;
+       print $tmp $_;
 }
-close TMP;
+close $tmp;
 
-printf '\input texinfo
+print '\input texinfo
 @setfilename gitman.info
 @documentencoding UTF-8
 @dircategory Development
 @top Git Manual Pages
 @documentlanguage en
 @menu
-', $menu[0];
+';
 
 for (@menu) {
        print "* ${_}::\n";
 }
 print "\@end menu\n";
-open TMP, '<', "$output.tmp";
-while (<TMP>) {
+open $tmp, '<', "$output.tmp";
+while (<$tmp>) {
        print;
 }
-close TMP;
+close $tmp;
 print "\@bye\n";
 unlink "$output.tmp";
index af2ae4cc02af75c5cf9395e228ff8bbc6e390181..fc78139c21009f6991b8bed4c56509a8a2ab2c43 100644 (file)
@@ -170,6 +170,9 @@ The position of any attributes with respect to the colors
 be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`,
 `no-ul`, etc).
 +
+An empty color string produces no color effect at all. This can be used
+to avoid coloring specific elements without disabling color entirely.
++
 For git's pre-defined color slots, the attributes are meant to be reset
 at the beginning of each item in the colored output. So setting
 `color.decorate.branch` to `black` will paint that branch name in a
@@ -2036,6 +2039,10 @@ log.follow::
        i.e. it cannot be used to follow multiple files and does not work well
        on non-linear history.
 
+log.graphColors::
+       A list of colors, separated by commas, that can be used to draw
+       history lines in `git log --graph`.
+
 log.showRoot::
        If true, the initial commit will be shown as a big creation event.
        This is equivalent to a diff against an empty tree.
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
deleted file mode 100644 (file)
index 3b33c99..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-git-relink(1)
-=============
-
-NAME
-----
-git-relink - Hardlink common objects in local repositories
-
-SYNOPSIS
---------
-[verse]
-'git relink' [--safe] <dir>... <master_dir>
-
-DESCRIPTION
------------
-This will scan 1 or more object repositories and look for objects in common
-with a master repository. Objects not already hardlinked to the master
-repository will be replaced with a hardlink to the master repository.
-
-OPTIONS
--------
---safe::
-       Stops if two objects with the same hash exist but have different sizes.
-       Default is to warn and continue.
-
-<dir>::
-       Directories containing a .git/objects/ subdirectory.
-
-GIT
----
-Part of the linkgit:git[1] suite
index 5055a9682393409c1ed07e1e417015f46e46a5bc..8e70c5b6a4117034eb55969bd0a8d0702ae75d71 100644 (file)
@@ -15,7 +15,7 @@ SYNOPSIS
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
        [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
        [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
-'git tag' -v <tagname>...
+'git tag' -v [--format=<format>] <tagname>...
 
 DESCRIPTION
 -----------
index d590edcebd99a3c9321cfdc00499aa6996b78a5b..0b8075dad965f58b159bbe45f18d94bcc9f5ae60 100644 (file)
@@ -8,7 +8,7 @@ git-verify-tag - Check the GPG signature of tags
 SYNOPSIS
 --------
 [verse]
-'git verify-tag' <tag>...
+'git verify-tag' [--format=<format>] <tag>...
 
 DESCRIPTION
 -----------
index 28f5a8b71574916820cdb1f1a023e27cb45ed6e6..a3f020cd9ef6a5110a1ca439403e525377975b72 100644 (file)
@@ -188,7 +188,9 @@ Returns the removed entry, or NULL if not found.
 `void *hashmap_iter_next(struct hashmap_iter *iter)`::
 `void *hashmap_iter_first(struct hashmap *map, struct hashmap_iter *iter)`::
 
-       Used to iterate over all entries of a hashmap.
+       Used to iterate over all entries of a hashmap. Note that it is
+       not safe to add or remove entries to the hashmap while
+       iterating.
 +
 `hashmap_iter_init` initializes a `hashmap_iter` structure.
 +
diff --git a/Documentation/technical/api-in-core-index.txt b/Documentation/technical/api-in-core-index.txt
deleted file mode 100644 (file)
index adbdbf5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-in-core index API
-=================
-
-Talk about <read-cache.c> and <cache-tree.c>, things like:
-
-* cache -> the_index macros
-* read_index()
-* write_index()
-* ie_match_stat() and ie_modified(); how they are different and when to
-  use which.
-* index_name_pos()
-* remove_index_entry_at()
-* remove_file_from_index()
-* add_file_to_index()
-* add_index_entry()
-* refresh_index()
-* discard_index()
-* cache_tree_invalidate_path()
-* cache_tree_update()
-
-(JC, Linus)
diff --git a/Documentation/texi.xsl b/Documentation/texi.xsl
new file mode 100644 (file)
index 0000000..0f8ff07
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- texi.xsl:
+     convert refsection elements into refsect elements that docbook2texi can
+     understand -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<xsl:output method="xml"
+           encoding="UTF-8"
+           doctype-public="-//OASIS//DTD DocBook XML V4.5//EN"
+           doctype-system="http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" />
+
+<xsl:template match="//refsection">
+       <xsl:variable name="element">refsect<xsl:value-of select="count(ancestor-or-self::refsection)" /></xsl:variable>
+       <xsl:element name="{$element}">
+               <xsl:apply-templates select="@*|node()" />
+       </xsl:element>
+</xsl:template>
+
+<!-- Copy all other nodes through. -->
+<xsl:template match="node()|@*">
+       <xsl:copy>
+               <xsl:apply-templates select="@*|node()" />
+       </xsl:copy>
+</xsl:template>
+
+</xsl:stylesheet>
index 27afd0f378619c1960861d72df74a4b7cba7514f..8e4081e0619f8a9927a3a4c544048f576a194fd0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -250,6 +250,12 @@ all::
 # apostrophes to be ASCII so that cut&pasting examples to the shell
 # will work.
 #
+# Define USE_ASCIIDOCTOR to use Asciidoctor instead of AsciiDoc to build the
+# documentation.
+#
+# Define ASCIIDOCTOR_EXTENSIONS_LAB to point to the location of the Asciidoctor
+# Extensions Lab if you have it available.
+#
 # Define PERL_PATH to the path of your Perl binary (usually /usr/bin/perl).
 #
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
@@ -279,6 +285,9 @@ all::
 # is a simplified version of the merge sort used in glibc. This is
 # recommended if Git triggers O(n^2) behavior in your platform's qsort().
 #
+# Define HAVE_ISO_QSORT_S if your platform provides a qsort_s() that's
+# compatible with the one described in C11 Annex K.
+#
 # Define UNRELIABLE_FSTAT if your system's fstat does not return the same
 # information on a not yet closed file that lstat would return for the same
 # file after it was closed.
@@ -522,12 +531,10 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
 SCRIPT_PERL += git-cvsserver.perl
-SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
 SCRIPT_PERL += git-svn.perl
 
@@ -883,6 +890,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
@@ -1418,6 +1426,11 @@ ifdef INTERNAL_QSORT
        COMPAT_CFLAGS += -DINTERNAL_QSORT
        COMPAT_OBJS += compat/qsort.o
 endif
+ifdef HAVE_ISO_QSORT_S
+       COMPAT_CFLAGS += -DHAVE_ISO_QSORT_S
+else
+       COMPAT_OBJS += compat/qsort_s.o
+endif
 ifdef RUNTIME_PREFIX
        COMPAT_CFLAGS += -DRUNTIME_PREFIX
 endif
index fce40fddcc3b68a644fb93a698da8164a1d9b9cf..2f0c26e0e2cbee88aa671f3960d21720e4307aba 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -239,6 +239,13 @@ const char *absolute_path(const char *path)
        return sb.buf;
 }
 
+char *absolute_pathdup(const char *path)
+{
+       struct strbuf sb = STRBUF_INIT;
+       strbuf_add_absolute_path(&sb, path);
+       return strbuf_detach(&sb, NULL);
+}
+
 /*
  * Unlike prefix_path, this should be used if the named file does
  * not have to interact with index entry; i.e. name of a random file
index b9122bc5f497ea2030aa4d5348c44da1a3bd8995..67f80519dafc4875434437a34e438453bc2c78c4 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
index 5ef81927a629985aa4f0de6acba326f79747e7aa..3f63edbbf94fdf83307621ed822722e5c9f89321 100644 (file)
@@ -170,7 +170,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 
        strbuf_addstr(&path, repo);
        raw = get_repo_path_1(&path, is_bundle);
-       canon = raw ? xstrdup(absolute_path(raw)) : NULL;
+       canon = raw ? absolute_pathdup(raw) : NULL;
        strbuf_release(&path);
        return canon;
 }
@@ -894,7 +894,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        path = get_repo_path(repo_name, &is_bundle);
        if (path)
-               repo = xstrdup(absolute_path(repo_name));
+               repo = absolute_pathdup(repo_name);
        else if (!strchr(repo_name, ':'))
                die(_("repository '%s' does not exist"), repo_name);
        else
index 711f96cc438c0125290a587a0187702121bc461d..2de5f6cc6401b48852d8174edcbf4e149cbe51cf 100644 (file)
@@ -960,15 +960,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                return 0;
 
        if (use_editor) {
-               char index[PATH_MAX];
-               const char *env[2] = { NULL };
-               env[0] =  index;
-               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
-               if (launch_editor(git_path_commit_editmsg(), NULL, env)) {
+               struct argv_array env = ARGV_ARRAY_INIT;
+
+               argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file);
+               if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) {
                        fprintf(stderr,
                        _("Please supply the message using either -m or -F option.\n"));
                        exit(1);
                }
+               argv_array_clear(&env);
        }
 
        if (!no_verify &&
@@ -1525,12 +1525,10 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 static int run_rewrite_hook(const unsigned char *oldsha1,
                            const unsigned char *newsha1)
 {
-       /* oldsha1 SP newsha1 LF NUL */
-       static char buf[2*40 + 3];
        struct child_process proc = CHILD_PROCESS_INIT;
        const char *argv[3];
        int code;
-       size_t n;
+       struct strbuf sb = STRBUF_INIT;
 
        argv[0] = find_hook("post-rewrite");
        if (!argv[0])
@@ -1546,34 +1544,33 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
        code = start_command(&proc);
        if (code)
                return code;
-       n = snprintf(buf, sizeof(buf), "%s %s\n",
-                    sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+       strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
        sigchain_push(SIGPIPE, SIG_IGN);
-       write_in_full(proc.in, buf, n);
+       write_in_full(proc.in, sb.buf, sb.len);
        close(proc.in);
+       strbuf_release(&sb);
        sigchain_pop(SIGPIPE);
        return finish_command(&proc);
 }
 
 int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
 {
-       const char *hook_env[3] =  { NULL };
-       char index[PATH_MAX];
+       struct argv_array hook_env = ARGV_ARRAY_INIT;
        va_list args;
        int ret;
 
-       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
-       hook_env[0] = index;
+       argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
 
        /*
         * Let the hook know that no editor will be launched.
         */
        if (!editor_is_used)
-               hook_env[1] = "GIT_EDITOR=:";
+               argv_array_push(&hook_env, "GIT_EDITOR=:");
 
        va_start(args, name);
-       ret = run_hook_ve(hook_envname, args);
+       ret = run_hook_ve(hook_env.argv,name, args);
        va_end(args);
+       argv_array_clear(&hook_env);
 
        return ret;
 }
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644 (file)
index 0000000..b5e85ab
--- /dev/null
@@ -0,0 +1,692 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char *const builtin_difftool_usage[] = {
+       N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+       NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "diff.guitool")) {
+               diff_gui_tool = xstrdup(value);
+               return 0;
+       }
+
+       if (!strcmp(var, "difftool.trustexitcode")) {
+               trust_exit_code = git_config_bool(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+       const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+                           struct object_id *oid1, struct object_id *oid2,
+                           char *status)
+{
+       if (*p != ':')
+               return error("expected ':', got '%c'", *p);
+       *mode1 = (int)strtol(p + 1, &p, 8);
+       if (*p != ' ')
+               return error("expected ' ', got '%c'", *p);
+       *mode2 = (int)strtol(p + 1, &p, 8);
+       if (*p != ' ')
+               return error("expected ' ', got '%c'", *p);
+       if (get_oid_hex(++p, oid1))
+               return error("expected object ID, got '%s'", p + 1);
+       p += GIT_SHA1_HEXSZ;
+       if (*p != ' ')
+               return error("expected ' ', got '%c'", *p);
+       if (get_oid_hex(++p, oid2))
+               return error("expected object ID, got '%s'", p + 1);
+       p += GIT_SHA1_HEXSZ;
+       if (*p != ' ')
+               return error("expected ' ', got '%c'", *p);
+       *status = *++p;
+       if (!*status)
+               return error("missing status");
+       if (p[1] && !isdigit(p[1]))
+               return error("unexpected trailer: '%s'", p + 1);
+       return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+       strbuf_setlen(buf, base_len);
+       if (buf->len && buf->buf[buf->len - 1] != '/')
+               strbuf_addch(buf, '/');
+       strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+                      struct object_id *oid)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct stat st;
+       int use = 0;
+
+       strbuf_addstr(&buf, workdir);
+       add_path(&buf, buf.len, name);
+
+       if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+               struct object_id wt_oid;
+               int fd = open(buf.buf, O_RDONLY);
+
+               if (fd >= 0 &&
+                   !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+                       if (is_null_oid(oid)) {
+                               oidcpy(oid, &wt_oid);
+                               use = 1;
+                       } else if (!oidcmp(oid, &wt_oid))
+                               use = 1;
+               }
+       }
+
+       strbuf_release(&buf);
+
+       return use;
+}
+
+struct working_tree_entry {
+       struct hashmap_entry entry;
+       char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+                                 struct working_tree_entry *b, void *keydata)
+{
+       return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+       struct hashmap_entry entry;
+       char left[PATH_MAX], right[PATH_MAX];
+       const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+       return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+                             const char *content, int is_right)
+{
+       struct pair_entry *e, *existing;
+
+       FLEX_ALLOC_STR(e, path, path);
+       hashmap_entry_init(e, strhash(path));
+       existing = hashmap_get(map, e, NULL);
+       if (existing) {
+               free(e);
+               e = existing;
+       } else {
+               e->left[0] = e->right[0] = '\0';
+               hashmap_add(map, e);
+       }
+       strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
+}
+
+struct path_entry {
+       struct hashmap_entry entry;
+       char path[FLEX_ARRAY];
+};
+
+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+       return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+                         const char *workdir)
+{
+       struct child_process update_index = CHILD_PROCESS_INIT;
+       struct child_process diff_files = CHILD_PROCESS_INIT;
+       struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+       const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+               NULL, NULL
+       };
+       FILE *fp;
+
+       strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+       env[0] = index_env.buf;
+
+       argv_array_pushl(&update_index.args,
+                        "--git-dir", git_dir, "--work-tree", workdir,
+                        "update-index", "--really-refresh", "-q",
+                        "--unmerged", NULL);
+       update_index.no_stdin = 1;
+       update_index.no_stdout = 1;
+       update_index.no_stderr = 1;
+       update_index.git_cmd = 1;
+       update_index.use_shell = 0;
+       update_index.clean_on_exit = 1;
+       update_index.dir = workdir;
+       update_index.env = env;
+       /* Ignore any errors of update-index */
+       run_command(&update_index);
+
+       argv_array_pushl(&diff_files.args,
+                        "--git-dir", git_dir, "--work-tree", workdir,
+                        "diff-files", "--name-only", "-z", NULL);
+       diff_files.no_stdin = 1;
+       diff_files.git_cmd = 1;
+       diff_files.use_shell = 0;
+       diff_files.clean_on_exit = 1;
+       diff_files.out = -1;
+       diff_files.dir = workdir;
+       diff_files.env = env;
+       if (start_command(&diff_files))
+               die("could not obtain raw diff");
+       fp = xfdopen(diff_files.out, "r");
+       while (!strbuf_getline_nul(&buf, fp)) {
+               struct path_entry *entry;
+               FLEX_ALLOC_STR(entry, path, buf.buf);
+               hashmap_entry_init(entry, strhash(buf.buf));
+               hashmap_add(result, entry);
+       }
+       if (finish_command(&diff_files))
+               die("diff-files did not exit properly");
+       strbuf_release(&index_env);
+       strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+       struct strbuf buf = STRBUF_INIT;
+       strbuf_addstr(&buf, tmpdir);
+       remove_dir_recursively(&buf, 0);
+       if (exit_code)
+               warning(_("failed: %d"), exit_code);
+       exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+       switch (safe_create_leading_directories(path)) {
+               case SCLD_OK:
+               case SCLD_EXISTS:
+                       return 0;
+               default:
+                       return error(_("could not create leading directories "
+                                      "of '%s'"), path);
+       }
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
+                       int argc, const char **argv)
+{
+       char tmpdir[PATH_MAX];
+       struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+       struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+       struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+       struct strbuf wtdir = STRBUF_INIT;
+       size_t ldir_len, rdir_len, wtdir_len;
+       struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+       const char *workdir, *tmp;
+       int ret = 0, i;
+       FILE *fp;
+       struct hashmap working_tree_dups, submodules, symlinks2;
+       struct hashmap_iter iter;
+       struct pair_entry *entry;
+       enum object_type type;
+       unsigned long size;
+       struct index_state wtindex;
+       struct checkout lstate, rstate;
+       int rc, flags = RUN_GIT_CMD, err = 0;
+       struct child_process child = CHILD_PROCESS_INIT;
+       const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+       struct hashmap wt_modified, tmp_modified;
+       int indices_loaded = 0;
+
+       workdir = get_git_work_tree();
+
+       /* Setup temp directories */
+       tmp = getenv("TMPDIR");
+       xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+       if (!mkdtemp(tmpdir))
+               return error("could not create '%s'", tmpdir);
+       strbuf_addf(&ldir, "%s/left/", tmpdir);
+       strbuf_addf(&rdir, "%s/right/", tmpdir);
+       strbuf_addstr(&wtdir, workdir);
+       if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+               strbuf_addch(&wtdir, '/');
+       mkdir(ldir.buf, 0700);
+       mkdir(rdir.buf, 0700);
+
+       memset(&wtindex, 0, sizeof(wtindex));
+
+       memset(&lstate, 0, sizeof(lstate));
+       lstate.base_dir = ldir.buf;
+       lstate.base_dir_len = ldir.len;
+       lstate.force = 1;
+       memset(&rstate, 0, sizeof(rstate));
+       rstate.base_dir = rdir.buf;
+       rstate.base_dir_len = rdir.len;
+       rstate.force = 1;
+
+       ldir_len = ldir.len;
+       rdir_len = rdir.len;
+       wtdir_len = wtdir.len;
+
+       hashmap_init(&working_tree_dups,
+                    (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+       hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+       hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+       child.no_stdin = 1;
+       child.git_cmd = 1;
+       child.use_shell = 0;
+       child.clean_on_exit = 1;
+       child.dir = prefix;
+       child.out = -1;
+       argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+                        NULL);
+       for (i = 0; i < argc; i++)
+               argv_array_push(&child.args, argv[i]);
+       if (start_command(&child))
+               die("could not obtain raw diff");
+       fp = xfdopen(child.out, "r");
+
+       /* Build index info for left and right sides of the diff */
+       i = 0;
+       while (!strbuf_getline_nul(&info, fp)) {
+               int lmode, rmode;
+               struct object_id loid, roid;
+               char status;
+               const char *src_path, *dst_path;
+               size_t src_path_len, dst_path_len;
+
+               if (starts_with(info.buf, "::"))
+                       die(N_("combined diff formats('-c' and '--cc') are "
+                              "not supported in\n"
+                              "directory diff mode('-d' and '--dir-diff')."));
+
+               if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+                                    &status))
+                       break;
+               if (strbuf_getline_nul(&lpath, fp))
+                       break;
+               src_path = lpath.buf;
+               src_path_len = lpath.len;
+
+               i++;
+               if (status != 'C' && status != 'R') {
+                       dst_path = src_path;
+                       dst_path_len = src_path_len;
+               } else {
+                       if (strbuf_getline_nul(&rpath, fp))
+                               break;
+                       dst_path = rpath.buf;
+                       dst_path_len = rpath.len;
+               }
+
+               if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+                       strbuf_reset(&buf);
+                       strbuf_addf(&buf, "Subproject commit %s",
+                                   oid_to_hex(&loid));
+                       add_left_or_right(&submodules, src_path, buf.buf, 0);
+                       strbuf_reset(&buf);
+                       strbuf_addf(&buf, "Subproject commit %s",
+                                   oid_to_hex(&roid));
+                       if (!oidcmp(&loid, &roid))
+                               strbuf_addstr(&buf, "-dirty");
+                       add_left_or_right(&submodules, dst_path, buf.buf, 1);
+                       continue;
+               }
+
+               if (S_ISLNK(lmode)) {
+                       char *content = read_sha1_file(loid.hash, &type, &size);
+                       add_left_or_right(&symlinks2, src_path, content, 0);
+                       free(content);
+               }
+
+               if (S_ISLNK(rmode)) {
+                       char *content = read_sha1_file(roid.hash, &type, &size);
+                       add_left_or_right(&symlinks2, dst_path, content, 1);
+                       free(content);
+               }
+
+               if (lmode && status != 'C') {
+                       ce->ce_mode = lmode;
+                       oidcpy(&ce->oid, &loid);
+                       strcpy(ce->name, src_path);
+                       ce->ce_namelen = src_path_len;
+                       if (checkout_entry(ce, &lstate, NULL))
+                               return error("could not write '%s'", src_path);
+               }
+
+               if (rmode) {
+                       struct working_tree_entry *entry;
+
+                       /* Avoid duplicate working_tree entries */
+                       FLEX_ALLOC_STR(entry, path, dst_path);
+                       hashmap_entry_init(entry, strhash(dst_path));
+                       if (hashmap_get(&working_tree_dups, entry, NULL)) {
+                               free(entry);
+                               continue;
+                       }
+                       hashmap_add(&working_tree_dups, entry);
+
+                       if (!use_wt_file(workdir, dst_path, &roid)) {
+                               ce->ce_mode = rmode;
+                               oidcpy(&ce->oid, &roid);
+                               strcpy(ce->name, dst_path);
+                               ce->ce_namelen = dst_path_len;
+                               if (checkout_entry(ce, &rstate, NULL))
+                                       return error("could not write '%s'",
+                                                    dst_path);
+                       } else if (!is_null_oid(&roid)) {
+                               /*
+                                * Changes in the working tree need special
+                                * treatment since they are not part of the
+                                * index.
+                                */
+                               struct cache_entry *ce2 =
+                                       make_cache_entry(rmode, roid.hash,
+                                                        dst_path, 0, 0);
+
+                               add_index_entry(&wtindex, ce2,
+                                               ADD_CACHE_JUST_APPEND);
+
+                               add_path(&rdir, rdir_len, dst_path);
+                               if (ensure_leading_directories(rdir.buf))
+                                       return error("could not create "
+                                                    "directory for '%s'",
+                                                    dst_path);
+                               add_path(&wtdir, wtdir_len, dst_path);
+                               if (symlinks) {
+                                       if (symlink(wtdir.buf, rdir.buf)) {
+                                               ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+                                               goto finish;
+                                       }
+                               } else {
+                                       struct stat st;
+                                       if (stat(wtdir.buf, &st))
+                                               st.st_mode = 0644;
+                                       if (copy_file(rdir.buf, wtdir.buf,
+                                                     st.st_mode)) {
+                                               ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+                                               goto finish;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (finish_command(&child)) {
+               ret = error("error occurred running diff --raw");
+               goto finish;
+       }
+
+       if (!i)
+               return 0;
+
+       /*
+        * Changes to submodules require special treatment.This loop writes a
+        * temporary file to both the left and right directories to show the
+        * change in the recorded SHA1 for the submodule.
+        */
+       hashmap_iter_init(&submodules, &iter);
+       while ((entry = hashmap_iter_next(&iter))) {
+               if (*entry->left) {
+                       add_path(&ldir, ldir_len, entry->path);
+                       ensure_leading_directories(ldir.buf);
+                       write_file(ldir.buf, "%s", entry->left);
+               }
+               if (*entry->right) {
+                       add_path(&rdir, rdir_len, entry->path);
+                       ensure_leading_directories(rdir.buf);
+                       write_file(rdir.buf, "%s", entry->right);
+               }
+       }
+
+       /*
+        * Symbolic links require special treatment.The standard "git diff"
+        * shows only the link itself, not the contents of the link target.
+        * This loop replicates that behavior.
+        */
+       hashmap_iter_init(&symlinks2, &iter);
+       while ((entry = hashmap_iter_next(&iter))) {
+               if (*entry->left) {
+                       add_path(&ldir, ldir_len, entry->path);
+                       ensure_leading_directories(ldir.buf);
+                       write_file(ldir.buf, "%s", entry->left);
+               }
+               if (*entry->right) {
+                       add_path(&rdir, rdir_len, entry->path);
+                       ensure_leading_directories(rdir.buf);
+                       write_file(rdir.buf, "%s", entry->right);
+               }
+       }
+
+       strbuf_release(&buf);
+
+       strbuf_setlen(&ldir, ldir_len);
+       helper_argv[1] = ldir.buf;
+       strbuf_setlen(&rdir, rdir_len);
+       helper_argv[2] = rdir.buf;
+
+       if (extcmd) {
+               helper_argv[0] = extcmd;
+               flags = 0;
+       } else
+               setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+       rc = run_command_v_opt(helper_argv, flags);
+
+       /*
+        * If the diff includes working copy files and those
+        * files were modified during the diff, then the changes
+        * should be copied back to the working tree.
+        * Do not copy back files when symlinks are used and the
+        * external tool did not replace the original link with a file.
+        *
+        * These hashes are loaded lazily since they aren't needed
+        * in the common case of --symlinks and the difftool updating
+        * files through the symlink.
+        */
+       hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+                    wtindex.cache_nr);
+       hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+                    wtindex.cache_nr);
+
+       for (i = 0; i < wtindex.cache_nr; i++) {
+               struct hashmap_entry dummy;
+               const char *name = wtindex.cache[i]->name;
+               struct stat st;
+
+               add_path(&rdir, rdir_len, name);
+               if (lstat(rdir.buf, &st))
+                       continue;
+
+               if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+                       continue;
+
+               if (!indices_loaded) {
+                       static struct lock_file lock;
+                       strbuf_reset(&buf);
+                       strbuf_addf(&buf, "%s/wtindex", tmpdir);
+                       if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+                           write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+                               ret = error("could not write %s", buf.buf);
+                               rollback_lock_file(&lock);
+                               goto finish;
+                       }
+                       changed_files(&wt_modified, buf.buf, workdir);
+                       strbuf_setlen(&rdir, rdir_len);
+                       changed_files(&tmp_modified, buf.buf, rdir.buf);
+                       add_path(&rdir, rdir_len, name);
+                       indices_loaded = 1;
+               }
+
+               hashmap_entry_init(&dummy, strhash(name));
+               if (hashmap_get(&tmp_modified, &dummy, name)) {
+                       add_path(&wtdir, wtdir_len, name);
+                       if (hashmap_get(&wt_modified, &dummy, name)) {
+                               warning(_("both files modified: '%s' and '%s'."),
+                                       wtdir.buf, rdir.buf);
+                               warning(_("working tree file has been left."));
+                               warning("%s", "");
+                               err = 1;
+                       } else if (unlink(wtdir.buf) ||
+                                  copy_file(wtdir.buf, rdir.buf, st.st_mode))
+                               warning_errno(_("could not copy '%s' to '%s'"),
+                                             rdir.buf, wtdir.buf);
+               }
+       }
+
+       if (err) {
+               warning(_("temporary files exist in '%s'."), tmpdir);
+               warning(_("you may want to cleanup or recover these."));
+               exit(1);
+       } else
+               exit_cleanup(tmpdir, rc);
+
+finish:
+       free(ce);
+       strbuf_release(&ldir);
+       strbuf_release(&rdir);
+       strbuf_release(&wtdir);
+       strbuf_release(&buf);
+
+       return ret;
+}
+
+static int run_file_diff(int prompt, const char *prefix,
+                        int argc, const char **argv)
+{
+       struct argv_array args = ARGV_ARRAY_INIT;
+       const char *env[] = {
+               "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+               NULL
+       };
+       int ret = 0, i;
+
+       if (prompt > 0)
+               env[2] = "GIT_DIFFTOOL_PROMPT=true";
+       else if (!prompt)
+               env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+
+       argv_array_push(&args, "diff");
+       for (i = 0; i < argc; i++)
+               argv_array_push(&args, argv[i]);
+       ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+       exit(ret);
+}
+
+int cmd_difftool(int argc, const char **argv, const char *prefix)
+{
+       int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+           tool_help = 0;
+       static char *difftool_cmd = NULL, *extcmd = NULL;
+       struct option builtin_difftool_options[] = {
+               OPT_BOOL('g', "gui", &use_gui_tool,
+                        N_("use `diff.guitool` instead of `diff.tool`")),
+               OPT_BOOL('d', "dir-diff", &dir_diff,
+                        N_("perform a full-directory diff")),
+               { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+                       N_("do not prompt before launching a diff tool"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+               { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+                       NULL, 1 },
+               OPT_BOOL(0, "symlinks", &symlinks,
+                        N_("use symlinks in dir-diff mode")),
+               OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+                          N_("use the specified diff tool")),
+               OPT_BOOL(0, "tool-help", &tool_help,
+                        N_("print a list of diff tools that may be used with "
+                           "`--tool`")),
+               OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+                        N_("make 'git-difftool' exit when an invoked diff "
+                           "tool returns a non - zero exit code")),
+               OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+                          N_("specify a custom command for viewing diffs")),
+               OPT_END()
+       };
+
+       /* NEEDSWORK: once we no longer spawn anything, remove this */
+       setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+       setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+
+       git_config(difftool_config, NULL);
+       symlinks = has_symlinks;
+
+       argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+                            builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+                            PARSE_OPT_KEEP_DASHDASH);
+
+       if (tool_help)
+               return print_tool_help();
+
+       if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+               setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+       else if (difftool_cmd) {
+               if (*difftool_cmd)
+                       setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+               else
+                       die(_("no <tool> given for --tool=<tool>"));
+       }
+
+       if (extcmd) {
+               if (*extcmd)
+                       setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+               else
+                       die(_("no <cmd> given for --extcmd=<cmd>"));
+       }
+
+       setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+              trust_exit_code ? "true" : "false", 1);
+
+       /*
+        * In directory diff mode, 'git-difftool--helper' is called once
+        * to compare the a / b directories. In file diff mode, 'git diff'
+        * will invoke a separate instance of 'git-difftool--helper' for
+        * each file that changed.
+        */
+       if (dir_diff)
+               return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
+       return run_file_diff(prompt, prefix, argc, argv);
+}
index f1570e346434e8695e80ba568c28b1363b2c0cb5..b5ad09d0460cc7a51f35a56a4304e6a1fb26d26e 100644 (file)
@@ -1177,7 +1177,7 @@ static int add_remote_or_group(const char *name, struct string_list *list)
        git_config(get_remote_group, &g);
        if (list->nr == prev_nr) {
                struct remote *remote = remote_get(name);
-               if (!remote_is_configured(remote))
+               if (!remote_is_configured(remote, 0))
                        return 0;
                string_list_append(list, remote->name);
        }
index f01b81eebfebc1c221e81c44e3f14a4e2781f0a7..1a5caccd0f5ee3fa56af06e8f536271f58312665 100644 (file)
@@ -56,6 +56,23 @@ static const char *describe_object(struct object *obj)
        return buf.buf;
 }
 
+static const char *printable_type(struct object *obj)
+{
+       const char *ret;
+
+       if (obj->type == OBJ_NONE) {
+               enum object_type type = sha1_object_info(obj->oid.hash, NULL);
+               if (type > 0)
+                       object_as_type(obj, type, 0);
+       }
+
+       ret = typename(obj->type);
+       if (!ret)
+               ret = "unknown";
+
+       return ret;
+}
+
 static int fsck_config(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "fsck.skiplist") == 0) {
@@ -83,7 +100,7 @@ static void objreport(struct object *obj, const char *msg_type,
                        const char *err)
 {
        fprintf(stderr, "%s in %s %s: %s\n",
-               msg_type, typename(obj->type), describe_object(obj), err);
+               msg_type, printable_type(obj), describe_object(obj), err);
 }
 
 static int objerror(struct object *obj, const char *err)
@@ -114,7 +131,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
        if (!obj) {
                /* ... these references to parent->fld are safe here */
                printf("broken link from %7s %s\n",
-                          typename(parent->type), describe_object(parent));
+                          printable_type(parent), describe_object(parent));
                printf("broken link from %7s %s\n",
                           (type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
                errors_found |= ERROR_REACHABLE;
@@ -131,9 +148,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
        if (!(obj->flags & HAS_OBJ)) {
                if (parent && !has_object_file(&obj->oid)) {
                        printf("broken link from %7s %s\n",
-                                typename(parent->type), describe_object(parent));
+                                printable_type(parent), describe_object(parent));
                        printf("              to %7s %s\n",
-                                typename(obj->type), describe_object(obj));
+                                printable_type(obj), describe_object(obj));
                        errors_found |= ERROR_REACHABLE;
                }
                return 1;
@@ -205,9 +222,7 @@ static void check_reachable_object(struct object *obj)
        if (!(obj->flags & HAS_OBJ)) {
                if (has_sha1_pack(obj->oid.hash))
                        return; /* it is in pack - forget about it */
-               if (connectivity_only && has_object_file(&obj->oid))
-                       return;
-               printf("missing %s %s\n", typename(obj->type),
+               printf("missing %s %s\n", printable_type(obj),
                        describe_object(obj));
                errors_found |= ERROR_REACHABLE;
                return;
@@ -225,7 +240,7 @@ static void check_unreachable_object(struct object *obj)
         * to complain about it being unreachable (since it does
         * not exist).
         */
-       if (!obj->parsed)
+       if (!(obj->flags & HAS_OBJ))
                return;
 
        /*
@@ -233,7 +248,7 @@ static void check_unreachable_object(struct object *obj)
         * since this is something that is prunable.
         */
        if (show_unreachable) {
-               printf("unreachable %s %s\n", typename(obj->type),
+               printf("unreachable %s %s\n", printable_type(obj),
                        describe_object(obj));
                return;
        }
@@ -252,7 +267,7 @@ static void check_unreachable_object(struct object *obj)
         */
        if (!obj->used) {
                if (show_dangling)
-                       printf("dangling %s %s\n", typename(obj->type),
+                       printf("dangling %s %s\n", printable_type(obj),
                               describe_object(obj));
                if (write_lost_and_found) {
                        char *filename = git_pathdup("lost-found/%s/%s",
@@ -326,7 +341,7 @@ static int fsck_obj(struct object *obj)
 
        if (verbose)
                fprintf(stderr, "Checking %s %s\n",
-                       typename(obj->type), describe_object(obj));
+                       printable_type(obj), describe_object(obj));
 
        if (fsck_walk(obj, NULL, &fsck_obj_options))
                objerror(obj, "broken links");
@@ -352,7 +367,7 @@ static int fsck_obj(struct object *obj)
                struct tag *tag = (struct tag *) obj;
 
                if (show_tags && tag->tagged) {
-                       printf("tagged %s %s", typename(tag->tagged->type),
+                       printf("tagged %s %s", printable_type(tag->tagged),
                                describe_object(tag->tagged));
                        printf(" (%s) in %s\n", tag->tag,
                                describe_object(&tag->object));
@@ -362,18 +377,6 @@ static int fsck_obj(struct object *obj)
        return 0;
 }
 
-static int fsck_sha1(const unsigned char *sha1)
-{
-       struct object *obj = parse_object(sha1);
-       if (!obj) {
-               errors_found |= ERROR_OBJECT;
-               return error("%s: object corrupt or missing",
-                            sha1_to_hex(sha1));
-       }
-       obj->flags |= HAS_OBJ;
-       return fsck_obj(obj);
-}
-
 static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
                           unsigned long size, void *buffer, int *eaten)
 {
@@ -400,7 +403,7 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
 
        if (!is_null_sha1(sha1)) {
                obj = lookup_object(sha1);
-               if (obj) {
+               if (obj && (obj->flags & HAS_OBJ)) {
                        if (timestamp && name_objects)
                                add_decoration(fsck_walk_options.object_names,
                                        obj,
@@ -488,9 +491,41 @@ static void get_default_heads(void)
        }
 }
 
+static struct object *parse_loose_object(const unsigned char *sha1,
+                                        const char *path)
+{
+       struct object *obj;
+       void *contents;
+       enum object_type type;
+       unsigned long size;
+       int eaten;
+
+       if (read_loose_object(path, sha1, &type, &size, &contents) < 0)
+               return NULL;
+
+       if (!contents && type != OBJ_BLOB)
+               die("BUG: read_loose_object streamed a non-blob");
+
+       obj = parse_object_buffer(sha1, type, size, contents, &eaten);
+
+       if (!eaten)
+               free(contents);
+       return obj;
+}
+
 static int fsck_loose(const unsigned char *sha1, const char *path, void *data)
 {
-       if (fsck_sha1(sha1))
+       struct object *obj = parse_loose_object(sha1, path);
+
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               error("%s: object corrupt or missing: %s",
+                     sha1_to_hex(sha1), path);
+               return 0; /* keep checking other objects */
+       }
+
+       obj->flags = HAS_OBJ;
+       if (fsck_obj(obj))
                errors_found |= ERROR_OBJECT;
        return 0;
 }
@@ -584,6 +619,29 @@ static int fsck_cache_tree(struct cache_tree *it)
        return err;
 }
 
+static void mark_object_for_connectivity(const unsigned char *sha1)
+{
+       struct object *obj = lookup_unknown_object(sha1);
+       obj->flags |= HAS_OBJ;
+}
+
+static int mark_loose_for_connectivity(const unsigned char *sha1,
+                                      const char *path,
+                                      void *data)
+{
+       mark_object_for_connectivity(sha1);
+       return 0;
+}
+
+static int mark_packed_for_connectivity(const unsigned char *sha1,
+                                       struct packed_git *pack,
+                                       uint32_t pos,
+                                       void *data)
+{
+       mark_object_for_connectivity(sha1);
+       return 0;
+}
+
 static char const * const fsck_usage[] = {
        N_("git fsck [<options>] [<object>...]"),
        NULL
@@ -640,38 +698,41 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        git_config(fsck_config, NULL);
 
        fsck_head_link();
-       if (!connectivity_only) {
+       if (connectivity_only) {
+               for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
+               for_each_packed_object(mark_packed_for_connectivity, NULL, 0);
+       } else {
                fsck_object_dir(get_object_directory());
 
                prepare_alt_odb();
                for (alt = alt_odb_list; alt; alt = alt->next)
                        fsck_object_dir(alt->path);
-       }
 
-       if (check_full) {
-               struct packed_git *p;
-               uint32_t total = 0, count = 0;
-               struct progress *progress = NULL;
+               if (check_full) {
+                       struct packed_git *p;
+                       uint32_t total = 0, count = 0;
+                       struct progress *progress = NULL;
 
-               prepare_packed_git();
+                       prepare_packed_git();
 
-               if (show_progress) {
+                       if (show_progress) {
+                               for (p = packed_git; p; p = p->next) {
+                                       if (open_pack_index(p))
+                                               continue;
+                                       total += p->num_objects;
+                               }
+
+                               progress = start_progress(_("Checking objects"), total);
+                       }
                        for (p = packed_git; p; p = p->next) {
-                               if (open_pack_index(p))
-                                       continue;
-                               total += p->num_objects;
+                               /* verify gives error messages itself */
+                               if (verify_pack(p, fsck_obj_buffer,
+                                               progress, count))
+                                       errors_found |= ERROR_PACK;
+                               count += p->num_objects;
                        }
-
-                       progress = start_progress(_("Checking objects"), total);
+                       stop_progress(&progress);
                }
-               for (p = packed_git; p; p = p->next) {
-                       /* verify gives error messages itself */
-                       if (verify_pack(p, fsck_obj_buffer,
-                                       progress, count))
-                               errors_found |= ERROR_PACK;
-                       count += p->num_objects;
-               }
-               stop_progress(&progress);
        }
 
        heads = 0;
@@ -681,9 +742,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                if (!get_sha1(arg, sha1)) {
                        struct object *obj = lookup_object(sha1);
 
-                       /* Error is printed by lookup_object(). */
-                       if (!obj)
+                       if (!obj || !(obj->flags & HAS_OBJ)) {
+                               error("%s: object missing", sha1_to_hex(sha1));
+                               errors_found |= ERROR_OBJECT;
                                continue;
+                       }
 
                        obj->used = 1;
                        if (name_objects)
@@ -694,6 +757,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                        continue;
                }
                error("invalid parameter: expected sha1, got '%s'", arg);
+               errors_found |= ERROR_OBJECT;
        }
 
        /*
@@ -701,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
         * default ones from .git/refs. We also consider the index file
         * in this case (ie this implies --cache).
         */
-       if (!heads) {
+       if (!argc) {
                get_default_heads();
                keep_cache_objects = 1;
        }
index 9307ad56a9fb91455eaf06371382b1431d115598..5c22e9f2e56b7d8049890e44f4c6fb63b16f8357 100644 (file)
@@ -568,6 +568,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
        else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
                flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
+       else if (recurse_submodules == RECURSE_SUBMODULES_ONLY)
+               flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY;
 
        if (tags)
                add_refspec("refs/tags/*");
index fa6edb35b21cede4425746d84b41c8bd9e31e1dc..8ba64bc785670590897638c8e58172c74556189d 100644 (file)
@@ -109,34 +109,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"),
                  N_("write resulting index to <file>"),
                  PARSE_OPT_NONEG, index_output_cb },
-               OPT_SET_INT(0, "empty", &read_empty,
-                           N_("only empty the index"), 1),
+               OPT_BOOL(0, "empty", &read_empty,
+                           N_("only empty the index")),
                OPT__VERBOSE(&opts.verbose_update, N_("be verbose")),
                OPT_GROUP(N_("Merging")),
-               OPT_SET_INT('m', NULL, &opts.merge,
-                           N_("perform a merge in addition to a read"), 1),
-               OPT_SET_INT(0, "trivial", &opts.trivial_merges_only,
-                           N_("3-way merge if no file level merging required"), 1),
-               OPT_SET_INT(0, "aggressive", &opts.aggressive,
-                           N_("3-way merge in presence of adds and removes"), 1),
-               OPT_SET_INT(0, "reset", &opts.reset,
-                           N_("same as -m, but discard unmerged entries"), 1),
+               OPT_BOOL('m', NULL, &opts.merge,
+                        N_("perform a merge in addition to a read")),
+               OPT_BOOL(0, "trivial", &opts.trivial_merges_only,
+                        N_("3-way merge if no file level merging required")),
+               OPT_BOOL(0, "aggressive", &opts.aggressive,
+                        N_("3-way merge in presence of adds and removes")),
+               OPT_BOOL(0, "reset", &opts.reset,
+                        N_("same as -m, but discard unmerged entries")),
                { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"),
                  N_("read the tree into the index under <subdirectory>/"),
                  PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP },
-               OPT_SET_INT('u', NULL, &opts.update,
-                           N_("update working tree with merge result"), 1),
+               OPT_BOOL('u', NULL, &opts.update,
+                        N_("update working tree with merge result")),
                { OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
                  N_("gitignore"),
                  N_("allow explicitly ignored files to be overwritten"),
                  PARSE_OPT_NONEG, exclude_per_directory_cb },
-               OPT_SET_INT('i', NULL, &opts.index_only,
-                           N_("don't check the working tree after merging"), 1),
+               OPT_BOOL('i', NULL, &opts.index_only,
+                        N_("don't check the working tree after merging")),
                OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")),
-               OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
-                           N_("skip applying sparse checkout filter"), 1),
-               OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
-                           N_("debug unpack-trees"), 1),
+               OPT_BOOL(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
+                        N_("skip applying sparse checkout filter")),
+               OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
+                        N_("debug unpack-trees")),
                OPT_END()
        };
 
index 6b97cbdbe9444d72d575c149678e4b609a154842..1dbb8a069225be1e9d9fe27ad4b83a8bd66ca511 100644 (file)
@@ -1942,8 +1942,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                run_receive_hook(commands, "post-receive", 1,
                                 &push_options);
                run_update_post_hook(commands);
-               if (push_options.nr)
-                       string_list_clear(&push_options, 0);
+               string_list_clear(&push_options, 0);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {
                                "gc", "--auto", "--quiet", NULL,
index e52cf3925b2388008221de6f7cbedeecd6cdd010..5339ed6ad17bb0c83e05d3e60d654c4f0632ee50 100644 (file)
@@ -186,7 +186,7 @@ static int add(int argc, const char **argv)
        url = argv[1];
 
        remote = remote_get(name);
-       if (remote_is_configured(remote))
+       if (remote_is_configured(remote, 1))
                die(_("remote %s already exists."), name);
 
        strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
@@ -618,14 +618,14 @@ static int mv(int argc, const char **argv)
        rename.remote_branches = &remote_branches;
 
        oldremote = remote_get(rename.old);
-       if (!remote_is_configured(oldremote))
+       if (!remote_is_configured(oldremote, 1))
                die(_("No such remote: %s"), rename.old);
 
        if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
                return migrate_file(oldremote);
 
        newremote = remote_get(rename.new);
-       if (remote_is_configured(newremote))
+       if (remote_is_configured(newremote, 1))
                die(_("remote %s already exists."), rename.new);
 
        strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
@@ -753,7 +753,7 @@ static int rm(int argc, const char **argv)
                usage_with_options(builtin_remote_rm_usage, options);
 
        remote = remote_get(argv[1]);
-       if (!remote_is_configured(remote))
+       if (!remote_is_configured(remote, 1))
                die(_("No such remote: %s"), argv[1]);
 
        known_remotes.to_delete = remote;
@@ -1415,7 +1415,7 @@ static int set_remote_branches(const char *remotename, const char **branches,
        strbuf_addf(&key, "remote.%s.fetch", remotename);
 
        remote = remote_get(remotename);
-       if (!remote_is_configured(remote))
+       if (!remote_is_configured(remote, 1))
                die(_("No such remote '%s'"), remotename);
 
        if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
@@ -1469,7 +1469,7 @@ static int get_url(int argc, const char **argv)
        remotename = argv[0];
 
        remote = remote_get(remotename);
-       if (!remote_is_configured(remote))
+       if (!remote_is_configured(remote, 1))
                die(_("No such remote '%s'"), remotename);
 
        url_nr = 0;
@@ -1537,7 +1537,7 @@ static int set_url(int argc, const char **argv)
                oldurl = newurl;
 
        remote = remote_get(remotename);
-       if (!remote_is_configured(remote))
+       if (!remote_is_configured(remote, 1))
                die(_("No such remote '%s'"), remotename);
 
        if (push_mode) {
index 6d4e669002b1e58ebf40af5543221d809ffd0427..013d241abc09be76c318dbc25d5fb3b40ade7b3a 100644 (file)
@@ -19,19 +19,34 @@ static const char *exclude_existing_arg;
 
 static void show_one(const char *refname, const struct object_id *oid)
 {
-       const char *hex = find_unique_abbrev(oid->hash, abbrev);
+       const char *hex;
+       struct object_id peeled;
+
+       if (!has_sha1_file(oid->hash))
+               die("git show-ref: bad ref %s (%s)", refname,
+                   oid_to_hex(oid));
+
+       if (quiet)
+               return;
+
+       hex = find_unique_abbrev(oid->hash, abbrev);
        if (hash_only)
                printf("%s\n", hex);
        else
                printf("%s %s\n", hex, refname);
+
+       if (!deref_tags)
+               return;
+
+       if (!peel_ref(refname, peeled.hash)) {
+               hex = find_unique_abbrev(peeled.hash, abbrev);
+               printf("%s %s^{}\n", hex, refname);
+       }
 }
 
 static int show_ref(const char *refname, const struct object_id *oid,
                    int flag, void *cbdata)
 {
-       const char *hex;
-       struct object_id peeled;
-
        if (show_head && !strcmp(refname, "HEAD"))
                goto match;
 
@@ -54,9 +69,6 @@ static int show_ref(const char *refname, const struct object_id *oid,
                                continue;
                        if (len == reflen)
                                goto match;
-                       /* "--verify" requires an exact match */
-                       if (verify)
-                               continue;
                        if (refname[reflen - len - 1] == '/')
                                goto match;
                }
@@ -66,26 +78,8 @@ static int show_ref(const char *refname, const struct object_id *oid,
 match:
        found_match++;
 
-       /* This changes the semantics slightly that even under quiet we
-        * detect and return error if the repository is corrupt and
-        * ref points at a nonexistent object.
-        */
-       if (!has_sha1_file(oid->hash))
-               die("git show-ref: bad ref %s (%s)", refname,
-                   oid_to_hex(oid));
-
-       if (quiet)
-               return 0;
-
        show_one(refname, oid);
 
-       if (!deref_tags)
-               return 0;
-
-       if (!peel_ref(refname, peeled.hash)) {
-               hex = find_unique_abbrev(peeled.hash, abbrev);
-               printf("%s %s^{}\n", hex, refname);
-       }
        return 0;
 }
 
@@ -202,10 +196,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                while (*pattern) {
                        struct object_id oid;
 
-                       if (starts_with(*pattern, "refs/") &&
+                       if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
                            !read_ref(*pattern, oid.hash)) {
-                               if (!quiet)
-                                       show_one(*pattern, &oid);
+                               show_one(*pattern, &oid);
                        }
                        else if (!quiet)
                                die("'%s' - not a valid ref", *pattern);
index 74614a951e8bb8aef4ed0ef07665f8ec0e43cc78..899dc334e323a53f3e8a2981a1f8140f55f136a9 100644 (file)
@@ -626,7 +626,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                                   module_clone_options);
 
        strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
-       sm_gitdir = xstrdup(absolute_path(sb.buf));
+       sm_gitdir = absolute_pathdup(sb.buf);
        strbuf_reset(&sb);
 
        if (!is_absolute_path(path)) {
index 73df728114e81ac87dd1b84604947d739d948fb6..e40c4a96763a371b104a5bf47e6d839ed93e8ef5 100644 (file)
@@ -24,7 +24,7 @@ static const char * const git_tag_usage[] = {
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
                "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
-       N_("git tag -v <tagname>..."),
+       N_("git tag -v [--format=<format>] <tagname>..."),
        NULL
 };
 
@@ -66,9 +66,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con
 }
 
 typedef int (*each_tag_name_fn)(const char *name, const char *ref,
-                               const unsigned char *sha1);
+                               const unsigned char *sha1, const void *cb_data);
 
-static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
+static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
+                            const void *cb_data)
 {
        const char **p;
        char ref[PATH_MAX];
@@ -87,14 +88,14 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
                        had_error = 1;
                        continue;
                }
-               if (fn(*p, ref, sha1))
+               if (fn(*p, ref, sha1, cb_data))
                        had_error = 1;
        }
        return had_error;
 }
 
 static int delete_tag(const char *name, const char *ref,
-                               const unsigned char *sha1)
+                     const unsigned char *sha1, const void *cb_data)
 {
        if (delete_ref(ref, sha1, 0))
                return 1;
@@ -103,9 +104,22 @@ static int delete_tag(const char *name, const char *ref,
 }
 
 static int verify_tag(const char *name, const char *ref,
-                               const unsigned char *sha1)
+                     const unsigned char *sha1, const void *cb_data)
 {
-       return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE);
+       int flags;
+       const char *fmt_pretty = cb_data;
+       flags = GPG_VERIFY_VERBOSE;
+
+       if (fmt_pretty)
+               flags = GPG_VERIFY_OMIT_STATUS;
+
+       if (gpg_verify_tag(sha1, name, flags))
+               return -1;
+
+       if (fmt_pretty)
+               pretty_print_ref(name, sha1, fmt_pretty);
+
+       return 0;
 }
 
 static int do_sign(struct strbuf *buffer)
@@ -428,9 +442,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (filter.merge_commit)
                die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
-               return for_each_tag_name(argv, delete_tag);
-       if (cmdmode == 'v')
-               return for_each_tag_name(argv, verify_tag);
+               return for_each_tag_name(argv, delete_tag, NULL);
+       if (cmdmode == 'v') {
+               if (format)
+                       verify_ref_format(format);
+               return for_each_tag_name(argv, verify_tag, format);
+       }
 
        if (msg.given || msgfile) {
                if (msg.given && msgfile)
index 99f8148cf79bac1d636bfe35a93282f3da2ebfd7..5199553d914f266315ffc3f170ef95a2a7f621ff 100644 (file)
 #include <signal.h>
 #include "parse-options.h"
 #include "gpg-interface.h"
+#include "ref-filter.h"
 
 static const char * const verify_tag_usage[] = {
-               N_("git verify-tag [-v | --verbose] <tag>..."),
+               N_("git verify-tag [-v | --verbose] [--format=<format>] <tag>..."),
                NULL
 };
 
@@ -30,9 +31,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
        unsigned flags = 0;
+       char *fmt_pretty = NULL;
        const struct option verify_tag_options[] = {
                OPT__VERBOSE(&verbose, N_("print tag contents")),
                OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
+               OPT_STRING(  0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
 
@@ -46,13 +49,26 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
        if (verbose)
                flags |= GPG_VERIFY_VERBOSE;
 
+       if (fmt_pretty) {
+               verify_ref_format(fmt_pretty);
+               flags |= GPG_VERIFY_OMIT_STATUS;
+       }
+
        while (i < argc) {
                unsigned char sha1[20];
                const char *name = argv[i++];
-               if (get_sha1(name, sha1))
+               if (get_sha1(name, sha1)) {
                        had_error = !!error("tag '%s' not found.", name);
-               else if (gpg_verify_tag(sha1, name, flags))
+                       continue;
+               }
+
+               if (gpg_verify_tag(sha1, name, flags)) {
                        had_error = 1;
+                       continue;
+               }
+
+               if (fmt_pretty)
+                       pretty_print_ref(name, sha1, fmt_pretty);
        }
        return had_error;
 }
diff --git a/cache.h b/cache.h
index 00a029af3657d319b7df987a36979a519c2dda94..e26566a8ec3f9aae6547da4303ad62c013146208 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -507,9 +507,12 @@ extern int is_nonbare_repository_dir(struct strbuf *path);
 #define READ_GITFILE_ERR_NO_PATH 6
 #define READ_GITFILE_ERR_NOT_A_REPO 7
 #define READ_GITFILE_ERR_TOO_LARGE 8
+extern void read_gitfile_error_die(int error_code, const char *path, const char *dir);
 extern const char *read_gitfile_gently(const char *path, int *return_error_code);
 #define read_gitfile(path) read_gitfile_gently((path), NULL)
-extern const char *resolve_gitdir(const char *suspect);
+extern const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
+#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
+
 extern void set_git_work_tree(const char *tree);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
@@ -574,7 +577,26 @@ extern int verify_path(const char *path);
 extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
 extern void adjust_dirname_case(struct index_state *istate, char *name);
 extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
+
+/*
+ * Searches for an entry defined by name and namelen in the given index.
+ * If the return value is positive (including 0) it is the position of an
+ * exact match. If the return value is negative, the negated value minus 1
+ * is the position where the entry would be inserted.
+ * Example: The current index consists of these files and its stages:
+ *
+ *   b#0, d#0, f#1, f#3
+ *
+ * index_name_pos(&index, "a", 1) -> -1
+ * index_name_pos(&index, "b", 1) ->  0
+ * index_name_pos(&index, "c", 1) -> -2
+ * index_name_pos(&index, "d", 1) ->  1
+ * index_name_pos(&index, "e", 1) -> -3
+ * index_name_pos(&index, "f", 1) -> -3
+ * index_name_pos(&index, "g", 1) -> -5
+ */
 extern int index_name_pos(const struct index_state *, const char *name, int namelen);
+
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
 #define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
@@ -583,7 +605,10 @@ extern int index_name_pos(const struct index_state *, const char *name, int name
 #define ADD_CACHE_KEEP_CACHE_TREE 32   /* Do not invalidate cache-tree */
 extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
 extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
+
+/* Remove entry, return true if there are more entries to go. */
 extern int remove_index_entry_at(struct index_state *, int pos);
+
 extern void remove_marked_cache_entries(struct index_state *istate);
 extern int remove_file_from_index(struct index_state *, const char *path);
 #define ADD_CACHE_VERBOSE 1
@@ -591,8 +616,18 @@ extern int remove_file_from_index(struct index_state *, const char *path);
 #define ADD_CACHE_IGNORE_ERRORS        4
 #define ADD_CACHE_IGNORE_REMOVAL 8
 #define ADD_CACHE_INTENT 16
+/*
+ * These two are used to add the contents of the file at path
+ * to the index, marking the working tree up-to-date by storing
+ * the cached stat info in the resulting cache entry.  A caller
+ * that has already run lstat(2) on the path can call
+ * add_to_index(), and all others can call add_file_to_index();
+ * the latter will do necessary lstat(2) internally before
+ * calling the former.
+ */
 extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
 extern int add_file_to_index(struct index_state *, const char *path, int flags);
+
 extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
 extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
 extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
@@ -1069,6 +1104,7 @@ const char *real_path(const char *path);
 const char *real_path_if_valid(const char *path);
 char *real_pathdup(const char *path);
 const char *absolute_path(const char *path);
+char *absolute_pathdup(const char *path);
 const char *remove_leading_path(const char *in, const char *prefix);
 const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
@@ -1142,6 +1178,19 @@ extern int finalize_object_file(const char *tmpfile, const char *filename);
 
 extern int has_sha1_pack(const unsigned char *sha1);
 
+/*
+ * Open the loose object at path, check its sha1, and return the contents,
+ * type, and size. If the object is a blob, then "contents" may return NULL,
+ * to allow streaming of large blobs.
+ *
+ * Returns 0 on success, negative on error (details may be written to stderr).
+ */
+int read_loose_object(const char *path,
+                     const unsigned char *expected_sha1,
+                     enum object_type *type,
+                     unsigned long *size,
+                     void **contents);
+
 /*
  * Return true iff we have an object named sha1, whether local or in
  * an alternate object database, and whether packed or loose.  This
diff --git a/color.c b/color.c
index 1b95e6b2a7bb1601fa5862de8989775e197c7b41..dee61557e03f452f1e20ce6c8c467203aa8aed60 100644 (file)
--- a/color.c
+++ b/color.c
@@ -207,7 +207,17 @@ int color_parse_mem(const char *value, int value_len, char *dst)
        struct color fg = { COLOR_UNSPECIFIED };
        struct color bg = { COLOR_UNSPECIFIED };
 
-       if (!strncasecmp(value, "reset", len)) {
+       while (len > 0 && isspace(*ptr)) {
+               ptr++;
+               len--;
+       }
+
+       if (!len) {
+               dst[0] = '\0';
+               return 0;
+       }
+
+       if (!strncasecmp(ptr, "reset", len)) {
                xsnprintf(dst, end - dst, GIT_COLOR_RESET);
                return 0;
        }
index 2a94137bbb383fda655b481446d0891062d73cb9..a1fad28fd82da18cc2b8f43e8eb26fed9864411b 100644 (file)
@@ -107,7 +107,6 @@ git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
 git-reflog                              ancillarymanipulators
-git-relink                              ancillarymanipulators
 git-remote                              ancillarymanipulators
 git-repack                              ancillarymanipulators
 git-replace                             ancillarymanipulators
diff --git a/compat/qsort_s.c b/compat/qsort_s.c
new file mode 100644 (file)
index 0000000..52d1f0a
--- /dev/null
@@ -0,0 +1,69 @@
+#include "../git-compat-util.h"
+
+/*
+ * A merge sort implementation, simplified from the qsort implementation
+ * by Mike Haertel, which is a part of the GNU C Library.
+ * Added context pointer, safety checks and return value.
+ */
+
+static void msort_with_tmp(void *b, size_t n, size_t s,
+                          int (*cmp)(const void *, const void *, void *),
+                          char *t, void *ctx)
+{
+       char *tmp;
+       char *b1, *b2;
+       size_t n1, n2;
+
+       if (n <= 1)
+               return;
+
+       n1 = n / 2;
+       n2 = n - n1;
+       b1 = b;
+       b2 = (char *)b + (n1 * s);
+
+       msort_with_tmp(b1, n1, s, cmp, t, ctx);
+       msort_with_tmp(b2, n2, s, cmp, t, ctx);
+
+       tmp = t;
+
+       while (n1 > 0 && n2 > 0) {
+               if (cmp(b1, b2, ctx) <= 0) {
+                       memcpy(tmp, b1, s);
+                       tmp += s;
+                       b1 += s;
+                       --n1;
+               } else {
+                       memcpy(tmp, b2, s);
+                       tmp += s;
+                       b2 += s;
+                       --n2;
+               }
+       }
+       if (n1 > 0)
+               memcpy(tmp, b1, n1 * s);
+       memcpy(b, t, (n - n2) * s);
+}
+
+int git_qsort_s(void *b, size_t n, size_t s,
+               int (*cmp)(const void *, const void *, void *), void *ctx)
+{
+       const size_t size = st_mult(n, s);
+       char buf[1024];
+
+       if (!n)
+               return 0;
+       if (!b || !cmp)
+               return -1;
+
+       if (size < sizeof(buf)) {
+               /* The temporary array fits on the small on-stack buffer. */
+               msort_with_tmp(b, n, s, cmp, buf, ctx);
+       } else {
+               /* It's somewhat large, so malloc it.  */
+               char *tmp = xmalloc(size);
+               msort_with_tmp(b, n, s, cmp, tmp, ctx);
+               free(tmp);
+       }
+       return 0;
+}
index 3c9ed3cfe07c1f3b58f5400af861fb8ae44c3fdb..82b89ab13767a5e562d4f41826d98bce0d704e1c 100644 (file)
@@ -494,19 +494,16 @@ static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
         * It is because of this implicit close() that we created the
         * copy of the original.
         *
-        * Note that the OS can recycle HANDLE (numbers) just like it
-        * recycles fd (numbers), so we must update the cached value
-        * of "console".  You can use GetFileType() to see that
-        * handle and _get_osfhandle(fd) may have the same number
-        * value, but they refer to different actual files now.
+        * Note that we need to update the cached console handle to the
+        * duplicated one because the dup2() call will implicitly close
+        * the original one.
         *
         * Note that dup2() when given target := {0,1,2} will also
         * call SetStdHandle(), so we don't need to worry about that.
         */
-       dup2(new_fd, fd);
        if (console == handle)
                console = duplicate;
-       handle = INVALID_HANDLE_VALUE;
+       dup2(new_fd, fd);
 
        /* Close the temp fd.  This explicitly closes "new_handle"
         * (because it has been associated with it).
index 3fceef132bf57623e04a080d03da9aff951f1c11..8e05d1ca4b61b9792a6f7cc4e7d322efeab02e01 100644 (file)
@@ -5,3 +5,9 @@ expression V;
 - if (E)
 -    V = xstrdup(E);
 + V = xstrdup_or_null(E);
+
+@@
+expression E;
+@@
+- xstrdup(absolute_path(E))
++ absolute_pathdup(E)
diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c
deleted file mode 100644 (file)
index f3b57bf..0000000
+++ /dev/null
@@ -1,329 +0,0 @@
-#include "cache.h"
-#include "blob.h"
-#include "commit.h"
-#include "tree.h"
-
-struct entry {
-       unsigned char old_sha1[20];
-       unsigned char new_sha1[20];
-       int converted;
-};
-
-#define MAXOBJECTS (1000000)
-
-static struct entry *convert[MAXOBJECTS];
-static int nr_convert;
-
-static struct entry * convert_entry(unsigned char *sha1);
-
-static struct entry *insert_new(unsigned char *sha1, int pos)
-{
-       struct entry *new = xcalloc(1, sizeof(struct entry));
-       hashcpy(new->old_sha1, sha1);
-       memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
-       convert[pos] = new;
-       nr_convert++;
-       if (nr_convert == MAXOBJECTS)
-               die("you're kidding me - hit maximum object limit");
-       return new;
-}
-
-static struct entry *lookup_entry(unsigned char *sha1)
-{
-       int low = 0, high = nr_convert;
-
-       while (low < high) {
-               int next = (low + high) / 2;
-               struct entry *n = convert[next];
-               int cmp = hashcmp(sha1, n->old_sha1);
-               if (!cmp)
-                       return n;
-               if (cmp < 0) {
-                       high = next;
-                       continue;
-               }
-               low = next+1;
-       }
-       return insert_new(sha1, low);
-}
-
-static void convert_binary_sha1(void *buffer)
-{
-       struct entry *entry = convert_entry(buffer);
-       hashcpy(buffer, entry->new_sha1);
-}
-
-static void convert_ascii_sha1(void *buffer)
-{
-       unsigned char sha1[20];
-       struct entry *entry;
-
-       if (get_sha1_hex(buffer, sha1))
-               die("expected sha1, got '%s'", (char *) buffer);
-       entry = convert_entry(sha1);
-       memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
-}
-
-static unsigned int convert_mode(unsigned int mode)
-{
-       unsigned int newmode;
-
-       newmode = mode & S_IFMT;
-       if (S_ISREG(mode))
-               newmode |= (mode & 0100) ? 0755 : 0644;
-       return newmode;
-}
-
-static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
-{
-       char *new = xmalloc(size);
-       unsigned long newlen = 0;
-       unsigned long used;
-
-       used = 0;
-       while (size) {
-               int len = 21 + strlen(buffer);
-               char *path = strchr(buffer, ' ');
-               unsigned char *sha1;
-               unsigned int mode;
-               char *slash, *origpath;
-
-               if (!path || strtoul_ui(buffer, 8, &mode))
-                       die("bad tree conversion");
-               mode = convert_mode(mode);
-               path++;
-               if (memcmp(path, base, baselen))
-                       break;
-               origpath = path;
-               path += baselen;
-               slash = strchr(path, '/');
-               if (!slash) {
-                       newlen += sprintf(new + newlen, "%o %s", mode, path);
-                       new[newlen++] = '\0';
-                       hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20);
-                       newlen += 20;
-
-                       used += len;
-                       size -= len;
-                       buffer = (char *) buffer + len;
-                       continue;
-               }
-
-               newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
-               new[newlen++] = 0;
-               sha1 = (unsigned char *)(new + newlen);
-               newlen += 20;
-
-               len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
-
-               used += len;
-               size -= len;
-               buffer = (char *) buffer + len;
-       }
-
-       write_sha1_file(new, newlen, tree_type, result_sha1);
-       free(new);
-       return used;
-}
-
-static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
-       void *orig_buffer = buffer;
-       unsigned long orig_size = size;
-
-       while (size) {
-               size_t len = 1+strlen(buffer);
-
-               convert_binary_sha1((char *) buffer + len);
-
-               len += 20;
-               if (len > size)
-                       die("corrupt tree object");
-               size -= len;
-               buffer = (char *) buffer + len;
-       }
-
-       write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
-}
-
-static unsigned long parse_oldstyle_date(const char *buf)
-{
-       char c, *p;
-       char buffer[100];
-       struct tm tm;
-       const char *formats[] = {
-               "%c",
-               "%a %b %d %T",
-               "%Z",
-               "%Y",
-               " %Y",
-               NULL
-       };
-       /* We only ever did two timezones in the bad old format .. */
-       const char *timezones[] = {
-               "PDT", "PST", "CEST", NULL
-       };
-       const char **fmt = formats;
-
-       p = buffer;
-       while (isspace(c = *buf))
-               buf++;
-       while ((c = *buf++) != '\n')
-               *p++ = c;
-       *p++ = 0;
-       buf = buffer;
-       memset(&tm, 0, sizeof(tm));
-       do {
-               const char *next = strptime(buf, *fmt, &tm);
-               if (next) {
-                       if (!*next)
-                               return mktime(&tm);
-                       buf = next;
-               } else {
-                       const char **p = timezones;
-                       while (isspace(*buf))
-                               buf++;
-                       while (*p) {
-                               if (!memcmp(buf, *p, strlen(*p))) {
-                                       buf += strlen(*p);
-                                       break;
-                               }
-                               p++;
-                       }
-               }
-               fmt++;
-       } while (*buf && *fmt);
-       printf("left: %s\n", buf);
-       return mktime(&tm);
-}
-
-static int convert_date_line(char *dst, void **buf, unsigned long *sp)
-{
-       unsigned long size = *sp;
-       char *line = *buf;
-       char *next = strchr(line, '\n');
-       char *date = strchr(line, '>');
-       int len;
-
-       if (!next || !date)
-               die("missing or bad author/committer line %s", line);
-       next++; date += 2;
-
-       *buf = next;
-       *sp = size - (next - line);
-
-       len = date - line;
-       memcpy(dst, line, len);
-       dst += len;
-
-       /* Is it already in new format? */
-       if (isdigit(*date)) {
-               int datelen = next - date;
-               memcpy(dst, date, datelen);
-               return len + datelen;
-       }
-
-       /*
-        * Hacky hacky: one of the sparse old-style commits does not have
-        * any date at all, but we can fake it by using the committer date.
-        */
-       if (*date == '\n' && strchr(next, '>'))
-               date = strchr(next, '>')+2;
-
-       return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
-}
-
-static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
-       char *new = xmalloc(size + 100);
-       unsigned long newlen = 0;
-
-       /* "tree <sha1>\n" */
-       memcpy(new + newlen, buffer, 46);
-       newlen += 46;
-       buffer = (char *) buffer + 46;
-       size -= 46;
-
-       /* "parent <sha1>\n" */
-       while (!memcmp(buffer, "parent ", 7)) {
-               memcpy(new + newlen, buffer, 48);
-               newlen += 48;
-               buffer = (char *) buffer + 48;
-               size -= 48;
-       }
-
-       /* "author xyz <xyz> date" */
-       newlen += convert_date_line(new + newlen, &buffer, &size);
-       /* "committer xyz <xyz> date" */
-       newlen += convert_date_line(new + newlen, &buffer, &size);
-
-       /* Rest */
-       memcpy(new + newlen, buffer, size);
-       newlen += size;
-
-       write_sha1_file(new, newlen, commit_type, result_sha1);
-       free(new);
-}
-
-static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
-       void *orig_buffer = buffer;
-       unsigned long orig_size = size;
-
-       if (memcmp(buffer, "tree ", 5))
-               die("Bad commit '%s'", (char *) buffer);
-       convert_ascii_sha1((char *) buffer + 5);
-       buffer = (char *) buffer + 46;    /* "tree " + "hex sha1" + "\n" */
-       while (!memcmp(buffer, "parent ", 7)) {
-               convert_ascii_sha1((char *) buffer + 7);
-               buffer = (char *) buffer + 48;
-       }
-       convert_date(orig_buffer, orig_size, result_sha1);
-}
-
-static struct entry * convert_entry(unsigned char *sha1)
-{
-       struct entry *entry = lookup_entry(sha1);
-       enum object_type type;
-       void *buffer, *data;
-       unsigned long size;
-
-       if (entry->converted)
-               return entry;
-       data = read_sha1_file(sha1, &type, &size);
-       if (!data)
-               die("unable to read object %s", sha1_to_hex(sha1));
-
-       buffer = xmalloc(size);
-       memcpy(buffer, data, size);
-
-       if (type == OBJ_BLOB) {
-               write_sha1_file(buffer, size, blob_type, entry->new_sha1);
-       } else if (type == OBJ_TREE)
-               convert_tree(buffer, size, entry->new_sha1);
-       else if (type == OBJ_COMMIT)
-               convert_commit(buffer, size, entry->new_sha1);
-       else
-               die("unknown object type %d in %s", type, sha1_to_hex(sha1));
-       entry->converted = 1;
-       free(buffer);
-       free(data);
-       return entry;
-}
-
-int main(int argc, char **argv)
-{
-       unsigned char sha1[20];
-       struct entry *entry;
-
-       setup_git_directory();
-
-       if (argc != 2)
-               usage("git-convert-objects <sha1>");
-       if (get_sha1(argv[1], sha1))
-               die("Not a valid object name %s", argv[1]);
-
-       entry = convert_entry(sha1);
-       printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
-       return 0;
-}
diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt
deleted file mode 100644 (file)
index f871880..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-git-convert-objects(1)
-======================
-
-NAME
-----
-git-convert-objects - Converts old-style git repository
-
-
-SYNOPSIS
---------
-[verse]
-'git-convert-objects'
-
-DESCRIPTION
------------
-Converts old-style git repository to the latest format
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the linkgit:git[7] suite
diff --git a/contrib/examples/git-difftool.perl b/contrib/examples/git-difftool.perl
new file mode 100755 (executable)
index 0000000..df59bdf
--- /dev/null
@@ -0,0 +1,481 @@
+#!/usr/bin/perl
+# Copyright (c) 2009, 2010 David Aguilar
+# Copyright (c) 2012 Tim Henigan
+#
+# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+# git-difftool--helper script.
+#
+# This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+# The GIT_DIFF* variables are exported for use by git-difftool--helper.
+#
+# Any arguments that are unknown to this script are forwarded to 'git diff'.
+
+use 5.008;
+use strict;
+use warnings;
+use Error qw(:try);
+use File::Basename qw(dirname);
+use File::Copy;
+use File::Find;
+use File::stat;
+use File::Path qw(mkpath rmtree);
+use File::Temp qw(tempdir);
+use Getopt::Long qw(:config pass_through);
+use Git;
+use Git::I18N;
+
+sub usage
+{
+       my $exitcode = shift;
+       print << 'USAGE';
+usage: git difftool [-t|--tool=<tool>] [--tool-help]
+                    [-x|--extcmd=<cmd>]
+                    [-g|--gui] [--no-gui]
+                    [--prompt] [-y|--no-prompt]
+                    [-d|--dir-diff]
+                    ['git diff' options]
+USAGE
+       exit($exitcode);
+}
+
+sub print_tool_help
+{
+       # See the comment at the bottom of file_diff() for the reason behind
+       # using system() followed by exit() instead of exec().
+       my $rc = system(qw(git mergetool --tool-help=diff));
+       exit($rc | ($rc >> 8));
+}
+
+sub exit_cleanup
+{
+       my ($tmpdir, $status) = @_;
+       my $errno = $!;
+       rmtree($tmpdir);
+       if ($status and $errno) {
+               my ($package, $file, $line) = caller();
+               warn "$file line $line: $errno\n";
+       }
+       exit($status | ($status >> 8));
+}
+
+sub use_wt_file
+{
+       my ($file, $sha1) = @_;
+       my $null_sha1 = '0' x 40;
+
+       if (-l $file || ! -e _) {
+               return (0, $null_sha1);
+       }
+
+       my $wt_sha1 = Git::command_oneline('hash-object', $file);
+       my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1);
+       return ($use, $wt_sha1);
+}
+
+sub changed_files
+{
+       my ($repo_path, $index, $worktree) = @_;
+       $ENV{GIT_INDEX_FILE} = $index;
+
+       my @gitargs = ('--git-dir', $repo_path, '--work-tree', $worktree);
+       my @refreshargs = (
+               @gitargs, 'update-index',
+               '--really-refresh', '-q', '--unmerged');
+       try {
+               Git::command_oneline(@refreshargs);
+       } catch Git::Error::Command with {};
+
+       my @diffargs = (@gitargs, 'diff-files', '--name-only', '-z');
+       my $line = Git::command_oneline(@diffargs);
+       my @files;
+       if (defined $line) {
+               @files = split('\0', $line);
+       } else {
+               @files = ();
+       }
+
+       delete($ENV{GIT_INDEX_FILE});
+
+       return map { $_ => 1 } @files;
+}
+
+sub setup_dir_diff
+{
+       my ($worktree, $symlinks) = @_;
+       my @gitargs = ('diff', '--raw', '--no-abbrev', '-z', @ARGV);
+       my $diffrtn = Git::command_oneline(@gitargs);
+       exit(0) unless defined($diffrtn);
+
+       # Go to the root of the worktree now that we've captured the list of
+       # changed files.  The paths returned by diff --raw are relative to the
+       # top-level of the repository, but we defer changing directories so
+       # that @ARGV can perform pathspec limiting in the current directory.
+       chdir($worktree);
+
+       # Build index info for left and right sides of the diff
+       my $submodule_mode = '160000';
+       my $symlink_mode = '120000';
+       my $null_mode = '0' x 6;
+       my $null_sha1 = '0' x 40;
+       my $lindex = '';
+       my $rindex = '';
+       my $wtindex = '';
+       my %submodule;
+       my %symlink;
+       my @files = ();
+       my %working_tree_dups = ();
+       my @rawdiff = split('\0', $diffrtn);
+
+       my $i = 0;
+       while ($i < $#rawdiff) {
+               if ($rawdiff[$i] =~ /^::/) {
+                       warn __ <<'EOF';
+Combined diff formats ('-c' and '--cc') are not supported in
+directory diff mode ('-d' and '--dir-diff').
+EOF
+                       exit(1);
+               }
+
+               my ($lmode, $rmode, $lsha1, $rsha1, $status) =
+                       split(' ', substr($rawdiff[$i], 1));
+               my $src_path = $rawdiff[$i + 1];
+               my $dst_path;
+
+               if ($status =~ /^[CR]/) {
+                       $dst_path = $rawdiff[$i + 2];
+                       $i += 3;
+               } else {
+                       $dst_path = $src_path;
+                       $i += 2;
+               }
+
+               if ($lmode eq $submodule_mode or $rmode eq $submodule_mode) {
+                       $submodule{$src_path}{left} = $lsha1;
+                       if ($lsha1 ne $rsha1) {
+                               $submodule{$dst_path}{right} = $rsha1;
+                       } else {
+                               $submodule{$dst_path}{right} = "$rsha1-dirty";
+                       }
+                       next;
+               }
+
+               if ($lmode eq $symlink_mode) {
+                       $symlink{$src_path}{left} =
+                               Git::command_oneline('show', $lsha1);
+               }
+
+               if ($rmode eq $symlink_mode) {
+                       $symlink{$dst_path}{right} =
+                               Git::command_oneline('show', $rsha1);
+               }
+
+               if ($lmode ne $null_mode and $status !~ /^C/) {
+                       $lindex .= "$lmode $lsha1\t$src_path\0";
+               }
+
+               if ($rmode ne $null_mode) {
+                       # Avoid duplicate entries
+                       if ($working_tree_dups{$dst_path}++) {
+                               next;
+                       }
+                       my ($use, $wt_sha1) =
+                               use_wt_file($dst_path, $rsha1);
+                       if ($use) {
+                               push @files, $dst_path;
+                               $wtindex .= "$rmode $wt_sha1\t$dst_path\0";
+                       } else {
+                               $rindex .= "$rmode $rsha1\t$dst_path\0";
+                       }
+               }
+       }
+
+       # Go to the root of the worktree so that the left index files
+       # are properly setup -- the index is toplevel-relative.
+       chdir($worktree);
+
+       # Setup temp directories
+       my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 0, TMPDIR => 1);
+       my $ldir = "$tmpdir/left";
+       my $rdir = "$tmpdir/right";
+       mkpath($ldir) or exit_cleanup($tmpdir, 1);
+       mkpath($rdir) or exit_cleanup($tmpdir, 1);
+
+       # Populate the left and right directories based on each index file
+       my ($inpipe, $ctx);
+       $ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
+       ($inpipe, $ctx) =
+               Git::command_input_pipe('update-index', '-z', '--index-info');
+       print($inpipe $lindex);
+       Git::command_close_pipe($inpipe, $ctx);
+
+       my $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
+       exit_cleanup($tmpdir, $rc) if $rc != 0;
+
+       $ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
+       ($inpipe, $ctx) =
+               Git::command_input_pipe('update-index', '-z', '--index-info');
+       print($inpipe $rindex);
+       Git::command_close_pipe($inpipe, $ctx);
+
+       $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
+       exit_cleanup($tmpdir, $rc) if $rc != 0;
+
+       $ENV{GIT_INDEX_FILE} = "$tmpdir/wtindex";
+       ($inpipe, $ctx) =
+               Git::command_input_pipe('update-index', '--info-only', '-z', '--index-info');
+       print($inpipe $wtindex);
+       Git::command_close_pipe($inpipe, $ctx);
+
+       # If $GIT_DIR was explicitly set just for the update/checkout
+       # commands, then it should be unset before continuing.
+       delete($ENV{GIT_INDEX_FILE});
+
+       # Changes in the working tree need special treatment since they are
+       # not part of the index.
+       for my $file (@files) {
+               my $dir = dirname($file);
+               unless (-d "$rdir/$dir") {
+                       mkpath("$rdir/$dir") or
+                       exit_cleanup($tmpdir, 1);
+               }
+               if ($symlinks) {
+                       symlink("$worktree/$file", "$rdir/$file") or
+                       exit_cleanup($tmpdir, 1);
+               } else {
+                       copy($file, "$rdir/$file") or
+                       exit_cleanup($tmpdir, 1);
+
+                       my $mode = stat($file)->mode;
+                       chmod($mode, "$rdir/$file") or
+                       exit_cleanup($tmpdir, 1);
+               }
+       }
+
+       # Changes to submodules require special treatment. This loop writes a
+       # temporary file to both the left and right directories to show the
+       # change in the recorded SHA1 for the submodule.
+       for my $path (keys %submodule) {
+               my $ok = 0;
+               if (defined($submodule{$path}{left})) {
+                       $ok = write_to_file("$ldir/$path",
+                               "Subproject commit $submodule{$path}{left}");
+               }
+               if (defined($submodule{$path}{right})) {
+                       $ok = write_to_file("$rdir/$path",
+                               "Subproject commit $submodule{$path}{right}");
+               }
+               exit_cleanup($tmpdir, 1) if not $ok;
+       }
+
+       # Symbolic links require special treatment. The standard "git diff"
+       # shows only the link itself, not the contents of the link target.
+       # This loop replicates that behavior.
+       for my $path (keys %symlink) {
+               my $ok = 0;
+               if (defined($symlink{$path}{left})) {
+                       $ok = write_to_file("$ldir/$path",
+                                       $symlink{$path}{left});
+               }
+               if (defined($symlink{$path}{right})) {
+                       $ok = write_to_file("$rdir/$path",
+                                       $symlink{$path}{right});
+               }
+               exit_cleanup($tmpdir, 1) if not $ok;
+       }
+
+       return ($ldir, $rdir, $tmpdir, @files);
+}
+
+sub write_to_file
+{
+       my $path = shift;
+       my $value = shift;
+
+       # Make sure the path to the file exists
+       my $dir = dirname($path);
+       unless (-d "$dir") {
+               mkpath("$dir") or return 0;
+       }
+
+       # If the file already exists in that location, delete it.  This
+       # is required in the case of symbolic links.
+       unlink($path);
+
+       open(my $fh, '>', $path) or return 0;
+       print($fh $value);
+       close($fh);
+
+       return 1;
+}
+
+sub main
+{
+       # parse command-line options. all unrecognized options and arguments
+       # are passed through to the 'git diff' command.
+       my %opts = (
+               difftool_cmd => undef,
+               dirdiff => undef,
+               extcmd => undef,
+               gui => undef,
+               help => undef,
+               prompt => undef,
+               symlinks => $^O ne 'cygwin' &&
+                               $^O ne 'MSWin32' && $^O ne 'msys',
+               tool_help => undef,
+               trust_exit_code => undef,
+       );
+       GetOptions('g|gui!' => \$opts{gui},
+               'd|dir-diff' => \$opts{dirdiff},
+               'h' => \$opts{help},
+               'prompt!' => \$opts{prompt},
+               'y' => sub { $opts{prompt} = 0; },
+               'symlinks' => \$opts{symlinks},
+               'no-symlinks' => sub { $opts{symlinks} = 0; },
+               't|tool:s' => \$opts{difftool_cmd},
+               'tool-help' => \$opts{tool_help},
+               'trust-exit-code' => \$opts{trust_exit_code},
+               'no-trust-exit-code' => sub { $opts{trust_exit_code} = 0; },
+               'x|extcmd:s' => \$opts{extcmd});
+
+       if (defined($opts{help})) {
+               usage(0);
+       }
+       if (defined($opts{tool_help})) {
+               print_tool_help();
+       }
+       if (defined($opts{difftool_cmd})) {
+               if (length($opts{difftool_cmd}) > 0) {
+                       $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd};
+               } else {
+                       print __("No <tool> given for --tool=<tool>\n");
+                       usage(1);
+               }
+       }
+       if (defined($opts{extcmd})) {
+               if (length($opts{extcmd}) > 0) {
+                       $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd};
+               } else {
+                       print __("No <cmd> given for --extcmd=<cmd>\n");
+                       usage(1);
+               }
+       }
+       if ($opts{gui}) {
+               my $guitool = Git::config('diff.guitool');
+               if (defined($guitool) && length($guitool) > 0) {
+                       $ENV{GIT_DIFF_TOOL} = $guitool;
+               }
+       }
+
+       if (!defined $opts{trust_exit_code}) {
+               $opts{trust_exit_code} = Git::config_bool('difftool.trustExitCode');
+       }
+       if ($opts{trust_exit_code}) {
+               $ENV{GIT_DIFFTOOL_TRUST_EXIT_CODE} = 'true';
+       } else {
+               $ENV{GIT_DIFFTOOL_TRUST_EXIT_CODE} = 'false';
+       }
+
+       # In directory diff mode, 'git-difftool--helper' is called once
+       # to compare the a/b directories.  In file diff mode, 'git diff'
+       # will invoke a separate instance of 'git-difftool--helper' for
+       # each file that changed.
+       if (defined($opts{dirdiff})) {
+               dir_diff($opts{extcmd}, $opts{symlinks});
+       } else {
+               file_diff($opts{prompt});
+       }
+}
+
+sub dir_diff
+{
+       my ($extcmd, $symlinks) = @_;
+       my $rc;
+       my $error = 0;
+       my $repo = Git->repository();
+       my $repo_path = $repo->repo_path();
+       my $worktree = $repo->wc_path();
+       $worktree =~ s|/$||; # Avoid double slashes in symlink targets
+       my ($a, $b, $tmpdir, @files) = setup_dir_diff($worktree, $symlinks);
+
+       if (defined($extcmd)) {
+               $rc = system($extcmd, $a, $b);
+       } else {
+               $ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
+               $rc = system('git', 'difftool--helper', $a, $b);
+       }
+       # If the diff including working copy files and those
+       # files were modified during the diff, then the changes
+       # should be copied back to the working tree.
+       # Do not copy back files when symlinks are used and the
+       # external tool did not replace the original link with a file.
+       #
+       # These hashes are loaded lazily since they aren't needed
+       # in the common case of --symlinks and the difftool updating
+       # files through the symlink.
+       my %wt_modified;
+       my %tmp_modified;
+       my $indices_loaded = 0;
+
+       for my $file (@files) {
+               next if $symlinks && -l "$b/$file";
+               next if ! -f "$b/$file";
+
+               if (!$indices_loaded) {
+                       %wt_modified = changed_files(
+                               $repo_path, "$tmpdir/wtindex", $worktree);
+                       %tmp_modified = changed_files(
+                               $repo_path, "$tmpdir/wtindex", $b);
+                       $indices_loaded = 1;
+               }
+
+               if (exists $wt_modified{$file} and exists $tmp_modified{$file}) {
+                       warn sprintf(__(
+                               "warning: Both files modified:\n" .
+                               "'%s/%s' and '%s/%s'.\n" .
+                               "warning: Working tree file has been left.\n" .
+                               "warning:\n"), $worktree, $file, $b, $file);
+                       $error = 1;
+               } elsif (exists $tmp_modified{$file}) {
+                       my $mode = stat("$b/$file")->mode;
+                       copy("$b/$file", $file) or
+                       exit_cleanup($tmpdir, 1);
+
+                       chmod($mode, $file) or
+                       exit_cleanup($tmpdir, 1);
+               }
+       }
+       if ($error) {
+               warn sprintf(__(
+                       "warning: Temporary files exist in '%s'.\n" .
+                       "warning: You may want to cleanup or recover these.\n"), $tmpdir);
+               exit(1);
+       } else {
+               exit_cleanup($tmpdir, $rc);
+       }
+}
+
+sub file_diff
+{
+       my ($prompt) = @_;
+
+       if (defined($prompt)) {
+               if ($prompt) {
+                       $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
+               } else {
+                       $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+               }
+       }
+
+       $ENV{GIT_PAGER} = '';
+       $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+
+       # ActiveState Perl for Win32 does not implement POSIX semantics of
+       # exec* system call. It just spawns the given executable and finishes
+       # the starting program, exiting with code 0.
+       # system will at least catch the errors returned by git diff,
+       # allowing the caller of git difftool better handling of failures.
+       my $rc = system('git', 'diff', @ARGV);
+       exit($rc | ($rc >> 8));
+}
+
+main();
index 19ac2146d0c49be8f5ef3739664dc3105544b4c2..fb94aeba9cef2893ea7173e5634640e596659474 100644 (file)
@@ -64,17 +64,19 @@ void git_set_argv_exec_path(const char *exec_path)
 /* Returns the highest-priority, location to look for git programs. */
 const char *git_exec_path(void)
 {
-       const char *env;
+       static char *cached_exec_path;
 
        if (argv_exec_path)
                return argv_exec_path;
 
-       env = getenv(EXEC_PATH_ENVIRONMENT);
-       if (env && *env) {
-               return env;
+       if (!cached_exec_path) {
+               const char *env = getenv(EXEC_PATH_ENVIRONMENT);
+               if (env && *env)
+                       cached_exec_path = xstrdup(env);
+               else
+                       cached_exec_path = system_path(GIT_EXEC_PATH);
        }
-
-       return system_path(GIT_EXEC_PATH);
+       return cached_exec_path;
 }
 
 static void add_path(struct strbuf *out, const char *path)
diff --git a/fsck.c b/fsck.c
index 4a3069e204ea555bfb6d1ce05a0163860f852437..939792752bf39c72cc536f00c21e2af8ed854c3e 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -458,6 +458,10 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
 {
        if (!obj)
                return -1;
+
+       if (obj->type == OBJ_NONE)
+               parse_object(obj->oid.hash);
+
        switch (obj->type) {
        case OBJ_BLOB:
                return 0;
index 87237b092b51df6255552f0756359eddf3b12042..f46d40e4a475c9bd35ff02373101e8431e722b7d 100644 (file)
@@ -988,6 +988,17 @@ static inline void sane_qsort(void *base, size_t nmemb, size_t size,
                qsort(base, nmemb, size, compar);
 }
 
+#ifndef HAVE_ISO_QSORT_S
+int git_qsort_s(void *base, size_t nmemb, size_t size,
+               int (*compar)(const void *, const void *, void *), void *ctx);
+#define qsort_s git_qsort_s
+#endif
+
+#define QSORT_S(base, n, compar, ctx) do {                     \
+       if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \
+               die("BUG: qsort_s() failed");                   \
+} while (0)
+
 #ifndef REG_STARTEND
 #error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd"
 #endif
diff --git a/git-difftool.perl b/git-difftool.perl
deleted file mode 100755 (executable)
index df59bdf..0000000
+++ /dev/null
@@ -1,481 +0,0 @@
-#!/usr/bin/perl
-# Copyright (c) 2009, 2010 David Aguilar
-# Copyright (c) 2012 Tim Henigan
-#
-# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
-# git-difftool--helper script.
-#
-# This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
-# The GIT_DIFF* variables are exported for use by git-difftool--helper.
-#
-# Any arguments that are unknown to this script are forwarded to 'git diff'.
-
-use 5.008;
-use strict;
-use warnings;
-use Error qw(:try);
-use File::Basename qw(dirname);
-use File::Copy;
-use File::Find;
-use File::stat;
-use File::Path qw(mkpath rmtree);
-use File::Temp qw(tempdir);
-use Getopt::Long qw(:config pass_through);
-use Git;
-use Git::I18N;
-
-sub usage
-{
-       my $exitcode = shift;
-       print << 'USAGE';
-usage: git difftool [-t|--tool=<tool>] [--tool-help]
-                    [-x|--extcmd=<cmd>]
-                    [-g|--gui] [--no-gui]
-                    [--prompt] [-y|--no-prompt]
-                    [-d|--dir-diff]
-                    ['git diff' options]
-USAGE
-       exit($exitcode);
-}
-
-sub print_tool_help
-{
-       # See the comment at the bottom of file_diff() for the reason behind
-       # using system() followed by exit() instead of exec().
-       my $rc = system(qw(git mergetool --tool-help=diff));
-       exit($rc | ($rc >> 8));
-}
-
-sub exit_cleanup
-{
-       my ($tmpdir, $status) = @_;
-       my $errno = $!;
-       rmtree($tmpdir);
-       if ($status and $errno) {
-               my ($package, $file, $line) = caller();
-               warn "$file line $line: $errno\n";
-       }
-       exit($status | ($status >> 8));
-}
-
-sub use_wt_file
-{
-       my ($file, $sha1) = @_;
-       my $null_sha1 = '0' x 40;
-
-       if (-l $file || ! -e _) {
-               return (0, $null_sha1);
-       }
-
-       my $wt_sha1 = Git::command_oneline('hash-object', $file);
-       my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1);
-       return ($use, $wt_sha1);
-}
-
-sub changed_files
-{
-       my ($repo_path, $index, $worktree) = @_;
-       $ENV{GIT_INDEX_FILE} = $index;
-
-       my @gitargs = ('--git-dir', $repo_path, '--work-tree', $worktree);
-       my @refreshargs = (
-               @gitargs, 'update-index',
-               '--really-refresh', '-q', '--unmerged');
-       try {
-               Git::command_oneline(@refreshargs);
-       } catch Git::Error::Command with {};
-
-       my @diffargs = (@gitargs, 'diff-files', '--name-only', '-z');
-       my $line = Git::command_oneline(@diffargs);
-       my @files;
-       if (defined $line) {
-               @files = split('\0', $line);
-       } else {
-               @files = ();
-       }
-
-       delete($ENV{GIT_INDEX_FILE});
-
-       return map { $_ => 1 } @files;
-}
-
-sub setup_dir_diff
-{
-       my ($worktree, $symlinks) = @_;
-       my @gitargs = ('diff', '--raw', '--no-abbrev', '-z', @ARGV);
-       my $diffrtn = Git::command_oneline(@gitargs);
-       exit(0) unless defined($diffrtn);
-
-       # Go to the root of the worktree now that we've captured the list of
-       # changed files.  The paths returned by diff --raw are relative to the
-       # top-level of the repository, but we defer changing directories so
-       # that @ARGV can perform pathspec limiting in the current directory.
-       chdir($worktree);
-
-       # Build index info for left and right sides of the diff
-       my $submodule_mode = '160000';
-       my $symlink_mode = '120000';
-       my $null_mode = '0' x 6;
-       my $null_sha1 = '0' x 40;
-       my $lindex = '';
-       my $rindex = '';
-       my $wtindex = '';
-       my %submodule;
-       my %symlink;
-       my @files = ();
-       my %working_tree_dups = ();
-       my @rawdiff = split('\0', $diffrtn);
-
-       my $i = 0;
-       while ($i < $#rawdiff) {
-               if ($rawdiff[$i] =~ /^::/) {
-                       warn __ <<'EOF';
-Combined diff formats ('-c' and '--cc') are not supported in
-directory diff mode ('-d' and '--dir-diff').
-EOF
-                       exit(1);
-               }
-
-               my ($lmode, $rmode, $lsha1, $rsha1, $status) =
-                       split(' ', substr($rawdiff[$i], 1));
-               my $src_path = $rawdiff[$i + 1];
-               my $dst_path;
-
-               if ($status =~ /^[CR]/) {
-                       $dst_path = $rawdiff[$i + 2];
-                       $i += 3;
-               } else {
-                       $dst_path = $src_path;
-                       $i += 2;
-               }
-
-               if ($lmode eq $submodule_mode or $rmode eq $submodule_mode) {
-                       $submodule{$src_path}{left} = $lsha1;
-                       if ($lsha1 ne $rsha1) {
-                               $submodule{$dst_path}{right} = $rsha1;
-                       } else {
-                               $submodule{$dst_path}{right} = "$rsha1-dirty";
-                       }
-                       next;
-               }
-
-               if ($lmode eq $symlink_mode) {
-                       $symlink{$src_path}{left} =
-                               Git::command_oneline('show', $lsha1);
-               }
-
-               if ($rmode eq $symlink_mode) {
-                       $symlink{$dst_path}{right} =
-                               Git::command_oneline('show', $rsha1);
-               }
-
-               if ($lmode ne $null_mode and $status !~ /^C/) {
-                       $lindex .= "$lmode $lsha1\t$src_path\0";
-               }
-
-               if ($rmode ne $null_mode) {
-                       # Avoid duplicate entries
-                       if ($working_tree_dups{$dst_path}++) {
-                               next;
-                       }
-                       my ($use, $wt_sha1) =
-                               use_wt_file($dst_path, $rsha1);
-                       if ($use) {
-                               push @files, $dst_path;
-                               $wtindex .= "$rmode $wt_sha1\t$dst_path\0";
-                       } else {
-                               $rindex .= "$rmode $rsha1\t$dst_path\0";
-                       }
-               }
-       }
-
-       # Go to the root of the worktree so that the left index files
-       # are properly setup -- the index is toplevel-relative.
-       chdir($worktree);
-
-       # Setup temp directories
-       my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 0, TMPDIR => 1);
-       my $ldir = "$tmpdir/left";
-       my $rdir = "$tmpdir/right";
-       mkpath($ldir) or exit_cleanup($tmpdir, 1);
-       mkpath($rdir) or exit_cleanup($tmpdir, 1);
-
-       # Populate the left and right directories based on each index file
-       my ($inpipe, $ctx);
-       $ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
-       ($inpipe, $ctx) =
-               Git::command_input_pipe('update-index', '-z', '--index-info');
-       print($inpipe $lindex);
-       Git::command_close_pipe($inpipe, $ctx);
-
-       my $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
-       exit_cleanup($tmpdir, $rc) if $rc != 0;
-
-       $ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
-       ($inpipe, $ctx) =
-               Git::command_input_pipe('update-index', '-z', '--index-info');
-       print($inpipe $rindex);
-       Git::command_close_pipe($inpipe, $ctx);
-
-       $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
-       exit_cleanup($tmpdir, $rc) if $rc != 0;
-
-       $ENV{GIT_INDEX_FILE} = "$tmpdir/wtindex";
-       ($inpipe, $ctx) =
-               Git::command_input_pipe('update-index', '--info-only', '-z', '--index-info');
-       print($inpipe $wtindex);
-       Git::command_close_pipe($inpipe, $ctx);
-
-       # If $GIT_DIR was explicitly set just for the update/checkout
-       # commands, then it should be unset before continuing.
-       delete($ENV{GIT_INDEX_FILE});
-
-       # Changes in the working tree need special treatment since they are
-       # not part of the index.
-       for my $file (@files) {
-               my $dir = dirname($file);
-               unless (-d "$rdir/$dir") {
-                       mkpath("$rdir/$dir") or
-                       exit_cleanup($tmpdir, 1);
-               }
-               if ($symlinks) {
-                       symlink("$worktree/$file", "$rdir/$file") or
-                       exit_cleanup($tmpdir, 1);
-               } else {
-                       copy($file, "$rdir/$file") or
-                       exit_cleanup($tmpdir, 1);
-
-                       my $mode = stat($file)->mode;
-                       chmod($mode, "$rdir/$file") or
-                       exit_cleanup($tmpdir, 1);
-               }
-       }
-
-       # Changes to submodules require special treatment. This loop writes a
-       # temporary file to both the left and right directories to show the
-       # change in the recorded SHA1 for the submodule.
-       for my $path (keys %submodule) {
-               my $ok = 0;
-               if (defined($submodule{$path}{left})) {
-                       $ok = write_to_file("$ldir/$path",
-                               "Subproject commit $submodule{$path}{left}");
-               }
-               if (defined($submodule{$path}{right})) {
-                       $ok = write_to_file("$rdir/$path",
-                               "Subproject commit $submodule{$path}{right}");
-               }
-               exit_cleanup($tmpdir, 1) if not $ok;
-       }
-
-       # Symbolic links require special treatment. The standard "git diff"
-       # shows only the link itself, not the contents of the link target.
-       # This loop replicates that behavior.
-       for my $path (keys %symlink) {
-               my $ok = 0;
-               if (defined($symlink{$path}{left})) {
-                       $ok = write_to_file("$ldir/$path",
-                                       $symlink{$path}{left});
-               }
-               if (defined($symlink{$path}{right})) {
-                       $ok = write_to_file("$rdir/$path",
-                                       $symlink{$path}{right});
-               }
-               exit_cleanup($tmpdir, 1) if not $ok;
-       }
-
-       return ($ldir, $rdir, $tmpdir, @files);
-}
-
-sub write_to_file
-{
-       my $path = shift;
-       my $value = shift;
-
-       # Make sure the path to the file exists
-       my $dir = dirname($path);
-       unless (-d "$dir") {
-               mkpath("$dir") or return 0;
-       }
-
-       # If the file already exists in that location, delete it.  This
-       # is required in the case of symbolic links.
-       unlink($path);
-
-       open(my $fh, '>', $path) or return 0;
-       print($fh $value);
-       close($fh);
-
-       return 1;
-}
-
-sub main
-{
-       # parse command-line options. all unrecognized options and arguments
-       # are passed through to the 'git diff' command.
-       my %opts = (
-               difftool_cmd => undef,
-               dirdiff => undef,
-               extcmd => undef,
-               gui => undef,
-               help => undef,
-               prompt => undef,
-               symlinks => $^O ne 'cygwin' &&
-                               $^O ne 'MSWin32' && $^O ne 'msys',
-               tool_help => undef,
-               trust_exit_code => undef,
-       );
-       GetOptions('g|gui!' => \$opts{gui},
-               'd|dir-diff' => \$opts{dirdiff},
-               'h' => \$opts{help},
-               'prompt!' => \$opts{prompt},
-               'y' => sub { $opts{prompt} = 0; },
-               'symlinks' => \$opts{symlinks},
-               'no-symlinks' => sub { $opts{symlinks} = 0; },
-               't|tool:s' => \$opts{difftool_cmd},
-               'tool-help' => \$opts{tool_help},
-               'trust-exit-code' => \$opts{trust_exit_code},
-               'no-trust-exit-code' => sub { $opts{trust_exit_code} = 0; },
-               'x|extcmd:s' => \$opts{extcmd});
-
-       if (defined($opts{help})) {
-               usage(0);
-       }
-       if (defined($opts{tool_help})) {
-               print_tool_help();
-       }
-       if (defined($opts{difftool_cmd})) {
-               if (length($opts{difftool_cmd}) > 0) {
-                       $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd};
-               } else {
-                       print __("No <tool> given for --tool=<tool>\n");
-                       usage(1);
-               }
-       }
-       if (defined($opts{extcmd})) {
-               if (length($opts{extcmd}) > 0) {
-                       $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd};
-               } else {
-                       print __("No <cmd> given for --extcmd=<cmd>\n");
-                       usage(1);
-               }
-       }
-       if ($opts{gui}) {
-               my $guitool = Git::config('diff.guitool');
-               if (defined($guitool) && length($guitool) > 0) {
-                       $ENV{GIT_DIFF_TOOL} = $guitool;
-               }
-       }
-
-       if (!defined $opts{trust_exit_code}) {
-               $opts{trust_exit_code} = Git::config_bool('difftool.trustExitCode');
-       }
-       if ($opts{trust_exit_code}) {
-               $ENV{GIT_DIFFTOOL_TRUST_EXIT_CODE} = 'true';
-       } else {
-               $ENV{GIT_DIFFTOOL_TRUST_EXIT_CODE} = 'false';
-       }
-
-       # In directory diff mode, 'git-difftool--helper' is called once
-       # to compare the a/b directories.  In file diff mode, 'git diff'
-       # will invoke a separate instance of 'git-difftool--helper' for
-       # each file that changed.
-       if (defined($opts{dirdiff})) {
-               dir_diff($opts{extcmd}, $opts{symlinks});
-       } else {
-               file_diff($opts{prompt});
-       }
-}
-
-sub dir_diff
-{
-       my ($extcmd, $symlinks) = @_;
-       my $rc;
-       my $error = 0;
-       my $repo = Git->repository();
-       my $repo_path = $repo->repo_path();
-       my $worktree = $repo->wc_path();
-       $worktree =~ s|/$||; # Avoid double slashes in symlink targets
-       my ($a, $b, $tmpdir, @files) = setup_dir_diff($worktree, $symlinks);
-
-       if (defined($extcmd)) {
-               $rc = system($extcmd, $a, $b);
-       } else {
-               $ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
-               $rc = system('git', 'difftool--helper', $a, $b);
-       }
-       # If the diff including working copy files and those
-       # files were modified during the diff, then the changes
-       # should be copied back to the working tree.
-       # Do not copy back files when symlinks are used and the
-       # external tool did not replace the original link with a file.
-       #
-       # These hashes are loaded lazily since they aren't needed
-       # in the common case of --symlinks and the difftool updating
-       # files through the symlink.
-       my %wt_modified;
-       my %tmp_modified;
-       my $indices_loaded = 0;
-
-       for my $file (@files) {
-               next if $symlinks && -l "$b/$file";
-               next if ! -f "$b/$file";
-
-               if (!$indices_loaded) {
-                       %wt_modified = changed_files(
-                               $repo_path, "$tmpdir/wtindex", $worktree);
-                       %tmp_modified = changed_files(
-                               $repo_path, "$tmpdir/wtindex", $b);
-                       $indices_loaded = 1;
-               }
-
-               if (exists $wt_modified{$file} and exists $tmp_modified{$file}) {
-                       warn sprintf(__(
-                               "warning: Both files modified:\n" .
-                               "'%s/%s' and '%s/%s'.\n" .
-                               "warning: Working tree file has been left.\n" .
-                               "warning:\n"), $worktree, $file, $b, $file);
-                       $error = 1;
-               } elsif (exists $tmp_modified{$file}) {
-                       my $mode = stat("$b/$file")->mode;
-                       copy("$b/$file", $file) or
-                       exit_cleanup($tmpdir, 1);
-
-                       chmod($mode, $file) or
-                       exit_cleanup($tmpdir, 1);
-               }
-       }
-       if ($error) {
-               warn sprintf(__(
-                       "warning: Temporary files exist in '%s'.\n" .
-                       "warning: You may want to cleanup or recover these.\n"), $tmpdir);
-               exit(1);
-       } else {
-               exit_cleanup($tmpdir, $rc);
-       }
-}
-
-sub file_diff
-{
-       my ($prompt) = @_;
-
-       if (defined($prompt)) {
-               if ($prompt) {
-                       $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
-               } else {
-                       $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
-               }
-       }
-
-       $ENV{GIT_PAGER} = '';
-       $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
-
-       # ActiveState Perl for Win32 does not implement POSIX semantics of
-       # exec* system call. It just spawns the given executable and finishes
-       # the starting program, exiting with code 0.
-       # system will at least catch the errors returned by git diff,
-       # allowing the caller of git difftool better handling of failures.
-       my $rc = system('git', 'diff', @ARGV);
-       exit($rc | ($rc >> 8));
-}
-
-main();
index 7bda915bd0c9af438d7303abaabf53fc15f9a5dd..9695d2ed3ec2071ed7a279eac785b7e280b718e4 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -674,7 +674,7 @@ def gitConfigInt(key):
 def gitConfigList(key):
     if not _gitConfig.has_key(key):
         s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
-        _gitConfig[key] = s.strip().split(os.linesep)
+        _gitConfig[key] = s.strip().splitlines()
         if _gitConfig[key] == ['']:
             _gitConfig[key] = []
     return _gitConfig[key]
diff --git a/git-relink.perl b/git-relink.perl
deleted file mode 100755 (executable)
index 236a352..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/perl
-# Copyright 2005, Ryan Anderson <ryan@michonline.com>
-# Distribution permitted under the GPL v2, as distributed
-# by the Free Software Foundation.
-# Later versions of the GPL at the discretion of Linus Torvalds
-#
-# Scan two git object-trees, and hardlink any common objects between them.
-
-use 5.008;
-use strict;
-use warnings;
-use Getopt::Long;
-
-sub get_canonical_form($);
-sub do_scan_directory($$$);
-sub compare_two_files($$);
-sub usage();
-sub link_two_files($$);
-
-# stats
-my $total_linked = 0;
-my $total_already = 0;
-my ($linked,$already);
-
-my $fail_on_different_sizes = 0;
-my $help = 0;
-GetOptions("safe" => \$fail_on_different_sizes,
-          "help" => \$help);
-
-usage() if $help;
-
-my (@dirs) = @ARGV;
-
-usage() if (!defined $dirs[0] || !defined $dirs[1]);
-
-$_ = get_canonical_form($_) foreach (@dirs);
-
-my $master_dir = pop @dirs;
-
-opendir(D,$master_dir . "objects/")
-       or die "Failed to open $master_dir/objects/ : $!";
-
-my @hashdirs = grep { ($_ eq 'pack') || /^[0-9a-f]{2}$/ } readdir(D);
-
-foreach my $repo (@dirs) {
-       $linked = 0;
-       $already = 0;
-       printf("Searching '%s' and '%s' for common objects and hardlinking them...\n",
-               $master_dir,$repo);
-
-       foreach my $hashdir (@hashdirs) {
-               do_scan_directory($master_dir, $hashdir, $repo);
-       }
-
-       printf("Linked %d files, %d were already linked.\n",$linked, $already);
-
-       $total_linked += $linked;
-       $total_already += $already;
-}
-
-printf("Totals: Linked %d files, %d were already linked.\n",
-       $total_linked, $total_already);
-
-
-sub do_scan_directory($$$) {
-       my ($srcdir, $subdir, $dstdir) = @_;
-
-       my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
-       my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
-
-       opendir(S,$sfulldir)
-               or die "Failed to opendir $sfulldir: $!";
-
-       foreach my $file (grep(!/\.{1,2}$/, readdir(S))) {
-               my $sfilename = $sfulldir . $file;
-               my $dfilename = $dfulldir . $file;
-
-               compare_two_files($sfilename,$dfilename);
-
-       }
-       closedir(S);
-}
-
-sub compare_two_files($$) {
-       my ($sfilename, $dfilename) = @_;
-
-       # Perl's stat returns relevant information as follows:
-       # 0 = dev number
-       # 1 = inode number
-       # 7 = size
-       my @sstatinfo = stat($sfilename);
-       my @dstatinfo = stat($dfilename);
-
-       if (@sstatinfo == 0 && @dstatinfo == 0) {
-               die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!);
-
-       } elsif (@dstatinfo == 0) {
-               return;
-       }
-
-       if ( ($sstatinfo[0] == $dstatinfo[0]) &&
-            ($sstatinfo[1] != $dstatinfo[1])) {
-               if ($sstatinfo[7] == $dstatinfo[7]) {
-                       link_two_files($sfilename, $dfilename);
-
-               } else {
-                       my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
-                               $sfilename, $dfilename);
-                       if ($fail_on_different_sizes) {
-                               die $err;
-                       } else {
-                               warn $err;
-                       }
-               }
-
-       } elsif ( ($sstatinfo[0] == $dstatinfo[0]) &&
-            ($sstatinfo[1] == $dstatinfo[1])) {
-               $already++;
-       }
-}
-
-sub get_canonical_form($) {
-       my $dir = shift;
-       my $original = $dir;
-
-       die "$dir is not a directory." unless -d $dir;
-
-       $dir .= "/" unless $dir =~ m#/$#;
-       $dir .= ".git/" unless $dir =~ m#\.git/$#;
-
-       die "$original does not have a .git/ subdirectory.\n" unless -d $dir;
-
-       return $dir;
-}
-
-sub link_two_files($$) {
-       my ($sfilename, $dfilename) = @_;
-       my $tmpdname = sprintf("%s.old",$dfilename);
-       rename($dfilename,$tmpdname)
-               or die sprintf("Failure renaming %s to %s: %s",
-                       $dfilename, $tmpdname, $!);
-
-       if (! link($sfilename,$dfilename)) {
-               my $failtxt = "";
-               unless (rename($tmpdname,$dfilename)) {
-                       $failtxt = sprintf(
-                               "Git Repository containing %s is probably corrupted, " .
-                               "please copy '%s' to '%s' to fix.\n",
-                               $tmpdname, $dfilename);
-               }
-
-               die sprintf("Failed to link %s to %s: %s\n%s" .
-                       $sfilename, $dfilename,
-                       $!, $dfilename, $failtxt);
-       }
-
-       unlink($tmpdname)
-               or die sprintf("Unlink of %s failed: %s\n",
-                       $dfilename, $!);
-
-       $linked++;
-}
-
-
-sub usage() {
-       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" .
-               "Stops if two objects with the same hash exist but " .
-               "have different sizes.  Default is to warn and continue.\n");
-       exit(1);
-}
index 9788175979273f8748635e2f5078f3ca8d4b7500..136e26a2c8d4ca1e835e2873f3fd994864dc135e 100755 (executable)
@@ -204,8 +204,14 @@ cmd_add()
                        tstart
                        s|/*$||
                ')
-       git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
-       die "$(eval_gettext "'\$sm_path' already exists in the index")"
+       if test -z "$force"
+       then
+               git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
+               die "$(eval_gettext "'\$sm_path' already exists in the index")"
+       else
+               git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 &&
+               die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")"
+       fi
 
        if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1
        then
@@ -607,7 +613,10 @@ cmd_update()
                if test $just_cloned -eq 1
                then
                        subsha1=
-                       update_module=checkout
+                       case "$update_module" in
+                       merge | rebase | none)
+                               update_module=checkout ;;
+                       esac
                else
                        subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
diff --git a/git.c b/git.c
index b367cf6686029a7b54ff4b3ca3d63be2409904b3..c887272b129968db161a152ce8a91aca4439ca3c 100644 (file)
--- a/git.c
+++ b/git.c
@@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {
        { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
        { "diff-index", cmd_diff_index, RUN_SETUP },
        { "diff-tree", cmd_diff_tree, RUN_SETUP },
+       { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
        { "fast-export", cmd_fast_export, RUN_SETUP },
        { "fetch", cmd_fetch, RUN_SETUP },
        { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
@@ -471,7 +472,7 @@ static struct cmd_struct commands[] = {
        { "prune-packed", cmd_prune_packed, RUN_SETUP },
        { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
        { "push", cmd_push, RUN_SETUP },
-       { "read-tree", cmd_read_tree, RUN_SETUP },
+       { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
        { "receive-pack", cmd_receive_pack },
        { "reflog", cmd_reflog, RUN_SETUP },
        { "remote", cmd_remote, RUN_SETUP },
index ea68885ad5b73aa63acc936eee52a61252420892..d2d4fd3a656e3f4953aea5eda3eea435f37f237e 100644 (file)
@@ -1,8 +1,9 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
-#define GPG_VERIFY_VERBOSE     1
-#define GPG_VERIFY_RAW         2
+#define GPG_VERIFY_VERBOSE             1
+#define GPG_VERIFY_RAW                 2
+#define GPG_VERIFY_OMIT_STATUS 4
 
 struct signature_check {
        char *payload;
diff --git a/graph.c b/graph.c
index d4e8519c904df6e18869de527f7fe6d57adf2a26..0649007704ac635af163a4e1016121744a951d1c 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -3,6 +3,7 @@
 #include "color.h"
 #include "graph.h"
 #include "revision.h"
+#include "argv-array.h"
 
 /* Internal API */
 
@@ -79,6 +80,26 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
 static const char **column_colors;
 static unsigned short column_colors_max;
 
+static void parse_graph_colors_config(struct argv_array *colors, const char *string)
+{
+       const char *end, *start;
+
+       start = string;
+       end = string + strlen(string);
+       while (start < end) {
+               const char *comma = strchrnul(start, ',');
+               char color[COLOR_MAXLEN];
+
+               if (!color_parse_mem(start, comma - start, color))
+                       argv_array_push(colors, color);
+               else
+                       warning(_("ignore invalid color '%.*s' in log.graphColors"),
+                               (int)(comma - start), start);
+               start = comma + 1;
+       }
+       argv_array_push(colors, GIT_COLOR_RESET);
+}
+
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
        column_colors = colors;
@@ -238,9 +259,22 @@ struct git_graph *graph_init(struct rev_info *opt)
 {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
-       if (!column_colors)
-               graph_set_column_colors(column_colors_ansi,
-                                       column_colors_ansi_max);
+       if (!column_colors) {
+               char *string;
+               if (git_config_get_string("log.graphcolors", &string)) {
+                       /* not configured -- use default */
+                       graph_set_column_colors(column_colors_ansi,
+                                               column_colors_ansi_max);
+               } else {
+                       static struct argv_array custom_colors = ARGV_ARRAY_INIT;
+                       argv_array_clear(&custom_colors);
+                       parse_graph_colors_config(&custom_colors, string);
+                       free(string);
+                       /* graph_set_column_colors takes a max-index, not a count */
+                       graph_set_column_colors(custom_colors.argv,
+                                               custom_colors.argc - 1);
+               }
+       }
 
        graph->commit = NULL;
        graph->revs = opt;
diff --git a/help.c b/help.c
index 53e2a67e0052b7abb9f01e075f76c4eb5f35cbfc..bc6cd19cf3a5e4300e32cbdd437b2d9231fd2d89 100644 (file)
--- a/help.c
+++ b/help.c
@@ -105,7 +105,22 @@ static int is_executable(const char *name)
                return 0;
 
 #if defined(GIT_WINDOWS_NATIVE)
-{      /* cannot trust the executable bit, peek into the file instead */
+       /*
+        * On Windows there is no executable bit. The file extension
+        * indicates whether it can be run as an executable, and Git
+        * has special-handling to detect scripts and launch them
+        * through the indicated script interpreter. We test for the
+        * file extension first because virus scanners may make
+        * it quite expensive to open many files.
+        */
+       if (ends_with(name, ".exe"))
+               return S_IXUSR;
+
+{
+       /*
+        * Now that we know it does not have an executable extension,
+        * peek into the file instead.
+        */
        char buf[3] = { 0 };
        int n;
        int fd = open(name, O_RDONLY);
@@ -113,8 +128,8 @@ static int is_executable(const char *name)
        if (fd >= 0) {
                n = read(fd, buf, 2);
                if (n == 2)
-                       /* DOS executables start with "MZ" */
-                       if (!strcmp(buf, "#!") || !strcmp(buf, "MZ"))
+                       /* look for a she-bang */
+                       if (!strcmp(buf, "#!"))
                                st.st_mode |= S_IXUSR;
                close(fd);
        }
index 7a9a7de91e6e84c429b0d485301c3500e404dd2f..9054369dd0c6ddfd35794fc63f0e0296ec0c9c91 100644 (file)
@@ -503,7 +503,6 @@ int index_name_pos(const struct index_state *istate, const char *name, int namel
        return index_name_stage_pos(istate, name, namelen, 0);
 }
 
-/* Remove entry, return true if there are more entries to go.. */
 int remove_index_entry_at(struct index_state *istate, int pos)
 {
        struct cache_entry *ce = istate->cache[pos];
index 1a978405e6b97d1f57d75b867ddeae30f091ef19..3820b21cc75f5e7fd4b1b1851be0aaa9e3268d7d 100644 (file)
@@ -1361,7 +1361,7 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
        return ref;
 }
 
-static int filter_ref_kind(struct ref_filter *filter, const char *refname)
+static int ref_kind_from_refname(const char *refname)
 {
        unsigned int i;
 
@@ -1374,11 +1374,7 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)
                { "refs/tags/", FILTER_REFS_TAGS}
        };
 
-       if (filter->kind == FILTER_REFS_BRANCHES ||
-           filter->kind == FILTER_REFS_REMOTES ||
-           filter->kind == FILTER_REFS_TAGS)
-               return filter->kind;
-       else if (!strcmp(refname, "HEAD"))
+       if (!strcmp(refname, "HEAD"))
                return FILTER_REFS_DETACHED_HEAD;
 
        for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
@@ -1389,6 +1385,15 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)
        return FILTER_REFS_OTHERS;
 }
 
+static int filter_ref_kind(struct ref_filter *filter, const char *refname)
+{
+       if (filter->kind == FILTER_REFS_BRANCHES ||
+           filter->kind == FILTER_REFS_REMOTES ||
+           filter->kind == FILTER_REFS_TAGS)
+               return filter->kind;
+       return ref_kind_from_refname(refname);
+}
+
 /*
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
@@ -1589,8 +1594,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
        return (s->reverse) ? -cmp : cmp;
 }
 
-static struct ref_sorting *ref_sorting;
-static int compare_refs(const void *a_, const void *b_)
+static int compare_refs(const void *a_, const void *b_, void *ref_sorting)
 {
        struct ref_array_item *a = *((struct ref_array_item **)a_);
        struct ref_array_item *b = *((struct ref_array_item **)b_);
@@ -1606,8 +1610,7 @@ static int compare_refs(const void *a_, const void *b_)
 
 void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
 {
-       ref_sorting = sorting;
-       QSORT(array->items, array->nr, compare_refs);
+       QSORT_S(array->items, array->nr, compare_refs, sorting);
 }
 
 static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
@@ -1671,6 +1674,16 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
        putchar('\n');
 }
 
+void pretty_print_ref(const char *name, const unsigned char *sha1,
+               const char *format)
+{
+       struct ref_array_item *ref_item;
+       ref_item = new_ref_array_item(name, sha1, 0);
+       ref_item->kind = ref_kind_from_refname(name);
+       show_ref_array_item(ref_item, format, 0);
+       free_array_item(ref_item);
+}
+
 /*  If no sorting option is given, use refname to sort as default */
 struct ref_sorting *ref_default_sorting(void)
 {
index fc55fa3574620bfdc76b2997afc56909528e2275..7b05592baf00661a08f973d0a218f7e0afd99453 100644 (file)
@@ -109,4 +109,11 @@ struct ref_sorting *ref_default_sorting(void);
 /*  Function to parse --merged and --no-merged options */
 int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
 
+/*
+ * Print a single ref, outside of any ref-filter. Note that the
+ * name must be a fully qualified refname.
+ */
+void pretty_print_ref(const char *name, const unsigned char *sha1,
+               const char *format);
+
 #endif /*  REF_FILTER_H  */
index d5eaec73742e74738c522cdc36ba9bf811b158b2..bf1bf2309128bf886eac16959b8162b9990c98d7 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -255,6 +255,7 @@ static void read_remotes_file(struct remote *remote)
 
        if (!f)
                return;
+       remote->configured_in_repo = 1;
        remote->origin = REMOTE_REMOTES;
        while (strbuf_getline(&buf, f) != EOF) {
                const char *v;
@@ -289,6 +290,7 @@ static void read_branches_file(struct remote *remote)
                return;
        }
 
+       remote->configured_in_repo = 1;
        remote->origin = REMOTE_BRANCHES;
 
        /*
@@ -371,6 +373,8 @@ static int handle_config(const char *key, const char *value, void *cb)
        }
        remote = make_remote(name, namelen);
        remote->origin = REMOTE_CONFIG;
+       if (current_config_scope() == CONFIG_SCOPE_REPO)
+               remote->configured_in_repo = 1;
        if (!strcmp(subkey, "mirror"))
                remote->mirror = git_config_bool(key, value);
        else if (!strcmp(subkey, "skipdefaultupdate"))
@@ -714,9 +718,13 @@ struct remote *pushremote_get(const char *name)
        return remote_get_1(name, pushremote_for_branch);
 }
 
-int remote_is_configured(struct remote *remote)
+int remote_is_configured(struct remote *remote, int in_repo)
 {
-       return remote && remote->origin;
+       if (!remote)
+               return 0;
+       if (in_repo)
+               return remote->configured_in_repo;
+       return !!remote->origin;
 }
 
 int for_each_remote(each_remote_fn fn, void *priv)
index 924881169d9f6c5b9b09e2434d964f62e0e28d09..a5bbbe0ef965c5e62be50e8ee0c03794e393cc8c 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -15,7 +15,7 @@ struct remote {
        struct hashmap_entry ent;  /* must be first */
 
        const char *name;
-       int origin;
+       int origin, configured_in_repo;
 
        const char *foreign_vcs;
 
@@ -60,7 +60,7 @@ struct remote {
 
 struct remote *remote_get(const char *name);
 struct remote *pushremote_get(const char *name);
-int remote_is_configured(struct remote *remote);
+int remote_is_configured(struct remote *remote, int in_repo);
 
 typedef int each_remote_fn(struct remote *remote, void *priv);
 int for_each_remote(each_remote_fn fn, void *priv);
index 73bfba7ef94e678afa830550199f14b6f5ca5ad0..5227f78aeaae7e76bfc85d4d7ca2886b2b52215e 100644 (file)
@@ -871,8 +871,14 @@ const char *find_hook(const char *name)
 
        strbuf_reset(&path);
        strbuf_git_path(&path, "hooks/%s", name);
-       if (access(path.buf, X_OK) < 0)
+       if (access(path.buf, X_OK) < 0) {
+#ifdef STRIP_EXTENSION
+               strbuf_addstr(&path, STRIP_EXTENSION);
+               if (access(path.buf, X_OK) >= 0)
+                       return path.buf;
+#endif
                return NULL;
+       }
        return path.buf;
 }
 
index 9adb7bbf1d4815d9e4f67b66e90c9ec327b2fb72..1f729b053bbc6f544fe0b3e8736113082894b24d 100644 (file)
@@ -17,6 +17,8 @@
 #include "argv-array.h"
 #include "quote.h"
 #include "trailer.h"
+#include "log-tree.h"
+#include "wt-status.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -30,31 +32,117 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
 static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
 
+static GIT_PATH_FUNC(rebase_path, "rebase-merge")
+/*
+ * The file containing rebase commands, comments, and empty lines.
+ * This file is created by "git rebase -i" then edited by the user. As
+ * the lines are processed, they are removed from the front of this
+ * file and written to the tail of 'done'.
+ */
+static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
+/*
+ * The rebase command lines that have already been processed. A line
+ * is moved here when it is first handled, before any associated user
+ * actions.
+ */
+static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done")
+/*
+ * The file to keep track of how many commands were already processed (e.g.
+ * for the prompt).
+ */
+static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum");
+/*
+ * The file to keep track of how many commands are to be processed in total
+ * (e.g. for the prompt).
+ */
+static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end");
+/*
+ * The commit message that is planned to be used for any changes that
+ * need to be committed following a user interaction.
+ */
+static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message")
+/*
+ * The file into which is accumulated the suggested commit message for
+ * squash/fixup commands. When the first of a series of squash/fixups
+ * is seen, the file is created and the commit message from the
+ * previous commit and from the first squash/fixup commit are written
+ * to it. The commit message for each subsequent squash/fixup commit
+ * is appended to the file as it is processed.
+ *
+ * The first line of the file is of the form
+ *     # This is a combination of $count commits.
+ * where $count is the number of commits whose messages have been
+ * written to the file so far (including the initial "pick" commit).
+ * Each time that a commit message is processed, this line is read and
+ * updated. It is deleted just before the combined commit is made.
+ */
+static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
+/*
+ * If the current series of squash/fixups has not yet included a squash
+ * command, then this file exists and holds the commit message of the
+ * original "pick" commit.  (If the series ends without a "squash"
+ * command, then this can be used as the commit message of the combined
+ * commit without opening the editor.)
+ */
+static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup")
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
  * GIT_AUTHOR_DATE that will be used for the commit that is currently
  * being rebased.
  */
 static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
+/*
+ * When an "edit" rebase command is being processed, the SHA1 of the
+ * commit to be edited is recorded in this file.  When "git rebase
+ * --continue" is executed, if there are any staged changes then they
+ * will be amended to the HEAD commit, but only provided the HEAD
+ * commit is still the commit to be edited.  When any other rebase
+ * command is processed, this file is deleted.
+ */
+static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
+/*
+ * When we stop at a given patch via the "edit" command, this file contains
+ * the abbreviated commit name of the corresponding patch.
+ */
+static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
+/*
+ * For the post-rewrite hook, we make a list of rewritten commits and
+ * their new sha1s.  The rewritten-pending list keeps the sha1s of
+ * commits that have been processed, but not committed yet,
+ * e.g. because they are waiting for a 'squash' command.
+ */
+static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
+static GIT_PATH_FUNC(rebase_path_rewritten_pending,
+       "rebase-merge/rewritten-pending")
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line (and are only consumed, not modified, by the sequencer).
  */
 static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
+static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
+static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
+static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
+static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
+static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
+static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
+static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 
-/* We will introduce the 'interactive rebase' mode later */
 static inline int is_rebase_i(const struct replay_opts *opts)
 {
-       return 0;
+       return opts->action == REPLAY_INTERACTIVE_REBASE;
 }
 
 static const char *get_dir(const struct replay_opts *opts)
 {
+       if (is_rebase_i(opts))
+               return rebase_path();
        return git_path_seq_dir();
 }
 
 static const char *get_todo_path(const struct replay_opts *opts)
 {
+       if (is_rebase_i(opts))
+               return rebase_path_todo();
        return git_path_todo_file();
 }
 
@@ -122,7 +210,15 @@ int sequencer_remove_state(struct replay_opts *opts)
 
 static const char *action_name(const struct replay_opts *opts)
 {
-       return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick");
+       switch (opts->action) {
+       case REPLAY_REVERT:
+               return N_("revert");
+       case REPLAY_PICK:
+               return N_("cherry-pick");
+       case REPLAY_INTERACTIVE_REBASE:
+               return N_("rebase -i");
+       }
+       die(_("Unknown action: %d"), opts->action);
 }
 
 struct commit_message {
@@ -347,6 +443,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        o.ancestor = base ? base_label : "(empty tree)";
        o.branch1 = "HEAD";
        o.branch2 = next ? next_label : "(empty tree)";
+       if (is_rebase_i(opts))
+               o.buffer_output = 2;
 
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
@@ -358,13 +456,17 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        clean = merge_trees(&o,
                            head_tree,
                            next_tree, base_tree, &result);
+       if (is_rebase_i(opts) && clean <= 0)
+               fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
        if (clean < 0)
                return clean;
 
        if (active_cache_changed &&
            write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
-               /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+               /* TRANSLATORS: %s will be "revert", "cherry-pick" or
+                * "rebase -i".
+                */
                return error(_("%s: Unable to write new index file"),
                        _(action_name(opts)));
        rollback_lock_file(&index_lock);
@@ -409,19 +511,64 @@ static int is_index_unchanged(void)
        return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
 }
 
+static int write_author_script(const char *message)
+{
+       struct strbuf buf = STRBUF_INIT;
+       const char *eol;
+       int res;
+
+       for (;;)
+               if (!*message || starts_with(message, "\n")) {
+missing_author:
+                       /* Missing 'author' line? */
+                       unlink(rebase_path_author_script());
+                       return 0;
+               } else if (skip_prefix(message, "author ", &message))
+                       break;
+               else if ((eol = strchr(message, '\n')))
+                       message = eol + 1;
+               else
+                       goto missing_author;
+
+       strbuf_addstr(&buf, "GIT_AUTHOR_NAME='");
+       while (*message && *message != '\n' && *message != '\r')
+               if (skip_prefix(message, " <", &message))
+                       break;
+               else if (*message != '\'')
+                       strbuf_addch(&buf, *(message++));
+               else
+                       strbuf_addf(&buf, "'\\\\%c'", *(message++));
+       strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
+       while (*message && *message != '\n' && *message != '\r')
+               if (skip_prefix(message, "> ", &message))
+                       break;
+               else if (*message != '\'')
+                       strbuf_addch(&buf, *(message++));
+               else
+                       strbuf_addf(&buf, "'\\\\%c'", *(message++));
+       strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
+       while (*message && *message != '\n' && *message != '\r')
+               if (*message != '\'')
+                       strbuf_addch(&buf, *(message++));
+               else
+                       strbuf_addf(&buf, "'\\\\%c'", *(message++));
+       res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
+       strbuf_release(&buf);
+       return res;
+}
+
 /*
- * Read the author-script file into an environment block, ready for use in
- * run_command(), that can be free()d afterwards.
+ * Read a list of environment variable assignments (such as the author-script
+ * file) into an environment block. Returns -1 on error, 0 otherwise.
  */
-static char **read_author_script(void)
+static int read_env_script(struct argv_array *env)
 {
        struct strbuf script = STRBUF_INIT;
        int i, count = 0;
-       char *p, *p2, **env;
-       size_t env_size;
+       char *p, *p2;
 
        if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
-               return NULL;
+               return -1;
 
        for (p = script.buf; *p; p++)
                if (skip_prefix(p, "'\\\\''", (const char **)&p2))
@@ -433,19 +580,12 @@ static char **read_author_script(void)
                        count++;
                }
 
-       env_size = (count + 1) * sizeof(*env);
-       strbuf_grow(&script, env_size);
-       memmove(script.buf + env_size, script.buf, script.len);
-       p = script.buf + env_size;
-       env = (char **)strbuf_detach(&script, NULL);
-
-       for (i = 0; i < count; i++) {
-               env[i] = p;
+       for (i = 0, p = script.buf; i < count; i++) {
+               argv_array_push(env, p);
                p += strlen(p) + 1;
        }
-       env[count] = NULL;
 
-       return env;
+       return 0;
 }
 
 static const char staged_changes_advice[] =
@@ -478,14 +618,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                          int allow_empty, int edit, int amend,
                          int cleanup_commit_message)
 {
-       char **env = NULL;
-       struct argv_array array;
-       int rc;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
 
+       cmd.git_cmd = 1;
+
        if (is_rebase_i(opts)) {
-               env = read_author_script();
-               if (!env) {
+               if (!edit) {
+                       cmd.stdout_to_stderr = 1;
+                       cmd.err = -1;
+               }
+
+               if (read_env_script(&cmd.env_array)) {
                        const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
                        return error(_(staged_changes_advice),
@@ -493,39 +637,47 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                }
        }
 
-       argv_array_init(&array);
-       argv_array_push(&array, "commit");
-       argv_array_push(&array, "-n");
+       argv_array_push(&cmd.args, "commit");
+       argv_array_push(&cmd.args, "-n");
 
        if (amend)
-               argv_array_push(&array, "--amend");
+               argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
-               argv_array_pushf(&array, "-S%s", opts->gpg_sign);
+               argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
        if (opts->signoff)
-               argv_array_push(&array, "-s");
+               argv_array_push(&cmd.args, "-s");
        if (defmsg)
-               argv_array_pushl(&array, "-F", defmsg, NULL);
+               argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
        if (cleanup_commit_message)
-               argv_array_push(&array, "--cleanup=strip");
+               argv_array_push(&cmd.args, "--cleanup=strip");
        if (edit)
-               argv_array_push(&array, "-e");
+               argv_array_push(&cmd.args, "-e");
        else if (!cleanup_commit_message &&
                 !opts->signoff && !opts->record_origin &&
                 git_config_get_value("commit.cleanup", &value))
-               argv_array_push(&array, "--cleanup=verbatim");
+               argv_array_push(&cmd.args, "--cleanup=verbatim");
 
        if (allow_empty)
-               argv_array_push(&array, "--allow-empty");
+               argv_array_push(&cmd.args, "--allow-empty");
 
        if (opts->allow_empty_message)
-               argv_array_push(&array, "--allow-empty-message");
+               argv_array_push(&cmd.args, "--allow-empty-message");
 
-       rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL,
-                       (const char *const *)env);
-       argv_array_clear(&array);
-       free(env);
+       if (cmd.err == -1) {
+               /* hide stderr on success */
+               struct strbuf buf = STRBUF_INIT;
+               int rc = pipe_command(&cmd,
+                                     NULL, 0,
+                                     /* stdout is already redirected */
+                                     NULL, 0,
+                                     &buf, 0);
+               if (rc)
+                       fputs(buf.buf, stderr);
+               strbuf_release(&buf);
+               return rc;
+       }
 
-       return rc;
+       return run_command(&cmd);
 }
 
 static int is_original_commit_empty(struct commit *commit)
@@ -586,33 +738,202 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit)
                return 1;
 }
 
+/*
+ * Note that ordering matters in this enum. Not only must it match the mapping
+ * below, it is also divided into several sections that matter.  When adding
+ * new commands, make sure you add it in the right section.
+ */
 enum todo_command {
+       /* commands that handle commits */
        TODO_PICK = 0,
-       TODO_REVERT
+       TODO_REVERT,
+       TODO_EDIT,
+       TODO_REWORD,
+       TODO_FIXUP,
+       TODO_SQUASH,
+       /* commands that do something else than handling a single commit */
+       TODO_EXEC,
+       /* commands that do nothing but are counted for reporting progress */
+       TODO_NOOP,
+       TODO_DROP,
+       /* comments (not counted for reporting progress) */
+       TODO_COMMENT
 };
 
-static const char *todo_command_strings[] = {
-       "pick",
-       "revert"
+static struct {
+       char c;
+       const char *str;
+} todo_command_info[] = {
+       { 'p', "pick" },
+       { 0,   "revert" },
+       { 'e', "edit" },
+       { 'r', "reword" },
+       { 'f', "fixup" },
+       { 's', "squash" },
+       { 'x', "exec" },
+       { 0,   "noop" },
+       { 'd', "drop" },
+       { 0,   NULL }
 };
 
 static const char *command_to_string(const enum todo_command command)
 {
-       if ((size_t)command < ARRAY_SIZE(todo_command_strings))
-               return todo_command_strings[command];
+       if (command < TODO_COMMENT)
+               return todo_command_info[command].str;
        die("Unknown command: %d", command);
 }
 
+static int is_noop(const enum todo_command command)
+{
+       return TODO_NOOP <= command;
+}
+
+static int is_fixup(enum todo_command command)
+{
+       return command == TODO_FIXUP || command == TODO_SQUASH;
+}
+
+static int update_squash_messages(enum todo_command command,
+               struct commit *commit, struct replay_opts *opts)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int count, res;
+       const char *message, *body;
+
+       if (file_exists(rebase_path_squash_msg())) {
+               struct strbuf header = STRBUF_INIT;
+               char *eol, *p;
+
+               if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
+                       return error(_("could not read '%s'"),
+                               rebase_path_squash_msg());
+
+               p = buf.buf + 1;
+               eol = strchrnul(buf.buf, '\n');
+               if (buf.buf[0] != comment_line_char ||
+                   (p += strcspn(p, "0123456789\n")) == eol)
+                       return error(_("unexpected 1st line of squash message:"
+                                      "\n\n\t%.*s"),
+                                    (int)(eol - buf.buf), buf.buf);
+               count = strtol(p, NULL, 10);
+
+               if (count < 1)
+                       return error(_("invalid 1st line of squash message:\n"
+                                      "\n\t%.*s"),
+                                    (int)(eol - buf.buf), buf.buf);
+
+               strbuf_addf(&header, "%c ", comment_line_char);
+               strbuf_addf(&header,
+                           _("This is a combination of %d commits."), ++count);
+               strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
+               strbuf_release(&header);
+       } else {
+               unsigned char head[20];
+               struct commit *head_commit;
+               const char *head_message, *body;
+
+               if (get_sha1("HEAD", head))
+                       return error(_("need a HEAD to fixup"));
+               if (!(head_commit = lookup_commit_reference(head)))
+                       return error(_("could not read HEAD"));
+               if (!(head_message = get_commit_buffer(head_commit, NULL)))
+                       return error(_("could not read HEAD's commit message"));
+
+               find_commit_subject(head_message, &body);
+               if (write_message(body, strlen(body),
+                                 rebase_path_fixup_msg(), 0)) {
+                       unuse_commit_buffer(head_commit, head_message);
+                       return error(_("cannot write '%s'"),
+                                    rebase_path_fixup_msg());
+               }
+
+               count = 2;
+               strbuf_addf(&buf, "%c ", comment_line_char);
+               strbuf_addf(&buf, _("This is a combination of %d commits."),
+                           count);
+               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addstr(&buf, _("This is the 1st commit message:"));
+               strbuf_addstr(&buf, "\n\n");
+               strbuf_addstr(&buf, body);
+
+               unuse_commit_buffer(head_commit, head_message);
+       }
+
+       if (!(message = get_commit_buffer(commit, NULL)))
+               return error(_("could not read commit message of %s"),
+                            oid_to_hex(&commit->object.oid));
+       find_commit_subject(message, &body);
+
+       if (command == TODO_SQUASH) {
+               unlink(rebase_path_fixup_msg());
+               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addf(&buf, _("This is the commit message #%d:"), count);
+               strbuf_addstr(&buf, "\n\n");
+               strbuf_addstr(&buf, body);
+       } else if (command == TODO_FIXUP) {
+               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+                           count);
+               strbuf_addstr(&buf, "\n\n");
+               strbuf_add_commented_lines(&buf, body, strlen(body));
+       } else
+               return error(_("unknown command: %d"), command);
+       unuse_commit_buffer(commit, message);
+
+       res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
+       strbuf_release(&buf);
+       return res;
+}
+
+static void flush_rewritten_pending(void) {
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char newsha1[20];
+       FILE *out;
+
+       if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 &&
+                       !get_sha1("HEAD", newsha1) &&
+                       (out = fopen(rebase_path_rewritten_list(), "a"))) {
+               char *bol = buf.buf, *eol;
+
+               while (*bol) {
+                       eol = strchrnul(bol, '\n');
+                       fprintf(out, "%.*s %s\n", (int)(eol - bol),
+                                       bol, sha1_to_hex(newsha1));
+                       if (!*eol)
+                               break;
+                       bol = eol + 1;
+               }
+               fclose(out);
+               unlink(rebase_path_rewritten_pending());
+       }
+       strbuf_release(&buf);
+}
+
+static void record_in_rewritten(struct object_id *oid,
+               enum todo_command next_command) {
+       FILE *out = fopen(rebase_path_rewritten_pending(), "a");
+
+       if (!out)
+               return;
+
+       fprintf(out, "%s\n", oid_to_hex(oid));
+       fclose(out);
+
+       if (!is_fixup(next_command))
+               flush_rewritten_pending();
+}
 
 static int do_pick_commit(enum todo_command command, struct commit *commit,
-               struct replay_opts *opts)
+               struct replay_opts *opts, int final_fixup)
 {
+       int edit = opts->edit, cleanup_commit_message = 0;
+       const char *msg_file = edit ? NULL : git_path_merge_msg();
        unsigned char head[20];
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
-       int res, unborn = 0, allow;
+       int res, unborn = 0, amend = 0, allow = 0;
 
        if (opts->no_commit) {
                /*
@@ -632,9 +953,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        }
        discard_cache();
 
-       if (!commit->parents) {
+       if (!commit->parents)
                parent = NULL;
-       }
        else if (commit->parents->next) {
                /* Reverting or cherry-picking a merge commit */
                int cnt;
@@ -658,11 +978,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        else
                parent = commit->parents->item;
 
-       if (opts->allow_ff &&
-           ((parent && !hashcmp(parent->object.oid.hash, head)) ||
-            (!parent && unborn)))
-               return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
+       if (get_message(commit, &msg) != 0)
+               return error(_("cannot get commit message for %s"),
+                       oid_to_hex(&commit->object.oid));
 
+       if (opts->allow_ff && !is_fixup(command) &&
+           ((parent && !hashcmp(parent->object.oid.hash, head)) ||
+            (!parent && unborn))) {
+               if (is_rebase_i(opts))
+                       write_author_script(msg.message);
+               res = fast_forward_to(commit->object.oid.hash, head, unborn,
+                       opts);
+               if (res || command != TODO_REWORD)
+                       goto leave;
+               edit = amend = 1;
+               msg_file = NULL;
+               goto fast_forward_edit;
+       }
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be a "todo" command like
                   "revert" or "pick", the second %s a SHA1. */
@@ -670,10 +1002,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        command_to_string(command),
                        oid_to_hex(&parent->object.oid));
 
-       if (get_message(commit, &msg) != 0)
-               return error(_("cannot get commit message for %s"),
-                       oid_to_hex(&commit->object.oid));
-
        /*
         * "commit" is an existing commit.  We would want to apply
         * the difference it introduces since its first parent "prev"
@@ -704,14 +1032,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                next = commit;
                next_label = msg.label;
 
-               /*
-                * Append the commit log message to msgbuf; it starts
-                * after the tree, parent, author, committer
-                * information followed by "\n\n".
-                */
-               p = strstr(msg.message, "\n\n");
-               if (p)
-                       strbuf_addstr(&msgbuf, skip_blank_lines(p + 2));
+               /* Append the commit log message to msgbuf. */
+               if (find_commit_subject(msg.message, &p))
+                       strbuf_addstr(&msgbuf, p);
 
                if (opts->record_origin) {
                        if (!has_conforming_footer(&msgbuf, NULL, 0))
@@ -722,7 +1045,32 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                }
        }
 
-       if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
+       if (command == TODO_REWORD)
+               edit = 1;
+       else if (is_fixup(command)) {
+               if (update_squash_messages(command, commit, opts))
+                       return -1;
+               amend = 1;
+               if (!final_fixup)
+                       msg_file = rebase_path_squash_msg();
+               else if (file_exists(rebase_path_fixup_msg())) {
+                       cleanup_commit_message = 1;
+                       msg_file = rebase_path_fixup_msg();
+               } else {
+                       const char *dest = git_path("SQUASH_MSG");
+                       unlink(dest);
+                       if (copy_file(dest, rebase_path_squash_msg(), 0666))
+                               return error(_("could not rename '%s' to '%s'"),
+                                            rebase_path_squash_msg(), dest);
+                       unlink(git_path("MERGE_MSG"));
+                       msg_file = dest;
+                       edit = 1;
+               }
+       }
+
+       if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
+               res = -1;
+       else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
                                         head, &msgbuf, opts);
                if (res < 0)
@@ -777,8 +1125,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                goto leave;
        }
        if (!opts->no_commit)
-               res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(),
-                                    opts, allow, opts->edit, 0, 0);
+fast_forward_edit:
+               res = run_git_commit(msg_file, opts, allow, edit, amend,
+                                    cleanup_commit_message);
+
+       if (!res && final_fixup) {
+               unlink(rebase_path_fixup_msg());
+               unlink(rebase_path_squash_msg());
+       }
 
 leave:
        free_message(commit, &msg);
@@ -837,6 +1191,7 @@ struct todo_list {
        struct strbuf buf;
        struct todo_item *items;
        int nr, alloc, current;
+       int done_nr, total_nr;
 };
 
 #define TODO_LIST_INIT { STRBUF_INIT }
@@ -864,20 +1219,45 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        /* left-trim */
        bol += strspn(bol, " \t");
 
-       for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++)
-               if (skip_prefix(bol, todo_command_strings[i], &bol)) {
+       if (bol == eol || *bol == '\r' || *bol == comment_line_char) {
+               item->command = TODO_COMMENT;
+               item->commit = NULL;
+               item->arg = bol;
+               item->arg_len = eol - bol;
+               return 0;
+       }
+
+       for (i = 0; i < TODO_COMMENT; i++)
+               if (skip_prefix(bol, todo_command_info[i].str, &bol)) {
+                       item->command = i;
+                       break;
+               } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {
+                       bol++;
                        item->command = i;
                        break;
                }
-       if (i >= ARRAY_SIZE(todo_command_strings))
+       if (i >= TODO_COMMENT)
                return -1;
 
+       if (item->command == TODO_NOOP) {
+               item->commit = NULL;
+               item->arg = bol;
+               item->arg_len = eol - bol;
+               return 0;
+       }
+
        /* Eat up extra spaces/ tabs before object name */
        padding = strspn(bol, " \t");
        if (!padding)
                return -1;
        bol += padding;
 
+       if (item->command == TODO_EXEC) {
+               item->arg = bol;
+               item->arg_len = (int)(eol - bol);
+               return 0;
+       }
+
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
@@ -898,7 +1278,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
 {
        struct todo_item *item;
        char *p = buf, *next_p;
-       int i, res = 0;
+       int i, res = 0, fixup_okay = file_exists(rebase_path_done());
 
        for (i = 1; *p; i++, p = next_p) {
                char *eol = strchrnul(p, '\n');
@@ -913,14 +1293,32 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
                if (parse_insn_line(item, p, eol)) {
                        res = error(_("invalid line %d: %.*s"),
                                i, (int)(eol - p), p);
-                       item->command = -1;
+                       item->command = TODO_NOOP;
                }
+
+               if (fixup_okay)
+                       ; /* do nothing */
+               else if (is_fixup(item->command))
+                       return error(_("cannot '%s' without a previous commit"),
+                               command_to_string(item->command));
+               else if (!is_noop(item->command))
+                       fixup_okay = 1;
        }
-       if (!todo_list->nr)
-               return error(_("no commits parsed."));
+
        return res;
 }
 
+static int count_commands(struct todo_list *todo_list)
+{
+       int count = 0, i;
+
+       for (i = 0; i < todo_list->nr; i++)
+               if (todo_list->items[i].command != TODO_COMMENT)
+                       count++;
+
+       return count;
+}
+
 static int read_populate_todo(struct todo_list *todo_list,
                        struct replay_opts *opts)
 {
@@ -938,8 +1336,16 @@ static int read_populate_todo(struct todo_list *todo_list,
        close(fd);
 
        res = parse_insn_buffer(todo_list->buf.buf, todo_list);
-       if (res)
+       if (res) {
+               if (is_rebase_i(opts))
+                       return error(_("please fix this using "
+                                      "'git rebase --edit-todo'."));
                return error(_("unusable instruction sheet: '%s'"), todo_file);
+       }
+
+       if (!todo_list->nr &&
+           (!is_rebase_i(opts) || !file_exists(rebase_path_done())))
+               return error(_("no commits parsed."));
 
        if (!is_rebase_i(opts)) {
                enum todo_command valid =
@@ -955,6 +1361,26 @@ static int read_populate_todo(struct todo_list *todo_list,
                                return error(_("cannot revert during a cherry-pick."));
        }
 
+       if (is_rebase_i(opts)) {
+               struct todo_list done = TODO_LIST_INIT;
+               FILE *f = fopen(rebase_path_msgtotal(), "w");
+
+               if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
+                               !parse_insn_buffer(done.buf.buf, &done))
+                       todo_list->done_nr = count_commands(&done);
+               else
+                       todo_list->done_nr = 0;
+
+               todo_list->total_nr = todo_list->done_nr
+                       + count_commands(todo_list);
+               todo_list_release(&done);
+
+               if (f) {
+                       fprintf(f, "%d\n", todo_list->total_nr);
+                       fclose(f);
+               }
+       }
+
        return 0;
 }
 
@@ -1003,6 +1429,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
        return 0;
 }
 
+static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
+{
+       int i;
+
+       strbuf_reset(buf);
+       if (!read_oneliner(buf, rebase_path_strategy(), 0))
+               return;
+       opts->strategy = strbuf_detach(buf, NULL);
+       if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
+               return;
+
+       opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts);
+       for (i = 0; i < opts->xopts_nr; i++) {
+               const char *arg = opts->xopts[i];
+
+               skip_prefix(arg, "--", &arg);
+               opts->xopts[i] = xstrdup(arg);
+       }
+}
+
 static int read_populate_opts(struct replay_opts *opts)
 {
        if (is_rebase_i(opts)) {
@@ -1016,6 +1462,11 @@ static int read_populate_opts(struct replay_opts *opts)
                                opts->gpg_sign = xstrdup(buf.buf + 2);
                        }
                }
+
+               if (file_exists(rebase_path_verbose()))
+                       opts->verbose = 1;
+
+               read_strategy_opts(opts, &buf);
                strbuf_release(&buf);
 
                return 0;
@@ -1040,7 +1491,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
 {
        enum todo_command command = opts->action == REPLAY_PICK ?
                TODO_PICK : TODO_REVERT;
-       const char *command_string = todo_command_strings[command];
+       const char *command_string = todo_command_info[command].str;
        struct commit *commit;
 
        if (prepare_revs(opts))
@@ -1071,8 +1522,7 @@ static int create_seq_dir(void)
                error(_("a cherry-pick or revert is already in progress"));
                advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
                return -1;
-       }
-       else if (mkdir(git_path_seq_dir(), 0777) < 0)
+       } else if (mkdir(git_path_seq_dir(), 0777) < 0)
                return error_errno(_("could not create sequencer directory '%s'"),
                                   git_path_seq_dir());
        return 0;
@@ -1205,6 +1655,13 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
        const char *todo_path = get_todo_path(opts);
        int next = todo_list->current, offset, fd;
 
+       /*
+        * rebase -i writes "git-rebase-todo" without the currently executing
+        * command, appending it to "done" instead.
+        */
+       if (is_rebase_i(opts))
+               next++;
+
        fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
                return error_errno(_("could not lock '%s'"), todo_path);
@@ -1215,6 +1672,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
                return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
                return error(_("failed to finalize '%s'."), todo_path);
+
+       if (is_rebase_i(opts)) {
+               const char *done_path = rebase_path_done();
+               int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+               int prev_offset = !next ? 0 :
+                       todo_list->items[next - 1].offset_in_buf;
+
+               if (fd >= 0 && offset > prev_offset &&
+                   write_in_full(fd, todo_list->buf.buf + prev_offset,
+                                 offset - prev_offset) < 0) {
+                       close(fd);
+                       return error_errno(_("could not write to '%s'"),
+                                          done_path);
+               }
+               if (fd >= 0)
+                       close(fd);
+       }
        return 0;
 }
 
@@ -1253,9 +1727,228 @@ static int save_opts(struct replay_opts *opts)
        return res;
 }
 
+static int make_patch(struct commit *commit, struct replay_opts *opts)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct rev_info log_tree_opt;
+       const char *subject, *p;
+       int res = 0;
+
+       p = short_commit_name(commit);
+       if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
+               return -1;
+
+       strbuf_addf(&buf, "%s/patch", get_dir(opts));
+       memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+       init_revisions(&log_tree_opt, NULL);
+       log_tree_opt.abbrev = 0;
+       log_tree_opt.diff = 1;
+       log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
+       log_tree_opt.disable_stdin = 1;
+       log_tree_opt.no_commit_id = 1;
+       log_tree_opt.diffopt.file = fopen(buf.buf, "w");
+       log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
+       if (!log_tree_opt.diffopt.file)
+               res |= error_errno(_("could not open '%s'"), buf.buf);
+       else {
+               res |= log_tree_commit(&log_tree_opt, commit);
+               fclose(log_tree_opt.diffopt.file);
+       }
+       strbuf_reset(&buf);
+
+       strbuf_addf(&buf, "%s/message", get_dir(opts));
+       if (!file_exists(buf.buf)) {
+               const char *commit_buffer = get_commit_buffer(commit, NULL);
+               find_commit_subject(commit_buffer, &subject);
+               res |= write_message(subject, strlen(subject), buf.buf, 1);
+               unuse_commit_buffer(commit, commit_buffer);
+       }
+       strbuf_release(&buf);
+
+       return res;
+}
+
+static int intend_to_amend(void)
+{
+       unsigned char head[20];
+       char *p;
+
+       if (get_sha1("HEAD", head))
+               return error(_("cannot read HEAD"));
+
+       p = sha1_to_hex(head);
+       return write_message(p, strlen(p), rebase_path_amend(), 1);
+}
+
+static int error_with_patch(struct commit *commit,
+       const char *subject, int subject_len,
+       struct replay_opts *opts, int exit_code, int to_amend)
+{
+       if (make_patch(commit, opts))
+               return -1;
+
+       if (to_amend) {
+               if (intend_to_amend())
+                       return -1;
+
+               fprintf(stderr, "You can amend the commit now, with\n"
+                       "\n"
+                       "  git commit --amend %s\n"
+                       "\n"
+                       "Once you are satisfied with your changes, run\n"
+                       "\n"
+                       "  git rebase --continue\n", gpg_sign_opt_quoted(opts));
+       } else if (exit_code)
+               fprintf(stderr, "Could not apply %s... %.*s\n",
+                       short_commit_name(commit), subject_len, subject);
+
+       return exit_code;
+}
+
+static int error_failed_squash(struct commit *commit,
+       struct replay_opts *opts, int subject_len, const char *subject)
+{
+       if (rename(rebase_path_squash_msg(), rebase_path_message()))
+               return error(_("could not rename '%s' to '%s'"),
+                       rebase_path_squash_msg(), rebase_path_message());
+       unlink(rebase_path_fixup_msg());
+       unlink(git_path("MERGE_MSG"));
+       if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666))
+               return error(_("could not copy '%s' to '%s'"),
+                            rebase_path_message(), git_path("MERGE_MSG"));
+       return error_with_patch(commit, subject, subject_len, opts, 1, 0);
+}
+
+static int do_exec(const char *command_line)
+{
+       const char *child_argv[] = { NULL, NULL };
+       int dirty, status;
+
+       fprintf(stderr, "Executing: %s\n", command_line);
+       child_argv[0] = command_line;
+       status = run_command_v_opt(child_argv, RUN_USING_SHELL);
+
+       /* force re-reading of the cache */
+       if (discard_cache() < 0 || read_cache() < 0)
+               return error(_("could not read index"));
+
+       dirty = require_clean_work_tree("rebase", NULL, 1, 1);
+
+       if (status) {
+               warning(_("execution failed: %s\n%s"
+                         "You can fix the problem, and then run\n"
+                         "\n"
+                         "  git rebase --continue\n"
+                         "\n"),
+                       command_line,
+                       dirty ? N_("and made changes to the index and/or the "
+                               "working tree\n") : "");
+               if (status == 127)
+                       /* command not found */
+                       status = 1;
+       } else if (dirty) {
+               warning(_("execution succeeded: %s\nbut "
+                         "left changes to the index and/or the working tree\n"
+                         "Commit or stash your changes, and then run\n"
+                         "\n"
+                         "  git rebase --continue\n"
+                         "\n"), command_line);
+               status = 1;
+       }
+
+       return status;
+}
+
+static int is_final_fixup(struct todo_list *todo_list)
+{
+       int i = todo_list->current;
+
+       if (!is_fixup(todo_list->items[i].command))
+               return 0;
+
+       while (++i < todo_list->nr)
+               if (is_fixup(todo_list->items[i].command))
+                       return 0;
+               else if (!is_noop(todo_list->items[i].command))
+                       break;
+       return 1;
+}
+
+static enum todo_command peek_command(struct todo_list *todo_list, int offset)
+{
+       int i;
+
+       for (i = todo_list->current + offset; i < todo_list->nr; i++)
+               if (!is_noop(todo_list->items[i].command))
+                       return todo_list->items[i].command;
+
+       return -1;
+}
+
+static int apply_autostash(struct replay_opts *opts)
+{
+       struct strbuf stash_sha1 = STRBUF_INIT;
+       struct child_process child = CHILD_PROCESS_INIT;
+       int ret = 0;
+
+       if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) {
+               strbuf_release(&stash_sha1);
+               return 0;
+       }
+       strbuf_trim(&stash_sha1);
+
+       child.git_cmd = 1;
+       argv_array_push(&child.args, "stash");
+       argv_array_push(&child.args, "apply");
+       argv_array_push(&child.args, stash_sha1.buf);
+       if (!run_command(&child))
+               printf(_("Applied autostash."));
+       else {
+               struct child_process store = CHILD_PROCESS_INIT;
+
+               store.git_cmd = 1;
+               argv_array_push(&store.args, "stash");
+               argv_array_push(&store.args, "store");
+               argv_array_push(&store.args, "-m");
+               argv_array_push(&store.args, "autostash");
+               argv_array_push(&store.args, "-q");
+               argv_array_push(&store.args, stash_sha1.buf);
+               if (run_command(&store))
+                       ret = error(_("cannot store %s"), stash_sha1.buf);
+               else
+                       printf(_("Applying autostash resulted in conflicts.\n"
+                               "Your changes are safe in the stash.\n"
+                               "You can run \"git stash pop\" or"
+                               " \"git stash drop\" at any time.\n"));
+       }
+
+       strbuf_release(&stash_sha1);
+       return ret;
+}
+
+static const char *reflog_message(struct replay_opts *opts,
+       const char *sub_action, const char *fmt, ...)
+{
+       va_list ap;
+       static struct strbuf buf = STRBUF_INIT;
+
+       va_start(ap, fmt);
+       strbuf_reset(&buf);
+       strbuf_addstr(&buf, action_name(opts));
+       if (sub_action)
+               strbuf_addf(&buf, " (%s)", sub_action);
+       if (fmt) {
+               strbuf_addstr(&buf, ": ");
+               strbuf_vaddf(&buf, fmt, ap);
+       }
+       va_end(ap);
+
+       return buf.buf;
+}
+
 static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 {
-       int res;
+       int res = 0;
 
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (opts->allow_ff)
@@ -1268,12 +1961,179 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                struct todo_item *item = todo_list->items + todo_list->current;
                if (save_todo(todo_list, opts))
                        return -1;
-               res = do_pick_commit(item->command, item->commit, opts);
+               if (is_rebase_i(opts)) {
+                       if (item->command != TODO_COMMENT) {
+                               FILE *f = fopen(rebase_path_msgnum(), "w");
+
+                               todo_list->done_nr++;
+
+                               if (f) {
+                                       fprintf(f, "%d\n", todo_list->done_nr);
+                                       fclose(f);
+                               }
+                               fprintf(stderr, "Rebasing (%d/%d)%s",
+                                       todo_list->done_nr,
+                                       todo_list->total_nr,
+                                       opts->verbose ? "\n" : "\r");
+                       }
+                       unlink(rebase_path_message());
+                       unlink(rebase_path_author_script());
+                       unlink(rebase_path_stopped_sha());
+                       unlink(rebase_path_amend());
+               }
+               if (item->command <= TODO_SQUASH) {
+                       if (is_rebase_i(opts))
+                               setenv("GIT_REFLOG_ACTION", reflog_message(opts,
+                                       command_to_string(item->command), NULL),
+                                       1);
+                       res = do_pick_commit(item->command, item->commit,
+                                       opts, is_final_fixup(todo_list));
+                       if (is_rebase_i(opts) && res < 0) {
+                               /* Reschedule */
+                               todo_list->current--;
+                               if (save_todo(todo_list, opts))
+                                       return -1;
+                       }
+                       if (item->command == TODO_EDIT) {
+                               struct commit *commit = item->commit;
+                               if (!res)
+                                       warning(_("stopped at %s... %.*s"),
+                                               short_commit_name(commit),
+                                               item->arg_len, item->arg);
+                               return error_with_patch(commit,
+                                       item->arg, item->arg_len, opts, res,
+                                       !res);
+                       }
+                       if (is_rebase_i(opts) && !res)
+                               record_in_rewritten(&item->commit->object.oid,
+                                       peek_command(todo_list, 1));
+                       if (res && is_fixup(item->command)) {
+                               if (res == 1)
+                                       intend_to_amend();
+                               return error_failed_squash(item->commit, opts,
+                                       item->arg_len, item->arg);
+                       } else if (res && is_rebase_i(opts))
+                               return res | error_with_patch(item->commit,
+                                       item->arg, item->arg_len, opts, res,
+                                       item->command == TODO_REWORD);
+               } else if (item->command == TODO_EXEC) {
+                       char *end_of_arg = (char *)(item->arg + item->arg_len);
+                       int saved = *end_of_arg;
+
+                       *end_of_arg = '\0';
+                       res = do_exec(item->arg);
+                       *end_of_arg = saved;
+               } else if (!is_noop(item->command))
+                       return error(_("unknown command %d"), item->command);
+
                todo_list->current++;
                if (res)
                        return res;
        }
 
+       if (is_rebase_i(opts)) {
+               struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT;
+               struct stat st;
+
+               /* Stopped in the middle, as planned? */
+               if (todo_list->current < todo_list->nr)
+                       return 0;
+
+               if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
+                               starts_with(head_ref.buf, "refs/")) {
+                       const char *msg;
+                       unsigned char head[20], orig[20];
+                       int res;
+
+                       if (get_sha1("HEAD", head)) {
+                               res = error(_("cannot read HEAD"));
+cleanup_head_ref:
+                               strbuf_release(&head_ref);
+                               strbuf_release(&buf);
+                               return res;
+                       }
+                       if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
+                                       get_sha1_hex(buf.buf, orig)) {
+                               res = error(_("could not read orig-head"));
+                               goto cleanup_head_ref;
+                       }
+                       if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
+                               res = error(_("could not read 'onto'"));
+                               goto cleanup_head_ref;
+                       }
+                       msg = reflog_message(opts, "finish", "%s onto %s",
+                               head_ref.buf, buf.buf);
+                       if (update_ref(msg, head_ref.buf, head, orig,
+                                       REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
+                               res = error(_("could not update %s"),
+                                       head_ref.buf);
+                               goto cleanup_head_ref;
+                       }
+                       msg = reflog_message(opts, "finish", "returning to %s",
+                               head_ref.buf);
+                       if (create_symref("HEAD", head_ref.buf, msg)) {
+                               res = error(_("could not update HEAD to %s"),
+                                       head_ref.buf);
+                               goto cleanup_head_ref;
+                       }
+                       strbuf_reset(&buf);
+               }
+
+               if (opts->verbose) {
+                       struct rev_info log_tree_opt;
+                       struct object_id orig, head;
+
+                       memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+                       init_revisions(&log_tree_opt, NULL);
+                       log_tree_opt.diff = 1;
+                       log_tree_opt.diffopt.output_format =
+                               DIFF_FORMAT_DIFFSTAT;
+                       log_tree_opt.disable_stdin = 1;
+
+                       if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
+                           !get_sha1(buf.buf, orig.hash) &&
+                           !get_sha1("HEAD", head.hash)) {
+                               diff_tree_sha1(orig.hash, head.hash,
+                                              "", &log_tree_opt.diffopt);
+                               log_tree_diff_flush(&log_tree_opt);
+                       }
+               }
+               flush_rewritten_pending();
+               if (!stat(rebase_path_rewritten_list(), &st) &&
+                               st.st_size > 0) {
+                       struct child_process child = CHILD_PROCESS_INIT;
+                       const char *post_rewrite_hook =
+                               find_hook("post-rewrite");
+
+                       child.in = open(rebase_path_rewritten_list(), O_RDONLY);
+                       child.git_cmd = 1;
+                       argv_array_push(&child.args, "notes");
+                       argv_array_push(&child.args, "copy");
+                       argv_array_push(&child.args, "--for-rewrite=rebase");
+                       /* we don't care if this copying failed */
+                       run_command(&child);
+
+                       if (post_rewrite_hook) {
+                               struct child_process hook = CHILD_PROCESS_INIT;
+
+                               hook.in = open(rebase_path_rewritten_list(),
+                                       O_RDONLY);
+                               hook.stdout_to_stderr = 1;
+                               argv_array_push(&hook.args, post_rewrite_hook);
+                               argv_array_push(&hook.args, "rebase");
+                               /* we don't care if this hook failed */
+                               run_command(&hook);
+                       }
+               }
+               apply_autostash(opts);
+
+               fprintf(stderr, "Successfully rebased and updated %s.\n",
+                       head_ref.buf);
+
+               strbuf_release(&buf);
+               strbuf_release(&head_ref);
+       }
+
        /*
         * Sequence of picks finished successfully; cleanup by
         * removing the .git/sequencer directory
@@ -1291,6 +2151,47 @@ static int continue_single_pick(void)
        return run_command_v_opt(argv, RUN_GIT_CMD);
 }
 
+static int commit_staged_changes(struct replay_opts *opts)
+{
+       int amend = 0;
+
+       if (has_unstaged_changes(1))
+               return error(_("cannot rebase: You have unstaged changes."));
+       if (!has_uncommitted_changes(0)) {
+               const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD");
+
+               if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
+                       return error(_("could not remove CHERRY_PICK_HEAD"));
+               return 0;
+       }
+
+       if (file_exists(rebase_path_amend())) {
+               struct strbuf rev = STRBUF_INIT;
+               unsigned char head[20], to_amend[20];
+
+               if (get_sha1("HEAD", head))
+                       return error(_("cannot amend non-existing commit"));
+               if (!read_oneliner(&rev, rebase_path_amend(), 0))
+                       return error(_("invalid file: '%s'"), rebase_path_amend());
+               if (get_sha1_hex(rev.buf, to_amend))
+                       return error(_("invalid contents: '%s'"),
+                               rebase_path_amend());
+               if (hashcmp(head, to_amend))
+                       return error(_("\nYou have uncommitted changes in your "
+                                      "working tree. Please, commit them\n"
+                                      "first and then run 'git rebase "
+                                      "--continue' again."));
+
+               strbuf_release(&rev);
+               amend = 1;
+       }
+
+       if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0))
+               return error(_("could not commit staged changes."));
+       unlink(rebase_path_amend());
+       return 0;
+}
+
 int sequencer_continue(struct replay_opts *opts)
 {
        struct todo_list todo_list = TODO_LIST_INIT;
@@ -1299,25 +2200,39 @@ int sequencer_continue(struct replay_opts *opts)
        if (read_and_refresh_cache(opts))
                return -1;
 
-       if (!file_exists(get_todo_path(opts)))
+       if (is_rebase_i(opts)) {
+               if (commit_staged_changes(opts))
+                       return -1;
+       } else if (!file_exists(get_todo_path(opts)))
                return continue_single_pick();
        if (read_populate_opts(opts))
                return -1;
        if ((res = read_populate_todo(&todo_list, opts)))
                goto release_todo_list;
 
-       /* Verify that the conflict has been resolved */
-       if (file_exists(git_path_cherry_pick_head()) ||
-           file_exists(git_path_revert_head())) {
-               res = continue_single_pick();
-               if (res)
+       if (!is_rebase_i(opts)) {
+               /* Verify that the conflict has been resolved */
+               if (file_exists(git_path_cherry_pick_head()) ||
+                   file_exists(git_path_revert_head())) {
+                       res = continue_single_pick();
+                       if (res)
+                               goto release_todo_list;
+               }
+               if (index_differs_from("HEAD", 0, 0)) {
+                       res = error_dirty_index(opts);
                        goto release_todo_list;
+               }
+               todo_list.current++;
+       } else if (file_exists(rebase_path_stopped_sha())) {
+               struct strbuf buf = STRBUF_INIT;
+               struct object_id oid;
+
+               if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
+                   !get_sha1_committish(buf.buf, oid.hash))
+                       record_in_rewritten(&oid, peek_command(&todo_list, 0));
+               strbuf_release(&buf);
        }
-       if (index_differs_from("HEAD", 0, 0)) {
-               res = error_dirty_index(opts);
-               goto release_todo_list;
-       }
-       todo_list.current++;
+
        res = pick_commits(&todo_list, opts);
 release_todo_list:
        todo_list_release(&todo_list);
@@ -1328,7 +2243,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts)
 {
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        return do_pick_commit(opts->action == REPLAY_PICK ?
-               TODO_PICK : TODO_REVERT, cmit, opts);
+               TODO_PICK : TODO_REVERT, cmit, opts, 0);
 }
 
 int sequencer_pick_revisions(struct replay_opts *opts)
index 7a513c576bdccf8730828fdaa586ec9d0af4af6b..f885b68395f4bff1ded96c0ab84ed87d164f0c7d 100644 (file)
@@ -7,7 +7,8 @@ const char *git_path_seq_dir(void);
 
 enum replay_action {
        REPLAY_REVERT,
-       REPLAY_PICK
+       REPLAY_PICK,
+       REPLAY_INTERACTIVE_REBASE
 };
 
 struct replay_opts {
@@ -23,6 +24,7 @@ struct replay_opts {
        int allow_empty;
        int allow_empty_message;
        int keep_redundant_commits;
+       int verbose;
 
        int mainline;
 
diff --git a/setup.c b/setup.c
index 1b534a750810f3773ba9c21a5ec221f54857349b..967f289f1ef07d78f4b680e1d880e2fa86215371 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -486,6 +486,30 @@ int verify_repository_format(const struct repository_format *format,
        return 0;
 }
 
+void read_gitfile_error_die(int error_code, const char *path, const char *dir)
+{
+       switch (error_code) {
+       case READ_GITFILE_ERR_STAT_FAILED:
+       case READ_GITFILE_ERR_NOT_A_FILE:
+               /* non-fatal; follow return path */
+               break;
+       case READ_GITFILE_ERR_OPEN_FAILED:
+               die_errno("Error opening '%s'", path);
+       case READ_GITFILE_ERR_TOO_LARGE:
+               die("Too large to be a .git file: '%s'", path);
+       case READ_GITFILE_ERR_READ_FAILED:
+               die("Error reading %s", path);
+       case READ_GITFILE_ERR_INVALID_FORMAT:
+               die("Invalid gitfile format: %s", path);
+       case READ_GITFILE_ERR_NO_PATH:
+               die("No path in gitfile: %s", path);
+       case READ_GITFILE_ERR_NOT_A_REPO:
+               die("Not a git repository: %s", dir);
+       default:
+               die("BUG: unknown error code");
+       }
+}
+
 /*
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
@@ -559,28 +583,8 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 cleanup_return:
        if (return_error_code)
                *return_error_code = error_code;
-       else if (error_code) {
-               switch (error_code) {
-               case READ_GITFILE_ERR_STAT_FAILED:
-               case READ_GITFILE_ERR_NOT_A_FILE:
-                       /* non-fatal; follow return path */
-                       break;
-               case READ_GITFILE_ERR_OPEN_FAILED:
-                       die_errno("Error opening '%s'", path);
-               case READ_GITFILE_ERR_TOO_LARGE:
-                       die("Too large to be a .git file: '%s'", path);
-               case READ_GITFILE_ERR_READ_FAILED:
-                       die("Error reading %s", path);
-               case READ_GITFILE_ERR_INVALID_FORMAT:
-                       die("Invalid gitfile format: %s", path);
-               case READ_GITFILE_ERR_NO_PATH:
-                       die("No path in gitfile: %s", path);
-               case READ_GITFILE_ERR_NOT_A_REPO:
-                       die("Not a git repository: %s", dir);
-               default:
-                       assert(0);
-               }
-       }
+       else if (error_code)
+               read_gitfile_error_die(error_code, path, dir);
 
        free(buf);
        return error_code ? NULL : path;
@@ -1017,11 +1021,11 @@ const char *setup_git_directory(void)
        return setup_git_directory_gently(NULL);
 }
 
-const char *resolve_gitdir(const char *suspect)
+const char *resolve_gitdir_gently(const char *suspect, int *return_error_code)
 {
        if (is_git_directory(suspect))
                return suspect;
-       return read_gitfile(suspect);
+       return read_gitfile_gently(suspect, return_error_code);
 }
 
 /* if any standard file descriptor is missing open it to /dev/null */
index b5e827ac9e716db9fad69960058f1bf2fa50c9d3..ec957db5e1c2620b4a95e4f4d028f01794cef02a 100644 (file)
@@ -1630,39 +1630,54 @@ int git_open_cloexec(const char *name, int flags)
        return fd;
 }
 
-static int stat_sha1_file(const unsigned char *sha1, struct stat *st)
+/*
+ * Find "sha1" as a loose object in the local repository or in an alternate.
+ * Returns 0 on success, negative on failure.
+ *
+ * The "path" out-parameter will give the path of the object we found (if any).
+ * Note that it may point to static storage and is only valid until another
+ * call to sha1_file_name(), etc.
+ */
+static int stat_sha1_file(const unsigned char *sha1, struct stat *st,
+                         const char **path)
 {
        struct alternate_object_database *alt;
 
-       if (!lstat(sha1_file_name(sha1), st))
+       *path = sha1_file_name(sha1);
+       if (!lstat(*path, st))
                return 0;
 
        prepare_alt_odb();
        errno = ENOENT;
        for (alt = alt_odb_list; alt; alt = alt->next) {
-               const char *path = alt_sha1_path(alt, sha1);
-               if (!lstat(path, st))
+               *path = alt_sha1_path(alt, sha1);
+               if (!lstat(*path, st))
                        return 0;
        }
 
        return -1;
 }
 
-static int open_sha1_file(const unsigned char *sha1)
+/*
+ * Like stat_sha1_file(), but actually open the object and return the
+ * descriptor. See the caveats on the "path" parameter above.
+ */
+static int open_sha1_file(const unsigned char *sha1, const char **path)
 {
        int fd;
        struct alternate_object_database *alt;
        int most_interesting_errno;
 
-       fd = git_open(sha1_file_name(sha1));
+       *path = sha1_file_name(sha1);
+       fd = git_open(*path);
        if (fd >= 0)
                return fd;
        most_interesting_errno = errno;
 
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
-               const char *path = alt_sha1_path(alt, sha1);
-               fd = git_open(path);
+               *path = alt_sha1_path(alt, sha1);
+               fd = git_open(*path);
                if (fd >= 0)
                        return fd;
                if (most_interesting_errno == ENOENT)
@@ -1672,12 +1687,21 @@ static int open_sha1_file(const unsigned char *sha1)
        return -1;
 }
 
-void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+/*
+ * Map the loose object at "path" if it is not NULL, or the path found by
+ * searching for a loose object named "sha1".
+ */
+static void *map_sha1_file_1(const char *path,
+                            const unsigned char *sha1,
+                            unsigned long *size)
 {
        void *map;
        int fd;
 
-       fd = open_sha1_file(sha1);
+       if (path)
+               fd = git_open(path);
+       else
+               fd = open_sha1_file(sha1, &path);
        map = NULL;
        if (fd >= 0) {
                struct stat st;
@@ -1686,7 +1710,7 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
                        *size = xsize_t(st.st_size);
                        if (!*size) {
                                /* mmap() is forbidden on empty files */
-                               error("object file %s is empty", sha1_file_name(sha1));
+                               error("object file %s is empty", path);
                                return NULL;
                        }
                        map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
@@ -1696,6 +1720,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
        return map;
 }
 
+void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+{
+       return map_sha1_file_1(NULL, sha1, size);
+}
+
 unsigned long unpack_object_header_buffer(const unsigned char *buf,
                unsigned long len, enum object_type *type, unsigned long *sizep)
 {
@@ -2342,11 +2371,10 @@ static inline void release_delta_base_cache(struct delta_base_cache_entry *ent)
 
 void clear_delta_base_cache(void)
 {
-       struct hashmap_iter iter;
-       struct delta_base_cache_entry *entry;
-       for (entry = hashmap_iter_first(&delta_base_cache, &iter);
-            entry;
-            entry = hashmap_iter_next(&iter)) {
+       struct list_head *lru, *tmp;
+       list_for_each_safe(lru, tmp, &delta_base_cache_lru) {
+               struct delta_base_cache_entry *entry =
+                       list_entry(lru, struct delta_base_cache_entry, lru);
                release_delta_base_cache(entry);
        }
 }
@@ -2806,8 +2834,9 @@ static int sha1_loose_object_info(const unsigned char *sha1,
         * object even exists.
         */
        if (!oi->typep && !oi->typename && !oi->sizep) {
+               const char *path;
                struct stat st;
-               if (stat_sha1_file(sha1, &st) < 0)
+               if (stat_sha1_file(sha1, &st, &path) < 0)
                        return -1;
                if (oi->disk_sizep)
                        *oi->disk_sizep = st.st_size;
@@ -3003,6 +3032,8 @@ void *read_sha1_file_extended(const unsigned char *sha1,
 {
        void *data;
        const struct packed_git *p;
+       const char *path;
+       struct stat st;
        const unsigned char *repl = lookup_replace_object_extended(sha1, flag);
 
        errno = 0;
@@ -3018,12 +3049,9 @@ void *read_sha1_file_extended(const unsigned char *sha1,
                die("replacement %s not found for %s",
                    sha1_to_hex(repl), sha1_to_hex(sha1));
 
-       if (has_loose_object(repl)) {
-               const char *path = sha1_file_name(sha1);
-
+       if (!stat_sha1_file(repl, &st, &path))
                die("loose object %s (stored in %s) is corrupt",
                    sha1_to_hex(repl), path);
-       }
 
        if ((p = has_packed_and_bad(repl)) != NULL)
                die("packed object %s (stored in %s) is corrupt",
@@ -3793,3 +3821,122 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)
        }
        return r ? r : pack_errors;
 }
+
+static int check_stream_sha1(git_zstream *stream,
+                            const char *hdr,
+                            unsigned long size,
+                            const char *path,
+                            const unsigned char *expected_sha1)
+{
+       git_SHA_CTX c;
+       unsigned char real_sha1[GIT_SHA1_RAWSZ];
+       unsigned char buf[4096];
+       unsigned long total_read;
+       int status = Z_OK;
+
+       git_SHA1_Init(&c);
+       git_SHA1_Update(&c, hdr, stream->total_out);
+
+       /*
+        * We already read some bytes into hdr, but the ones up to the NUL
+        * do not count against the object's content size.
+        */
+       total_read = stream->total_out - strlen(hdr) - 1;
+
+       /*
+        * This size comparison must be "<=" to read the final zlib packets;
+        * see the comment in unpack_sha1_rest for details.
+        */
+       while (total_read <= size &&
+              (status == Z_OK || status == Z_BUF_ERROR)) {
+               stream->next_out = buf;
+               stream->avail_out = sizeof(buf);
+               if (size - total_read < stream->avail_out)
+                       stream->avail_out = size - total_read;
+               status = git_inflate(stream, Z_FINISH);
+               git_SHA1_Update(&c, buf, stream->next_out - buf);
+               total_read += stream->next_out - buf;
+       }
+       git_inflate_end(stream);
+
+       if (status != Z_STREAM_END) {
+               error("corrupt loose object '%s'", sha1_to_hex(expected_sha1));
+               return -1;
+       }
+       if (stream->avail_in) {
+               error("garbage at end of loose object '%s'",
+                     sha1_to_hex(expected_sha1));
+               return -1;
+       }
+
+       git_SHA1_Final(real_sha1, &c);
+       if (hashcmp(expected_sha1, real_sha1)) {
+               error("sha1 mismatch for %s (expected %s)", path,
+                     sha1_to_hex(expected_sha1));
+               return -1;
+       }
+
+       return 0;
+}
+
+int read_loose_object(const char *path,
+                     const unsigned char *expected_sha1,
+                     enum object_type *type,
+                     unsigned long *size,
+                     void **contents)
+{
+       int ret = -1;
+       int fd = -1;
+       void *map = NULL;
+       unsigned long mapsize;
+       git_zstream stream;
+       char hdr[32];
+
+       *contents = NULL;
+
+       map = map_sha1_file_1(path, NULL, &mapsize);
+       if (!map) {
+               error_errno("unable to mmap %s", path);
+               goto out;
+       }
+
+       if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) {
+               error("unable to unpack header of %s", path);
+               goto out;
+       }
+
+       *type = parse_sha1_header(hdr, size);
+       if (*type < 0) {
+               error("unable to parse header of %s", path);
+               git_inflate_end(&stream);
+               goto out;
+       }
+
+       if (*type == OBJ_BLOB) {
+               if (check_stream_sha1(&stream, hdr, *size, path, expected_sha1) < 0)
+                       goto out;
+       } else {
+               *contents = unpack_sha1_rest(&stream, hdr, *size, expected_sha1);
+               if (!*contents) {
+                       error("unable to unpack contents of %s", path);
+                       git_inflate_end(&stream);
+                       goto out;
+               }
+               if (check_sha1_signature(expected_sha1, *contents,
+                                        *size, typename(*type))) {
+                       error("sha1 mismatch for %s (expected %s)", path,
+                             sha1_to_hex(expected_sha1));
+                       free(*contents);
+                       goto out;
+               }
+       }
+
+       ret = 0; /* everything checks out */
+
+out:
+       if (map)
+               munmap(map, mapsize);
+       if (fd >= 0)
+               close(fd);
+       return ret;
+}
index 8c83cac189e94c327327e47cfaadfd621d59b6b9..45016ad86dd5eb55534524cb50e30542e87fab70 100644 (file)
@@ -211,21 +211,18 @@ struct string_list_item *string_list_append(struct string_list *list,
                        list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
-/* Yuck */
-static compare_strings_fn compare_for_qsort;
-
-/* Only call this from inside string_list_sort! */
-static int cmp_items(const void *a, const void *b)
+static int cmp_items(const void *a, const void *b, void *ctx)
 {
+       compare_strings_fn cmp = ctx;
        const struct string_list_item *one = a;
        const struct string_list_item *two = b;
-       return compare_for_qsort(one->string, two->string);
+       return cmp(one->string, two->string);
 }
 
 void string_list_sort(struct string_list *list)
 {
-       compare_for_qsort = list->cmp ? list->cmp : strcmp;
-       QSORT(list->items, list->nr, cmp_items);
+       QSORT_S(list->items, list->nr, cmp_items,
+               list->cmp ? list->cmp : strcmp);
 }
 
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
index 4bf50f398a5bd705531e32f9329c0e3b7e630506..93453909cf3225e2b4b9630d4013f76987ca8be8 100644 (file)
@@ -251,6 +251,8 @@ static int parse_push_recurse(const char *opt, const char *arg,
                        return RECURSE_SUBMODULES_ON_DEMAND;
                else if (!strcmp(arg, "check"))
                        return RECURSE_SUBMODULES_CHECK;
+               else if (!strcmp(arg, "only"))
+                       return RECURSE_SUBMODULES_ONLY;
                else if (die_on_error)
                        die("bad %s argument: %s", opt, arg);
                else
index 4c4f033e8a0f9f23e759949209c323875cd119fc..3b98766a6bcfe983e5f94e84edd6fcec7ce74f8d 100644 (file)
@@ -1437,22 +1437,57 @@ void absorb_git_dir_into_superproject(const char *prefix,
                                      const char *path,
                                      unsigned flags)
 {
-       const char *sub_git_dir, *v;
-       char *real_sub_git_dir = NULL, *real_common_git_dir = NULL;
+       int err_code;
+       const char *sub_git_dir;
        struct strbuf gitdir = STRBUF_INIT;
-
        strbuf_addf(&gitdir, "%s/.git", path);
-       sub_git_dir = resolve_gitdir(gitdir.buf);
+       sub_git_dir = resolve_gitdir_gently(gitdir.buf, &err_code);
 
        /* Not populated? */
-       if (!sub_git_dir)
-               goto out;
+       if (!sub_git_dir) {
+               char *real_new_git_dir;
+               const char *new_git_dir;
+               const struct submodule *sub;
+
+               if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
+                       /* unpopulated as expected */
+                       strbuf_release(&gitdir);
+                       return;
+               }
+
+               if (err_code != READ_GITFILE_ERR_NOT_A_REPO)
+                       /* We don't know what broke here. */
+                       read_gitfile_error_die(err_code, path, NULL);
+
+               /*
+               * Maybe populated, but no git directory was found?
+               * This can happen if the superproject is a submodule
+               * itself and was just absorbed. The absorption of the
+               * superproject did not rewrite the git file links yet,
+               * fix it now.
+               */
+               sub = submodule_from_path(null_sha1, path);
+               if (!sub)
+                       die(_("could not lookup name for submodule '%s'"), path);
+               new_git_dir = git_path("modules/%s", sub->name);
+               if (safe_create_leading_directories_const(new_git_dir) < 0)
+                       die(_("could not create directory '%s'"), new_git_dir);
+               real_new_git_dir = real_pathdup(new_git_dir);
+               connect_work_tree_and_git_dir(path, real_new_git_dir);
+
+               free(real_new_git_dir);
+       } else {
+               /* Is it already absorbed into the superprojects git dir? */
+               char *real_sub_git_dir = real_pathdup(sub_git_dir);
+               char *real_common_git_dir = real_pathdup(get_git_common_dir());
 
-       /* Is it already absorbed into the superprojects git dir? */
-       real_sub_git_dir = real_pathdup(sub_git_dir);
-       real_common_git_dir = real_pathdup(get_git_common_dir());
-       if (!skip_prefix(real_sub_git_dir, real_common_git_dir, &v))
-               relocate_single_git_dir_into_superproject(prefix, path);
+               if (!starts_with(real_sub_git_dir, real_common_git_dir))
+                       relocate_single_git_dir_into_superproject(prefix, path);
+
+               free(real_sub_git_dir);
+               free(real_common_git_dir);
+       }
+       strbuf_release(&gitdir);
 
        if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) {
                struct child_process cp = CHILD_PROCESS_INIT;
@@ -1478,9 +1513,4 @@ void absorb_git_dir_into_superproject(const char *prefix,
 
                strbuf_release(&sb);
        }
-
-out:
-       strbuf_release(&gitdir);
-       free(real_sub_git_dir);
-       free(real_common_git_dir);
 }
index b7fe4d20279dfe165338dc412595fe1ccf6ad73c..05ab674f069282b3d5b20ca69d9a1fe8f295879b 100644 (file)
@@ -6,6 +6,7 @@ struct argv_array;
 struct sha1_array;
 
 enum {
+       RECURSE_SUBMODULES_ONLY = -5,
        RECURSE_SUBMODULES_CHECK = -4,
        RECURSE_SUBMODULES_ERROR = -3,
        RECURSE_SUBMODULES_NONE = -2,
index 4a68967bd126e5ab74ec2b39113cce58e7c021bf..c502fa16d307957b6fe23cbd5481fc35f49c00da 100644 (file)
@@ -97,6 +97,31 @@ int cmd_main(int argc, const char **argv)
                return 0;
        }
 
+       if (argc == 2 && !strcmp(argv[1], "sort")) {
+               struct string_list list = STRING_LIST_INIT_NODUP;
+               struct strbuf sb = STRBUF_INIT;
+               struct string_list_item *item;
+
+               strbuf_read(&sb, 0, 0);
+
+               /*
+                * Split by newline, but don't create a string_list item
+                * for the empty string after the last separator.
+                */
+               if (sb.buf[sb.len - 1] == '\n')
+                       strbuf_setlen(&sb, sb.len - 1);
+               string_list_split_in_place(&list, sb.buf, '\n', -1);
+
+               string_list_sort(&list);
+
+               for_each_string_list_item(item, &list)
+                       puts(item->string);
+
+               string_list_clear(&list, 0);
+               strbuf_release(&sb);
+               return 0;
+       }
+
        fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
                argv[1] ? argv[1] : "(there was none)");
        return 1;
diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh
new file mode 100755 (executable)
index 0000000..7c9a35a
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test_description='Basic sort performance tests'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_expect_success 'setup' '
+       git ls-files --stage "*.[ch]" "*.sh" |
+       cut -f2 -d" " |
+       git cat-file --batch >unsorted
+'
+
+test_perf 'sort(1)' '
+       sort <unsorted >expect
+'
+
+test_perf 'string_list_sort()' '
+       test-string-list sort <unsorted >actual
+'
+
+test_expect_success 'string_list_sort() sorts like sort(1)' '
+       test_cmp_bin expect actual
+'
+
+test_done
index b8fc588b1922760ade8502fda3dc465f6ebf2887..e424de5363848233d2c1d9807df0a01411fd0d6d 100755 (executable)
@@ -258,6 +258,9 @@ test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shar
        (
                # Leading directories should honor umask while
                # the repository itself should follow "shared"
+               mkdir newdir &&
+               # Remove a default ACL if possible.
+               (setfacl -k newdir 2>/dev/null || true) &&
                umask 002 &&
                git init --bare --shared=0660 newdir/a/b/c &&
                test_path_is_dir newdir/a/b/c/refs &&
index a0b79b4839fb21a96bf8aca769542d93b21bb90f..3c4d2d6045bf026cf164374a41ae759d08a69bf8 100755 (executable)
@@ -128,29 +128,29 @@ cat >expected <<\EOF
 EOF
 
 check_result () {
-    git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
-    test_cmp expected current
+       git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+       test_cmp expected current
 }
 
 # This is done on an empty work directory, which is the normal
 # merge person behaviour.
-test_expect_success \
-    '3-way merge with git read-tree -m, empty cache' \
-    "rm -fr [NDMALTS][NDMALTSF] Z &&
-     rm .git/index &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
+test_expect_success '3-way merge with git read-tree -m, empty cache' '
+       rm -fr [NDMALTS][NDMALTSF] Z &&
+       rm .git/index &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
 
 # This starts out with the first head, which is the normal
 # patch submitter behaviour.
-test_expect_success \
-    '3-way merge with git read-tree -m, match H' \
-    "rm -fr [NDMALTS][NDMALTSF] Z &&
-     rm .git/index &&
-     read_tree_must_succeed $tree_A &&
-     git checkout-index -f -u -a &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
+test_expect_success '3-way merge with git read-tree -m, match H' '
+       rm -fr [NDMALTS][NDMALTSF] Z &&
+       rm .git/index &&
+       read_tree_must_succeed $tree_A &&
+       git checkout-index -f -u -a &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
 
 : <<\END_OF_CASE_TABLE
 
@@ -208,322 +208,304 @@ DF (file) when tree B require DF to be a directory by having DF/DF
 
 END_OF_CASE_TABLE
 
-test_expect_success '1 - must not have an entry not in A.' "
-     rm -f .git/index XX &&
-     echo XX >XX &&
-     git update-index --add XX &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '2 - must match B in !O && !A && B case.' \
-    "rm -f .git/index NA &&
-     cp .orig-B/NA NA &&
-     git update-index --add NA &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B"
-
-test_expect_success \
-    '2 - matching B alone is OK in !O && !A && B case.' \
-    "rm -f .git/index NA &&
-     cp .orig-B/NA NA &&
-     git update-index --add NA &&
-     echo extra >>NA &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B"
-
-test_expect_success \
-    '3 - must match A in !O && A && !B case.' \
-    "rm -f .git/index AN &&
-     cp .orig-A/AN AN &&
-     git update-index --add AN &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '3 - matching A alone is OK in !O && A && !B case.' \
-    "rm -f .git/index AN &&
-     cp .orig-A/AN AN &&
-     git update-index --add AN &&
-     echo extra >>AN &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B"
-
-test_expect_success \
-    '3 (fail) - must match A in !O && A && !B case.' "
-     rm -f .git/index AN &&
-     cp .orig-A/AN AN &&
-     echo extra >>AN &&
-     git update-index --add AN &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '4 - must match and be up-to-date in !O && A && B && A!=B case.' \
-    "rm -f .git/index AA &&
-     cp .orig-A/AA AA &&
-     git update-index --add AA &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' "
-     rm -f .git/index AA &&
-     cp .orig-A/AA AA &&
-     git update-index --add AA &&
-     echo extra >>AA &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' "
-     rm -f .git/index AA &&
-     cp .orig-A/AA AA &&
-     echo extra >>AA &&
-     git update-index --add AA &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '5 - must match in !O && A && B && A==B case.' \
-    "rm -f .git/index LL &&
-     cp .orig-A/LL LL &&
-     git update-index --add LL &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '5 - must match in !O && A && B && A==B case.' \
-    "rm -f .git/index LL &&
-     cp .orig-A/LL LL &&
-     git update-index --add LL &&
-     echo extra >>LL &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '5 (fail) - must match A in !O && A && B && A==B case.' "
-     rm -f .git/index LL &&
-     cp .orig-A/LL LL &&
-     echo extra >>LL &&
-     git update-index --add LL &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '6 - must not exist in O && !A && !B case' "
-     rm -f .git/index DD &&
-     echo DD >DD &&
-     git update-index --add DD &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '7 - must not exist in O && !A && B && O!=B case' "
-     rm -f .git/index DM &&
-     cp .orig-B/DM DM &&
-     git update-index --add DM &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '8 - must not exist in O && !A && B && O==B case' "
-     rm -f .git/index DN &&
-     cp .orig-B/DN DN &&
-     git update-index --add DN &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '9 - must match and be up-to-date in O && A && !B && O!=A case' \
-    "rm -f .git/index MD &&
-     cp .orig-A/MD MD &&
-     git update-index --add MD &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' "
-     rm -f .git/index MD &&
-     cp .orig-A/MD MD &&
-     git update-index --add MD &&
-     echo extra >>MD &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' "
-     rm -f .git/index MD &&
-     cp .orig-A/MD MD &&
-     echo extra >>MD &&
-     git update-index --add MD &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '10 - must match and be up-to-date in O && A && !B && O==A case' \
-    "rm -f .git/index ND &&
-     cp .orig-A/ND ND &&
-     git update-index --add ND &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' "
-     rm -f .git/index ND &&
-     cp .orig-A/ND ND &&
-     git update-index --add ND &&
-     echo extra >>ND &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' "
-     rm -f .git/index ND &&
-     cp .orig-A/ND ND &&
-     echo extra >>ND &&
-     git update-index --add ND &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
-    "rm -f .git/index MM &&
-     cp .orig-A/MM MM &&
-     git update-index --add MM &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' "
-     rm -f .git/index MM &&
-     cp .orig-A/MM MM &&
-     git update-index --add MM &&
-     echo extra >>MM &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' "
-     rm -f .git/index MM &&
-     cp .orig-A/MM MM &&
-     echo extra >>MM &&
-     git update-index --add MM &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '12 - must match A in O && A && B && O!=A && A==B case' \
-    "rm -f .git/index SS &&
-     cp .orig-A/SS SS &&
-     git update-index --add SS &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '12 - must match A in O && A && B && O!=A && A==B case' \
-    "rm -f .git/index SS &&
-     cp .orig-A/SS SS &&
-     git update-index --add SS &&
-     echo extra >>SS &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '12 (fail) - must match A in O && A && B && O!=A && A==B case' "
-     rm -f .git/index SS &&
-     cp .orig-A/SS SS &&
-     echo extra >>SS &&
-     git update-index --add SS &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '13 - must match A in O && A && B && O!=A && O==B case' \
-    "rm -f .git/index MN &&
-     cp .orig-A/MN MN &&
-     git update-index --add MN &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '13 - must match A in O && A && B && O!=A && O==B case' \
-    "rm -f .git/index MN &&
-     cp .orig-A/MN MN &&
-     git update-index --add MN &&
-     echo extra >>MN &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \
-    "rm -f .git/index NM &&
-     cp .orig-A/NM NM &&
-     git update-index --add NM &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '14 - may match B in O && A && B && O==A && O!=B case' \
-    "rm -f .git/index NM &&
-     cp .orig-B/NM NM &&
-     git update-index --add NM &&
-     echo extra >>NM &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' "
-     rm -f .git/index NM &&
-     cp .orig-A/NM NM &&
-     git update-index --add NM &&
-     echo extra >>NM &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' "
-     rm -f .git/index NM &&
-     cp .orig-A/NM NM &&
-     echo extra >>NM &&
-     git update-index --add NM &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-test_expect_success \
-    '15 - must match A in O && A && B && O==A && O==B case' \
-    "rm -f .git/index NN &&
-     cp .orig-A/NN NN &&
-     git update-index --add NN &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '15 - must match A in O && A && B && O==A && O==B case' \
-    "rm -f .git/index NN &&
-     cp .orig-A/NN NN &&
-     git update-index --add NN &&
-     echo extra >>NN &&
-     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
-     check_result"
-
-test_expect_success \
-    '15 (fail) - must match A in O && A && B && O==A && O==B case' "
-     rm -f .git/index NN &&
-     cp .orig-A/NN NN &&
-     echo extra >>NN &&
-     git update-index --add NN &&
-     read_tree_must_fail -m $tree_O $tree_A $tree_B
-"
-
-# #16
-test_expect_success \
-    '16 - A matches in one and B matches in another.' \
-    'rm -f .git/index F16 &&
-    echo F16 >F16 &&
-    git update-index --add F16 &&
-    tree0=$(git write-tree) &&
-    echo E16 >F16 &&
-    git update-index F16 &&
-    tree1=$(git write-tree) &&
-    read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 &&
-    git ls-files --stage'
+test_expect_success '1 - must not have an entry not in A.' '
+       rm -f .git/index XX &&
+       echo XX >XX &&
+       git update-index --add XX &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '2 - must match B in !O && !A && B case.' '
+       rm -f .git/index NA &&
+       cp .orig-B/NA NA &&
+       git update-index --add NA &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '2 - matching B alone is OK in !O && !A && B case.' '
+       rm -f .git/index NA &&
+       cp .orig-B/NA NA &&
+       git update-index --add NA &&
+       echo extra >>NA &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '3 - must match A in !O && A && !B case.' '
+       rm -f .git/index AN &&
+       cp .orig-A/AN AN &&
+       git update-index --add AN &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '3 - matching A alone is OK in !O && A && !B case.' '
+       rm -f .git/index AN &&
+       cp .orig-A/AN AN &&
+       git update-index --add AN &&
+       echo extra >>AN &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '3 (fail) - must match A in !O && A && !B case.' '
+       rm -f .git/index AN &&
+       cp .orig-A/AN AN &&
+       echo extra >>AN &&
+       git update-index --add AN &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '4 - must match and be up-to-date in !O && A && B && A!=B case.' '
+       rm -f .git/index AA &&
+       cp .orig-A/AA AA &&
+       git update-index --add AA &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' '
+       rm -f .git/index AA &&
+       cp .orig-A/AA AA &&
+       git update-index --add AA &&
+       echo extra >>AA &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' '
+       rm -f .git/index AA &&
+       cp .orig-A/AA AA &&
+       echo extra >>AA &&
+       git update-index --add AA &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '5 - must match in !O && A && B && A==B case.' '
+       rm -f .git/index LL &&
+       cp .orig-A/LL LL &&
+       git update-index --add LL &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '5 - must match in !O && A && B && A==B case.' '
+       rm -f .git/index LL &&
+       cp .orig-A/LL LL &&
+       git update-index --add LL &&
+       echo extra >>LL &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '5 (fail) - must match A in !O && A && B && A==B case.' '
+       rm -f .git/index LL &&
+       cp .orig-A/LL LL &&
+       echo extra >>LL &&
+       git update-index --add LL &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '6 - must not exist in O && !A && !B case' '
+       rm -f .git/index DD &&
+       echo DD >DD &&
+       git update-index --add DD &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '7 - must not exist in O && !A && B && O!=B case' '
+       rm -f .git/index DM &&
+       cp .orig-B/DM DM &&
+       git update-index --add DM &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '8 - must not exist in O && !A && B && O==B case' '
+       rm -f .git/index DN &&
+       cp .orig-B/DN DN &&
+       git update-index --add DN &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '9 - must match and be up-to-date in O && A && !B && O!=A case' '
+       rm -f .git/index MD &&
+       cp .orig-A/MD MD &&
+       git update-index --add MD &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' '
+       rm -f .git/index MD &&
+       cp .orig-A/MD MD &&
+       git update-index --add MD &&
+       echo extra >>MD &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' '
+       rm -f .git/index MD &&
+       cp .orig-A/MD MD &&
+       echo extra >>MD &&
+       git update-index --add MD &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '10 - must match and be up-to-date in O && A && !B && O==A case' '
+       rm -f .git/index ND &&
+       cp .orig-A/ND ND &&
+       git update-index --add ND &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' '
+       rm -f .git/index ND &&
+       cp .orig-A/ND ND &&
+       git update-index --add ND &&
+       echo extra >>ND &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' '
+       rm -f .git/index ND &&
+       cp .orig-A/ND ND &&
+       echo extra >>ND &&
+       git update-index --add ND &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' '
+       rm -f .git/index MM &&
+       cp .orig-A/MM MM &&
+       git update-index --add MM &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' '
+       rm -f .git/index MM &&
+       cp .orig-A/MM MM &&
+       git update-index --add MM &&
+       echo extra >>MM &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' '
+       rm -f .git/index MM &&
+       cp .orig-A/MM MM &&
+       echo extra >>MM &&
+       git update-index --add MM &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' '
+       rm -f .git/index SS &&
+       cp .orig-A/SS SS &&
+       git update-index --add SS &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' '
+       rm -f .git/index SS &&
+       cp .orig-A/SS SS &&
+       git update-index --add SS &&
+       echo extra >>SS &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '12 (fail) - must match A in O && A && B && O!=A && A==B case' '
+       rm -f .git/index SS &&
+       cp .orig-A/SS SS &&
+       echo extra >>SS &&
+       git update-index --add SS &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' '
+       rm -f .git/index MN &&
+       cp .orig-A/MN MN &&
+       git update-index --add MN &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' '
+       rm -f .git/index MN &&
+       cp .orig-A/MN MN &&
+       git update-index --add MN &&
+       echo extra >>MN &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' '
+       rm -f .git/index NM &&
+       cp .orig-A/NM NM &&
+       git update-index --add NM &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '14 - may match B in O && A && B && O==A && O!=B case' '
+       rm -f .git/index NM &&
+       cp .orig-B/NM NM &&
+       git update-index --add NM &&
+       echo extra >>NM &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' '
+       rm -f .git/index NM &&
+       cp .orig-A/NM NM &&
+       git update-index --add NM &&
+       echo extra >>NM &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' '
+       rm -f .git/index NM &&
+       cp .orig-A/NM NM &&
+       echo extra >>NM &&
+       git update-index --add NM &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '15 - must match A in O && A && B && O==A && O==B case' '
+       rm -f .git/index NN &&
+       cp .orig-A/NN NN &&
+       git update-index --add NN &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '15 - must match A in O && A && B && O==A && O==B case' '
+       rm -f .git/index NN &&
+       cp .orig-A/NN NN &&
+       git update-index --add NN &&
+       echo extra >>NN &&
+       read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
+       check_result
+'
+
+test_expect_success '15 (fail) - must match A in O && A && B && O==A && O==B case' '
+       rm -f .git/index NN &&
+       cp .orig-A/NN NN &&
+       echo extra >>NN &&
+       git update-index --add NN &&
+       read_tree_must_fail -m $tree_O $tree_A $tree_B
+'
+
+test_expect_success '16 - A matches in one and B matches in another.' '
+       rm -f .git/index F16 &&
+       echo F16 >F16 &&
+       git update-index --add F16 &&
+       tree0=$(git write-tree) &&
+       echo E16 >F16 &&
+       git update-index F16 &&
+       tree1=$(git write-tree) &&
+       read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 &&
+       git ls-files --stage
+'
 
 test_done
index db1b6f5cf4dd8c7932946a53bc2e1c30b09b762b..5ededd8e400e7e64faa1bd559d67251ecbf8608e 100755 (executable)
@@ -14,10 +14,10 @@ all the combinations described in the two-tree merge "carry forward"
 rules, found in <Documentation/git read-tree.txt>.
 
 In the test, these paths are used:
-        bozbar  - in H, stays in M, modified from bozbar to gnusto
-        frotz   - not in H added in M
-        nitfol  - in H, stays in M unmodified
-        rezrov  - in H, deleted in M
+       bozbar  - in H, stays in M, modified from bozbar to gnusto
+       frotz   - not in H added in M
+       nitfol  - in H, stays in M unmodified
+       rezrov  - in H, deleted in M
        yomin   - not in H or M
 '
 . ./test-lib.sh
@@ -60,336 +60,343 @@ EOF
 
 sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new
 
-test_expect_success \
-    setup \
-    'echo frotz >frotz &&
-     echo nitfol >nitfol &&
-     cat bozbar-old >bozbar &&
-     echo rezrov >rezrov &&
-     echo yomin >yomin &&
-     git update-index --add nitfol bozbar rezrov &&
-     treeH=$(git write-tree) &&
-     echo treeH $treeH &&
-     git ls-tree $treeH &&
-
-     cat bozbar-new >bozbar &&
-     git update-index --add frotz bozbar --force-remove rezrov &&
-     git ls-files --stage >M.out &&
-     treeM=$(git write-tree) &&
-     echo treeM $treeM &&
-     git ls-tree $treeM &&
-     git diff-tree $treeH $treeM'
-
-test_expect_success \
-    '1, 2, 3 - no carry forward' \
-    'rm -f .git/index &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >1-3.out &&
-     test_cmp M.out 1-3.out &&
-     check_cache_at bozbar dirty &&
-     check_cache_at frotz dirty &&
-     check_cache_at nitfol dirty'
+test_expect_success 'setup' '
+       echo frotz >frotz &&
+       echo nitfol >nitfol &&
+       cat bozbar-old >bozbar &&
+       echo rezrov >rezrov &&
+       echo yomin >yomin &&
+       git update-index --add nitfol bozbar rezrov &&
+       treeH=$(git write-tree) &&
+       echo treeH $treeH &&
+       git ls-tree $treeH &&
+
+       cat bozbar-new >bozbar &&
+       git update-index --add frotz bozbar --force-remove rezrov &&
+       git ls-files --stage >M.out &&
+       treeM=$(git write-tree) &&
+       echo treeM $treeM &&
+       git ls-tree $treeM &&
+       git diff-tree $treeH $treeM
+'
 
+test_expect_success '1, 2, 3 - no carry forward' '
+       rm -f .git/index &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >1-3.out &&
+       test_cmp M.out 1-3.out &&
+       check_cache_at bozbar dirty &&
+       check_cache_at frotz dirty &&
+       check_cache_at nitfol dirty
+'
 echo '+100644 X 0      yomin' >expected
 
-test_expect_success \
-    '4 - carry forward local addition.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     git update-index --add yomin &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >4.out &&
-     test_must_fail git diff --no-index M.out 4.out >4diff.out &&
-     compare_change 4diff.out expected &&
-     check_cache_at yomin clean'
-
-test_expect_success \
-    '5 - carry forward local addition.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo yomin >yomin &&
-     git update-index --add yomin &&
-     echo yomin yomin >yomin &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >5.out &&
-     test_must_fail git diff --no-index M.out 5.out >5diff.out &&
-     compare_change 5diff.out expected &&
-     check_cache_at yomin dirty'
-
-test_expect_success \
-    '6 - local addition already has the same.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     git update-index --add frotz &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >6.out &&
-     test_cmp M.out 6.out &&
-     check_cache_at frotz clean'
-
-test_expect_success \
-    '7 - local addition already has the same.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo frotz >frotz &&
-     git update-index --add frotz &&
-     echo frotz frotz >frotz &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >7.out &&
-     test_cmp M.out 7.out &&
-     check_cache_at frotz dirty'
-
-test_expect_success \
-    '8 - conflicting addition.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo frotz frotz >frotz &&
-     git update-index --add frotz &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
-
-test_expect_success \
-    '9 - conflicting addition.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo frotz frotz >frotz &&
-     git update-index --add frotz &&
-     echo frotz >frotz &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
-
-test_expect_success \
-    '10 - path removed.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo rezrov >rezrov &&
-     git update-index --add rezrov &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >10.out &&
-     test_cmp M.out 10.out'
-
-test_expect_success \
-    '11 - dirty path removed.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo rezrov >rezrov &&
-     git update-index --add rezrov &&
-     echo rezrov rezrov >rezrov &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
-
-test_expect_success \
-    '12 - unmatching local changes being removed.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo rezrov rezrov >rezrov &&
-     git update-index --add rezrov &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
-
-test_expect_success \
-    '13 - unmatching local changes being removed.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo rezrov rezrov >rezrov &&
-     git update-index --add rezrov &&
-     echo rezrov >rezrov &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+test_expect_success '4 - carry forward local addition.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       git update-index --add yomin &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >4.out &&
+       test_must_fail git diff --no-index M.out 4.out >4diff.out &&
+       compare_change 4diff.out expected &&
+       check_cache_at yomin clean
+'
+
+test_expect_success '5 - carry forward local addition.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo yomin >yomin &&
+       git update-index --add yomin &&
+       echo yomin yomin >yomin &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >5.out &&
+       test_must_fail git diff --no-index M.out 5.out >5diff.out &&
+       compare_change 5diff.out expected &&
+       check_cache_at yomin dirty
+'
+
+test_expect_success '6 - local addition already has the same.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       git update-index --add frotz &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >6.out &&
+       test_cmp M.out 6.out &&
+       check_cache_at frotz clean
+'
+
+test_expect_success '7 - local addition already has the same.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo frotz >frotz &&
+       git update-index --add frotz &&
+       echo frotz frotz >frotz &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >7.out &&
+       test_cmp M.out 7.out &&
+       check_cache_at frotz dirty
+'
+
+test_expect_success '8 - conflicting addition.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo frotz frotz >frotz &&
+       git update-index --add frotz &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
+
+test_expect_success '9 - conflicting addition.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo frotz frotz >frotz &&
+       git update-index --add frotz &&
+       echo frotz >frotz &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
+
+test_expect_success '10 - path removed.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo rezrov >rezrov &&
+       git update-index --add rezrov &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >10.out &&
+       test_cmp M.out 10.out
+'
+
+test_expect_success '11 - dirty path removed.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo rezrov >rezrov &&
+       git update-index --add rezrov &&
+       echo rezrov rezrov >rezrov &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
+
+test_expect_success '12 - unmatching local changes being removed.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo rezrov rezrov >rezrov &&
+       git update-index --add rezrov &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
+
+test_expect_success '13 - unmatching local changes being removed.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo rezrov rezrov >rezrov &&
+       git update-index --add rezrov &&
+       echo rezrov >rezrov &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
 
 cat >expected <<EOF
 -100644 X 0    nitfol
 +100644 X 0    nitfol
 EOF
 
-test_expect_success \
-    '14 - unchanged in two heads.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo nitfol nitfol >nitfol &&
-     git update-index --add nitfol &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >14.out &&
-     test_must_fail git diff --no-index M.out 14.out >14diff.out &&
-     compare_change 14diff.out expected &&
-     check_cache_at nitfol clean'
-
-test_expect_success \
-    '15 - unchanged in two heads.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo nitfol nitfol >nitfol &&
-     git update-index --add nitfol &&
-     echo nitfol nitfol nitfol >nitfol &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >15.out &&
-     test_must_fail git diff --no-index M.out 15.out >15diff.out &&
-     compare_change 15diff.out expected &&
-     check_cache_at nitfol dirty'
-
-test_expect_success \
-    '16 - conflicting local change.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo bozbar bozbar >bozbar &&
-     git update-index --add bozbar &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
-
-test_expect_success \
-    '17 - conflicting local change.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     echo bozbar bozbar >bozbar &&
-     git update-index --add bozbar &&
-     echo bozbar bozbar bozbar >bozbar &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
-
-test_expect_success \
-    '18 - local change already having a good result.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     cat bozbar-new >bozbar &&
-     git update-index --add bozbar &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >18.out &&
-     test_cmp M.out 18.out &&
-     check_cache_at bozbar clean'
-
-test_expect_success \
-    '19 - local change already having a good result, further modified.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     cat bozbar-new >bozbar &&
-     git update-index --add bozbar &&
-     echo gnusto gnusto >bozbar &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >19.out &&
-     test_cmp M.out 19.out &&
-     check_cache_at bozbar dirty'
-
-test_expect_success \
-    '20 - no local change, use new tree.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     cat bozbar-old >bozbar &&
-     git update-index --add bozbar &&
-     read_tree_twoway $treeH $treeM &&
-     git ls-files --stage >20.out &&
-     test_cmp M.out 20.out &&
-     check_cache_at bozbar dirty'
-
-test_expect_success \
-    '21 - no local change, dirty cache.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     cat bozbar-old >bozbar &&
-     git update-index --add bozbar &&
-     echo gnusto gnusto >bozbar &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+test_expect_success '14 - unchanged in two heads.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo nitfol nitfol >nitfol &&
+       git update-index --add nitfol &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >14.out &&
+       test_must_fail git diff --no-index M.out 14.out >14diff.out &&
+       compare_change 14diff.out expected &&
+       check_cache_at nitfol clean
+'
+
+test_expect_success '15 - unchanged in two heads.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo nitfol nitfol >nitfol &&
+       git update-index --add nitfol &&
+       echo nitfol nitfol nitfol >nitfol &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >15.out &&
+       test_must_fail git diff --no-index M.out 15.out >15diff.out &&
+       compare_change 15diff.out expected &&
+       check_cache_at nitfol dirty
+'
+
+test_expect_success '16 - conflicting local change.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo bozbar bozbar >bozbar &&
+       git update-index --add bozbar &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
+
+test_expect_success '17 - conflicting local change.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       echo bozbar bozbar >bozbar &&
+       git update-index --add bozbar &&
+       echo bozbar bozbar bozbar >bozbar &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
+
+test_expect_success '18 - local change already having a good result.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       cat bozbar-new >bozbar &&
+       git update-index --add bozbar &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >18.out &&
+       test_cmp M.out 18.out &&
+       check_cache_at bozbar clean
+'
+
+test_expect_success '19 - local change already having a good result, further modified.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       cat bozbar-new >bozbar &&
+       git update-index --add bozbar &&
+       echo gnusto gnusto >bozbar &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >19.out &&
+       test_cmp M.out 19.out &&
+       check_cache_at bozbar dirty
+'
+
+test_expect_success '20 - no local change, use new tree.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       cat bozbar-old >bozbar &&
+       git update-index --add bozbar &&
+       read_tree_twoway $treeH $treeM &&
+       git ls-files --stage >20.out &&
+       test_cmp M.out 20.out &&
+       check_cache_at bozbar dirty
+'
+
+test_expect_success '21 - no local change, dirty cache.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       cat bozbar-old >bozbar &&
+       git update-index --add bozbar &&
+       echo gnusto gnusto >bozbar &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
 
 # This fails with straight two-way fast-forward.
-test_expect_success \
-    '22 - local change cache updated.' \
-    'rm -f .git/index &&
-     read_tree_must_succeed $treeH &&
-     git checkout-index -u -f -q -a &&
-     sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
-     git update-index --add bozbar &&
-     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+test_expect_success '22 - local change cache updated.' '
+       rm -f .git/index &&
+       read_tree_must_succeed $treeH &&
+       git checkout-index -u -f -q -a &&
+       sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
+       git update-index --add bozbar &&
+       if read_tree_twoway $treeH $treeM; then false; else :; fi
+'
 
 # Also make sure we did not break DF vs DF/DF case.
-test_expect_success \
-    'DF vs DF/DF case setup.' \
-    'rm -f .git/index &&
-     echo DF >DF &&
-     git update-index --add DF &&
-     treeDF=$(git write-tree) &&
-     echo treeDF $treeDF &&
-     git ls-tree $treeDF &&
-
-     rm -f DF &&
-     mkdir DF &&
-     echo DF/DF >DF/DF &&
-     git update-index --add --remove DF DF/DF &&
-     treeDFDF=$(git write-tree) &&
-     echo treeDFDF $treeDFDF &&
-     git ls-tree $treeDFDF &&
-     git ls-files --stage >DFDF.out'
-
-test_expect_success \
-    'DF vs DF/DF case test.' \
-    'rm -f .git/index &&
-     rm -fr DF &&
-     echo DF >DF &&
-     git update-index --add DF &&
-     read_tree_twoway $treeDF $treeDFDF &&
-     git ls-files --stage >DFDFcheck.out &&
-     test_cmp DFDF.out DFDFcheck.out &&
-     check_cache_at DF/DF dirty &&
-     :'
-
-test_expect_success \
-    'a/b (untracked) vs a case setup.' \
-    'rm -f .git/index &&
-     : >a &&
-     git update-index --add a &&
-     treeM=$(git write-tree) &&
-     echo treeM $treeM &&
-     git ls-tree $treeM &&
-     git ls-files --stage >treeM.out &&
-
-     rm -f a &&
-     git update-index --remove a &&
-     mkdir a &&
-     : >a/b &&
-     treeH=$(git write-tree) &&
-     echo treeH $treeH &&
-     git ls-tree $treeH'
-
-test_expect_success \
-    'a/b (untracked) vs a, plus c/d case test.' \
-    'read_tree_u_must_fail -u -m "$treeH" "$treeM" &&
-     git ls-files --stage &&
-     test -f a/b'
-
-test_expect_success \
-    'a/b vs a, plus c/d case setup.' \
-    'rm -f .git/index &&
-     rm -fr a &&
-     : >a &&
-     mkdir c &&
-     : >c/d &&
-     git update-index --add a c/d &&
-     treeM=$(git write-tree) &&
-     echo treeM $treeM &&
-     git ls-tree $treeM &&
-     git ls-files --stage >treeM.out &&
-
-     rm -f a &&
-     mkdir a &&
-     : >a/b &&
-     git update-index --add --remove a a/b &&
-     treeH=$(git write-tree) &&
-     echo treeH $treeH &&
-     git ls-tree $treeH'
-
-test_expect_success \
-    'a/b vs a, plus c/d case test.' \
-    'read_tree_u_must_succeed -u -m "$treeH" "$treeM" &&
-     git ls-files --stage | tee >treeMcheck.out &&
-     test_cmp treeM.out treeMcheck.out'
+test_expect_success 'DF vs DF/DF case setup.' '
+       rm -f .git/index &&
+       echo DF >DF &&
+       git update-index --add DF &&
+       treeDF=$(git write-tree) &&
+       echo treeDF $treeDF &&
+       git ls-tree $treeDF &&
+
+       rm -f DF &&
+       mkdir DF &&
+       echo DF/DF >DF/DF &&
+       git update-index --add --remove DF DF/DF &&
+       treeDFDF=$(git write-tree) &&
+       echo treeDFDF $treeDFDF &&
+       git ls-tree $treeDFDF &&
+       git ls-files --stage >DFDF.out
+'
+
+test_expect_success 'DF vs DF/DF case test.' '
+       rm -f .git/index &&
+       rm -fr DF &&
+       echo DF >DF &&
+       git update-index --add DF &&
+       read_tree_twoway $treeDF $treeDFDF &&
+       git ls-files --stage >DFDFcheck.out &&
+       test_cmp DFDF.out DFDFcheck.out &&
+       check_cache_at DF/DF dirty &&
+       :
+'
+
+test_expect_success 'a/b (untracked) vs a case setup.' '
+       rm -f .git/index &&
+       : >a &&
+       git update-index --add a &&
+       treeM=$(git write-tree) &&
+       echo treeM $treeM &&
+       git ls-tree $treeM &&
+       git ls-files --stage >treeM.out &&
+
+       rm -f a &&
+       git update-index --remove a &&
+       mkdir a &&
+       : >a/b &&
+       treeH=$(git write-tree) &&
+       echo treeH $treeH &&
+       git ls-tree $treeH
+'
+
+test_expect_success 'a/b (untracked) vs a, plus c/d case test.' '
+       read_tree_u_must_fail -u -m "$treeH" "$treeM" &&
+       git ls-files --stage &&
+       test -f a/b
+'
+
+test_expect_success 'read-tree supports the super-prefix' '
+       cat <<-EOF >expect &&
+               error: Updating '\''fictional/a'\'' would lose untracked files in it
+       EOF
+       test_must_fail git --super-prefix fictional/ read-tree -u -m "$treeH" "$treeM" 2>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'a/b vs a, plus c/d case setup.' '
+       rm -f .git/index &&
+       rm -fr a &&
+       : >a &&
+       mkdir c &&
+       : >c/d &&
+       git update-index --add a c/d &&
+       treeM=$(git write-tree) &&
+       echo treeM $treeM &&
+       git ls-tree $treeM &&
+       git ls-files --stage >treeM.out &&
+
+       rm -f a &&
+       mkdir a &&
+       : >a/b &&
+       git update-index --add --remove a a/b &&
+       treeH=$(git write-tree) &&
+       echo treeH $treeH &&
+       git ls-tree $treeH
+'
+
+test_expect_success 'a/b vs a, plus c/d case test.' '
+       read_tree_u_must_succeed -u -m "$treeH" "$treeM" &&
+       git ls-files --stage | tee >treeMcheck.out &&
+       test_cmp treeM.out treeMcheck.out
+'
 
 test_expect_success '-m references the correct modified tree' '
        echo >file-a &&
index 7e10bcfe395609cff5edc1f8f6ce95fcf69cf5f6..30354fd26c0cf6b6e2a6c01bc2bcbef48168e562 100755 (executable)
@@ -97,6 +97,9 @@ test_expect_success 'show-ref -d' '
        git show-ref -d refs/tags/A refs/tags/C >actual &&
        test_cmp expect actual &&
 
+       git show-ref --verify -d refs/tags/A refs/tags/C >actual &&
+       test_cmp expect actual &&
+
        echo $(git rev-parse refs/heads/master) refs/heads/master >expect &&
        git show-ref -d master >actual &&
        test_cmp expect actual &&
@@ -116,6 +119,12 @@ test_expect_success 'show-ref -d' '
        test_cmp expect actual &&
 
        test_must_fail git show-ref -d --verify heads/master >actual &&
+       test_cmp expect actual &&
+
+       test_must_fail git show-ref --verify -d A C >actual &&
+       test_cmp expect actual &&
+
+       test_must_fail git show-ref --verify -d tags/A tags/C >actual &&
        test_cmp expect actual
 
 '
@@ -164,4 +173,37 @@ test_expect_success 'show-ref --heads, --tags, --head, pattern' '
        test_cmp expect actual
 '
 
+test_expect_success 'show-ref --verify HEAD' '
+       echo $(git rev-parse HEAD) HEAD >expect &&
+       git show-ref --verify HEAD >actual &&
+       test_cmp expect actual &&
+
+       >expect &&
+
+       git show-ref --verify -q HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'show-ref --verify with dangling ref' '
+       sha1_file() {
+               echo "$*" | sed "s#..#.git/objects/&/#"
+       } &&
+
+       remove_object() {
+               file=$(sha1_file "$*") &&
+               test -e "$file" &&
+               rm -f "$file"
+       } &&
+
+       test_when_finished "rm -rf dangling" &&
+       (
+               git init dangling &&
+               cd dangling &&
+               test_commit dangling &&
+               sha=$(git rev-parse refs/tags/dangling) &&
+               remove_object $sha &&
+               test_must_fail git show-ref --verify refs/tags/dangling
+       )
+'
+
 test_done
index ee7d4736db5ac67e7facfee77ffca54fde340d31..33a51c9a67fe833e31e51099f7568b64be385d07 100755 (executable)
@@ -43,13 +43,13 @@ test_expect_success 'HEAD is part of refs, valid objects appear valid' '
 
 test_expect_success 'setup: helpers for corruption tests' '
        sha1_file() {
-               echo "$*" | sed "s#..#.git/objects/&/#"
+               remainder=${1#??} &&
+               firsttwo=${1%$remainder} &&
+               echo ".git/objects/$firsttwo/$remainder"
        } &&
 
        remove_object() {
-               file=$(sha1_file "$*") &&
-               test -e "$file" &&
-               rm -f "$file"
+               rm "$(sha1_file "$1")"
        }
 '
 
@@ -189,14 +189,16 @@ test_expect_success 'commit with NUL in header' '
 '
 
 test_expect_success 'tree object with duplicate entries' '
-       test_when_finished "remove_object \$T" &&
+       test_when_finished "for i in \$T; do remove_object \$i; done" &&
        T=$(
                GIT_INDEX_FILE=test-index &&
                export GIT_INDEX_FILE &&
                rm -f test-index &&
                >x &&
                git add x &&
+               git rev-parse :x &&
                T=$(git write-tree) &&
+               echo $T &&
                (
                        git cat-file tree $T &&
                        git cat-file tree $T
@@ -521,9 +523,21 @@ test_expect_success 'fsck --connectivity-only' '
                touch empty &&
                git add empty &&
                test_commit empty &&
+
+               # Drop the index now; we want to be sure that we
+               # recursively notice the broken objects
+               # because they are reachable from refs, not because
+               # they are in the index.
+               rm -f .git/index &&
+
+               # corrupt the blob, but in a way that we can still identify
+               # its type. That lets us see that --connectivity-only is
+               # not actually looking at the contents, but leaves it
+               # free to examine the type if it chooses.
                empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 &&
-               rm -f $empty &&
-               echo invalid >$empty &&
+               blob=$(echo unrelated | git hash-object -w --stdin) &&
+               mv -f $(sha1_file $blob) $empty &&
+
                test_must_fail git fsck --strict &&
                git fsck --strict --connectivity-only &&
                tree=$(git rev-parse HEAD:) &&
@@ -535,12 +549,18 @@ test_expect_success 'fsck --connectivity-only' '
        )
 '
 
-remove_loose_object () {
-       sha1="$(git rev-parse "$1")" &&
-       remainder=${sha1#??} &&
-       firsttwo=${sha1%$remainder} &&
-       rm .git/objects/$firsttwo/$remainder
-}
+test_expect_success 'fsck --connectivity-only with explicit head' '
+       rm -rf connectivity-only &&
+       git init connectivity-only &&
+       (
+               cd connectivity-only &&
+               test_commit foo &&
+               rm -f .git/index &&
+               tree=$(git rev-parse HEAD^{tree}) &&
+               remove_object $(git rev-parse HEAD:foo.t) &&
+               test_must_fail git fsck --connectivity-only $tree
+       )
+'
 
 test_expect_success 'fsck --name-objects' '
        rm -rf name-objects &&
@@ -550,11 +570,123 @@ test_expect_success 'fsck --name-objects' '
                test_commit julius caesar.t &&
                test_commit augustus &&
                test_commit caesar &&
-               remove_loose_object $(git rev-parse julius:caesar.t) &&
+               remove_object $(git rev-parse julius:caesar.t) &&
                test_must_fail git fsck --name-objects >out &&
                tree=$(git rev-parse --verify julius:) &&
                grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out
        )
 '
 
+test_expect_success 'alternate objects are correctly blamed' '
+       test_when_finished "rm -rf alt.git .git/objects/info/alternates" &&
+       git init --bare alt.git &&
+       echo "../../alt.git/objects" >.git/objects/info/alternates &&
+       mkdir alt.git/objects/12 &&
+       >alt.git/objects/12/34567890123456789012345678901234567890 &&
+       test_must_fail git fsck >out 2>&1 &&
+       grep alt.git out
+'
+
+test_expect_success 'fsck errors in packed objects' '
+       git cat-file commit HEAD >basis &&
+       sed "s/</one/" basis >one &&
+       sed "s/</foo/" basis >two &&
+       one=$(git hash-object -t commit -w one) &&
+       two=$(git hash-object -t commit -w two) &&
+       pack=$(
+               {
+                       echo $one &&
+                       echo $two
+               } | git pack-objects .git/objects/pack/pack
+       ) &&
+       test_when_finished "rm -f .git/objects/pack/pack-$pack.*" &&
+       remove_object $one &&
+       remove_object $two &&
+       test_must_fail git fsck 2>out &&
+       grep "error in commit $one.* - bad name" out &&
+       grep "error in commit $two.* - bad name" out &&
+       ! grep corrupt out
+'
+
+test_expect_success 'fsck finds problems in duplicate loose objects' '
+       rm -rf broken-duplicate &&
+       git init broken-duplicate &&
+       (
+               cd broken-duplicate &&
+               test_commit duplicate &&
+               # no "-d" here, so we end up with duplicates
+               git repack &&
+               # now corrupt the loose copy
+               file=$(sha1_file "$(git rev-parse HEAD)") &&
+               rm "$file" &&
+               echo broken >"$file" &&
+               test_must_fail git fsck
+       )
+'
+
+test_expect_success 'fsck detects trailing loose garbage (commit)' '
+       git cat-file commit HEAD >basis &&
+       echo bump-commit-sha1 >>basis &&
+       commit=$(git hash-object -w -t commit basis) &&
+       file=$(sha1_file $commit) &&
+       test_when_finished "remove_object $commit" &&
+       chmod +w "$file" &&
+       echo garbage >>"$file" &&
+       test_must_fail git fsck 2>out &&
+       test_i18ngrep "garbage.*$commit" out
+'
+
+test_expect_success 'fsck detects trailing loose garbage (blob)' '
+       blob=$(echo trailing | git hash-object -w --stdin) &&
+       file=$(sha1_file $blob) &&
+       test_when_finished "remove_object $blob" &&
+       chmod +w "$file" &&
+       echo garbage >>"$file" &&
+       test_must_fail git fsck 2>out &&
+       test_i18ngrep "garbage.*$blob" out
+'
+
+# for each of type, we have one version which is referenced by another object
+# (and so while unreachable, not dangling), and another variant which really is
+# dangling.
+test_expect_success 'fsck notices dangling objects' '
+       git init dangling &&
+       (
+               cd dangling &&
+               blob=$(echo not-dangling | git hash-object -w --stdin) &&
+               dblob=$(echo dangling | git hash-object -w --stdin) &&
+               tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) &&
+               dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) &&
+               commit=$(git commit-tree $tree) &&
+               dcommit=$(git commit-tree -p $commit $tree) &&
+
+               cat >expect <<-EOF &&
+               dangling blob $dblob
+               dangling commit $dcommit
+               dangling tree $dtree
+               EOF
+
+               git fsck >actual &&
+               # the output order is non-deterministic, as it comes from a hash
+               sort <actual >actual.sorted &&
+               test_cmp expect actual.sorted
+       )
+'
+
+test_expect_success 'fsck $name notices bogus $name' '
+       test_must_fail git fsck bogus &&
+       test_must_fail git fsck $_z40
+'
+
+test_expect_success 'bogus head does not fallback to all heads' '
+       # set up a case that will cause a reachability complaint
+       echo to-be-deleted >foo &&
+       git add foo &&
+       blob=$(git rev-parse :foo) &&
+       test_when_finished "git rm --cached foo" &&
+       remove_object $blob &&
+       test_must_fail git fsck $_z40 >out 2>&1 &&
+       ! grep $blob out
+'
+
 test_done
index c896a4c1067fc80378130a09705bef58370f01be..e2f18d11f66afe19bf28b8218042a06f452f0aa1 100755 (executable)
@@ -237,6 +237,22 @@ test_expect_success 'retain authorship' '
        git show HEAD | grep "^Author: Twerp Snog"
 '
 
+test_expect_success 'retain authorship w/ conflicts' '
+       git reset --hard twerp &&
+       test_commit a conflict a conflict-a &&
+       git reset --hard twerp &&
+       GIT_AUTHOR_NAME=AttributeMe \
+       test_commit b conflict b conflict-b &&
+       set_fake_editor &&
+       test_must_fail git rebase -i conflict-a &&
+       echo resolved >conflict &&
+       git add conflict &&
+       git rebase --continue &&
+       test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) &&
+       git show >out &&
+       grep AttributeMe out
+'
+
 test_expect_success 'squash' '
        git reset --hard twerp &&
        echo B > file7 &&
index deae948c76bd12f9b3ef6b54568717b21d641d8a..5ffe78e920c4aa292a6c16e2aa8078ede4ab065c 100755 (executable)
@@ -380,4 +380,18 @@ test_expect_success 'patch mode ignores unmerged entries' '
        test_cmp expected diff
 '
 
+test_expect_success 'diffs can be colorized' '
+       git reset --hard &&
+
+       # force color even though the test script has no terminal
+       test_config color.ui always &&
+
+       echo content >test &&
+       printf y | git add -p >output 2>&1 &&
+
+       # We do not want to depend on the exact coloring scheme
+       # git uses for diffs, so just check that we saw some kind of color.
+       grep "$(printf "\\033")" output
+'
+
 test_done
index ec78c5e3ac5661977b30582df9bec95265e1c613..671e951ee5498c7691c5e996c9dfd75dac3e8861 100755 (executable)
@@ -6,10 +6,11 @@
 test_description='Test diff/status color escape codes'
 . ./test-lib.sh
 
+ESC=$(printf '\033')
 color()
 {
        actual=$(git config --get-color no.such.slot "$1") &&
-       test "$actual" = "\e$2"
+       test "$actual" = "${2:+$ESC}$2"
 }
 
 invalid_color()
@@ -21,6 +22,10 @@ test_expect_success 'reset' '
        color "reset" "[m"
 '
 
+test_expect_success 'empty color is empty' '
+       color "" ""
+'
+
 test_expect_success 'attribute before color name' '
        color "bold red" "[1;31m"
 '
index 1ccbd5948a735f692469e181933c97e8632404b4..08ea725de3307c886d8f341b49c61c0d3436f91c 100755 (executable)
@@ -359,6 +359,28 @@ test_expect_success 'log --graph --line-prefix="| | | " with merge' '
        test_cmp expect actual
 '
 
+cat > expect.colors <<\EOF
+*   Merge branch 'side'
+<BLUE>|<RESET><CYAN>\<RESET>
+<BLUE>|<RESET> * side-2
+<BLUE>|<RESET> * side-1
+* <CYAN>|<RESET> Second
+* <CYAN>|<RESET> sixth
+* <CYAN>|<RESET> fifth
+* <CYAN>|<RESET> fourth
+<CYAN>|<RESET><CYAN>/<RESET>
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge with log.graphColors' '
+       test_config log.graphColors " blue,invalid-color, cyan, red  , " &&
+       git log --color=always --graph --date-order --pretty=tformat:%s |
+               test_decode_color | sed "s/ *\$//" >actual &&
+       test_cmp expect.colors actual
+'
+
 test_expect_success 'log --raw --graph -m with merge' '
        git log --raw --graph --oneline -m master | head -n 500 >actual &&
        grep "initial" actual
index 8198d8eb05c5366c7d61a12351c333902a2265b6..ba46e86de0a7285bf88ff5fe05e378c8bfd256f9 100755 (executable)
@@ -764,6 +764,13 @@ test_expect_success 'rename a remote with name prefix of other remote' '
        )
 '
 
+test_expect_success 'rename succeeds with existing remote.<target>.prune' '
+       git clone one four.four &&
+       test_when_finished git config --global --unset remote.upstream.prune &&
+       git config --global remote.upstream.prune true &&
+       git -C four.four remote rename origin upstream
+'
+
 cat >remotes_origin <<EOF
 URL: $(pwd)/one
 Push: refs/heads/master:refs/heads/upstream
index 1524ff5ba692d9c962ec51ef0c7948aca6ddf240..f55137f76ffac4c6f8333444b444aef65da8b3fd 100755 (executable)
@@ -454,4 +454,25 @@ test_expect_success 'push --dry-run does not recursively update submodules' '
        test_cmp expected_submodule actual_submodule
 '
 
+test_expect_success 'push --dry-run does not recursively update submodules' '
+       git -C work push --dry-run --recurse-submodules=only ../pub.git master &&
+
+       git -C submodule.git rev-parse master >actual_submodule &&
+       git -C pub.git rev-parse master >actual_pub &&
+       test_cmp expected_pub actual_pub &&
+       test_cmp expected_submodule actual_submodule
+'
+
+test_expect_success 'push only unpushed submodules recursively' '
+       git -C work/gar/bage rev-parse master >expected_submodule &&
+       git -C pub.git rev-parse master >expected_pub &&
+
+       git -C work push --recurse-submodules=only ../pub.git master &&
+
+       git -C submodule.git rev-parse master >actual_submodule &&
+       git -C pub.git rev-parse master >actual_pub &&
+       test_cmp expected_submodule actual_submodule &&
+       test_cmp expected_pub actual_pub
+'
+
 test_done
index 1cfa8a21d23173e51e95906c00342fa13b85d955..b676b90c7dfd8bf743e788f07aedc2a4a8053540 100755 (executable)
@@ -871,6 +871,22 @@ test_expect_success GPG 'verifying a forged tag should fail' '
        test_must_fail git tag -v forged-tag
 '
 
+test_expect_success 'verifying a proper tag with --format pass and format accordingly' '
+       cat >expect <<-\EOF
+       tagname : signed-tag
+       EOF &&
+       git tag -v --format="tagname : %(tag)" "signed-tag" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verifying a forged tag with --format fail and format accordingly' '
+       cat >expect <<-\EOF
+       tagname : forged-tag
+       EOF &&
+       test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual &&
+       test_cmp expect actual
+'
+
 # blank and empty messages for signed tags:
 
 get_tag_header empty-signed-tag $commit commit $time >expect
index 07079a41c4f6b9f89786d7eb9187655102ed9bd0..d62ccbb98e6148fa2677bbb763c17b3321168635 100755 (executable)
@@ -125,4 +125,20 @@ test_expect_success GPG 'verify multiple tags' '
        test_cmp expect.stderr actual.stderr
 '
 
+test_expect_success 'verifying tag with --format' '
+       cat >expect <<-\EOF
+       tagname : fourth-signed
+       EOF &&
+       git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verifying a forged tag with --format fail and format accordingly' '
+       cat >expect <<-\EOF
+       tagname : 7th forged-signed
+       EOF &&
+       test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
+       test_cmp expect actual-forged
+'
+
 test_done
index b77cce8e4066ac7f46ba1a02eab7215a4cdeaaae..c09ce0d4c1dbd8bc49595be4e3928032fbdc3c8c 100755 (executable)
@@ -152,6 +152,20 @@ test_expect_success 'submodule add to .gitignored path with --force' '
        )
 '
 
+test_expect_success 'submodule add to reconfigure existing submodule with --force' '
+       (
+               cd addtest-ignore &&
+               git submodule add --force bogus-url submod &&
+               git submodule add -b initial "$submodurl" submod-branch &&
+               test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" &&
+               test "bogus-url" = "$(git config submodule.submod.url)" &&
+               # Restore the url
+               git submodule add --force "$submodurl" submod
+               test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" &&
+               test "$submodurl" = "$(git config submodule.submod.url)"
+       )
+'
+
 test_expect_success 'submodule add --branch' '
        echo "refs/heads/initial" >expect-head &&
        cat <<-\EOF >expect-heads &&
index 725bbed1f866265e8030ab6e4007c04f9493b6b5..347857fa7c7cb12a4bc9fd6a2f5373347a02db5c 100755 (executable)
@@ -441,6 +441,16 @@ test_expect_success 'submodule update - command in .git/config catches failure -
        test_i18ncmp actual expect
 '
 
+test_expect_success 'submodule update - command run for initial population of submodule' '
+       cat <<-\ EOF >expect
+       Execution of '\''false $submodulesha1'\'' failed in submodule path '\''submodule'\''
+       EOF &&
+       rm -rf super/submodule &&
+       test_must_fail git -C super submodule update >../actual &&
+       test_cmp expect actual &&
+       git -C super submodule update --checkout
+'
+
 cat << EOF >expect
 Execution of 'false $submodulesha1' failed in submodule path '../super/submodule'
 Failed to recurse into submodule path '../super'
@@ -493,6 +503,7 @@ test_expect_success 'submodule init picks up merge' '
 '
 
 test_expect_success 'submodule update --merge  - ignores --merge  for new submodules' '
+       test_config -C super submodule.submodule.update checkout &&
        (cd super &&
         rm -rf submodule &&
         git submodule update submodule &&
@@ -505,6 +516,7 @@ test_expect_success 'submodule update --merge  - ignores --merge  for new submod
 '
 
 test_expect_success 'submodule update --rebase - ignores --rebase for new submodules' '
+       test_config -C super submodule.submodule.update checkout &&
        (cd super &&
         rm -rf submodule &&
         git submodule update submodule &&
index 1c47780e2bf1bccaef7176e1440946331ecaac6e..e2bbb449b6a175c6dbb9f43b08f2fa7061d5d9f5 100755 (executable)
@@ -64,6 +64,33 @@ test_expect_success 'absorb the git dir in a nested submodule' '
        test_cmp expect.2 actual.2
 '
 
+test_expect_success 're-setup nested submodule' '
+       # un-absorb the direct submodule, to test if the nested submodule
+       # is still correct (needs a rewrite of the gitfile only)
+       rm -rf sub1/.git &&
+       mv .git/modules/sub1 sub1/.git &&
+       GIT_WORK_TREE=. git -C sub1 config --unset core.worktree &&
+       # fixup the nested submodule
+       echo "gitdir: ../.git/modules/nested" >sub1/nested/.git &&
+       GIT_WORK_TREE=../../../nested git -C sub1/.git/modules/nested config \
+               core.worktree "../../../nested" &&
+       # make sure this re-setup is correct
+       git status --ignore-submodules=none
+'
+
+test_expect_success 'absorb the git dir in a nested submodule' '
+       git status >expect.1 &&
+       git -C sub1/nested rev-parse HEAD >expect.2 &&
+       git submodule absorbgitdirs &&
+       test -f sub1/.git &&
+       test -f sub1/nested/.git &&
+       test -d .git/modules/sub1/modules/nested &&
+       git status >actual.1 &&
+       git -C sub1/nested rev-parse HEAD >actual.2 &&
+       test_cmp expect.1 actual.1 &&
+       test_cmp expect.2 actual.2
+'
+
 test_expect_success 'setup a gitlink with missing .gitmodules entry' '
        git init sub2 &&
        test_commit -C sub2 first &&
index 5c3db656dfaa0171598445720b62e63f13f67f44..458608cc1e7ca51e1a7e40017794b5f2c55d3db3 100755 (executable)
@@ -944,4 +944,23 @@ EOF
        test_i18ncmp expected actual
 '
 
+test_expect_success 'status: handle not-yet-started rebase -i gracefully' '
+       ONTO=$(git rev-parse --short HEAD^) &&
+       COMMIT=$(git rev-parse --short HEAD) &&
+       EDITOR="git status --untracked-files=no >actual" git rebase -i HEAD^ &&
+       cat >expected <<EOF &&
+On branch several_commits
+No commands done.
+Next command to do (1 remaining command):
+   pick $COMMIT four_commit
+  (use "git rebase --edit-todo" to view and edit)
+You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
+  (use "git commit --amend" to amend the current commit)
+  (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
+       test_i18ncmp expected actual
+'
+
 test_done
index 99d4123461096196c11368864c1f8c3d524d5c1a..aa0ef02597f051b17747df4c9cf3d4b50563ed69 100755 (executable)
@@ -24,7 +24,7 @@ prompt_given ()
 }
 
 # Create a file on master and change it on branch
-test_expect_success PERL 'setup' '
+test_expect_success 'setup' '
        echo master >file &&
        git add file &&
        git commit -m "added file" &&
@@ -36,7 +36,7 @@ test_expect_success PERL 'setup' '
 '
 
 # Configure a custom difftool.<tool>.cmd and use it
-test_expect_success PERL 'custom commands' '
+test_expect_success 'custom commands' '
        difftool_test_setup &&
        test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" &&
        echo master >expect &&
@@ -49,21 +49,21 @@ test_expect_success PERL 'custom commands' '
        test_cmp expect actual
 '
 
-test_expect_success PERL 'custom tool commands override built-ins' '
+test_expect_success '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
 '
 
-test_expect_success PERL 'difftool ignores bad --tool values' '
+test_expect_success 'difftool ignores bad --tool values' '
        : >expect &&
        test_must_fail \
                git difftool --no-prompt --tool=bad-tool branch >actual &&
        test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool forwards arguments to diff' '
+test_expect_success 'difftool forwards arguments to diff' '
        difftool_test_setup &&
        >for-diff &&
        git add for-diff &&
@@ -76,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '
        rm for-diff
 '
 
-test_expect_success PERL 'difftool ignores exit code' '
+test_expect_success 'difftool ignores exit code' '
        test_config difftool.error.cmd false &&
        git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '
+test_expect_success 'difftool forwards exit code with --trust-exit-code' '
        test_config difftool.error.cmd false &&
        test_must_fail git difftool -y --trust-exit-code -t error branch
 '
 
-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '
+test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '
        test_config difftool.vimdiff.path false &&
        test_must_fail git difftool -y --trust-exit-code -t vimdiff branch
 '
 
-test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '
+test_expect_success 'difftool honors difftool.trustExitCode = true' '
        test_config difftool.error.cmd false &&
        test_config difftool.trustExitCode true &&
        test_must_fail git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '
+test_expect_success 'difftool honors difftool.trustExitCode = false' '
        test_config difftool.error.cmd false &&
        test_config difftool.trustExitCode false &&
        git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '
+test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '
        test_config difftool.error.cmd false &&
        test_config difftool.trustExitCode true &&
        git difftool -y --no-trust-exit-code -t error branch
 '
 
-test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
+test_expect_success 'difftool stops on error with --trust-exit-code' '
        test_when_finished "rm -f for-diff .git/fail-right-file" &&
        test_when_finished "git reset -- for-diff" &&
        write_script .git/fail-right-file <<-\EOF &&
@@ -124,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
        test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool honors exit status if command not found' '
+test_expect_success 'difftool honors exit status if command not found' '
        test_config difftool.nonexistent.cmd i-dont-exist &&
        test_config difftool.trustExitCode false &&
        test_must_fail git difftool -y -t nonexistent branch
 '
 
-test_expect_success PERL 'difftool honors --gui' '
+test_expect_success 'difftool honors --gui' '
        difftool_test_setup &&
        test_config merge.tool bogus-tool &&
        test_config diff.tool bogus-tool &&
@@ -141,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '
        test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --gui last setting wins' '
+test_expect_success 'difftool --gui last setting wins' '
        difftool_test_setup &&
        : >expect &&
        git difftool --no-prompt --gui --no-gui >actual &&
@@ -155,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '
        test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
+test_expect_success 'difftool --gui works without configured diff.guitool' '
        difftool_test_setup &&
        echo branch >expect &&
        git difftool --no-prompt --gui branch >actual &&
@@ -163,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'
 '
 
 # Specify the diff tool using $GIT_DIFF_TOOL
-test_expect_success PERL 'GIT_DIFF_TOOL variable' '
+test_expect_success 'GIT_DIFF_TOOL variable' '
        difftool_test_setup &&
        git config --unset diff.tool &&
        echo branch >expect &&
@@ -173,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '
 
 # 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' '
+test_expect_success 'GIT_DIFF_TOOL overrides' '
        difftool_test_setup &&
        test_config diff.tool bogus-tool &&
        test_config merge.tool bogus-tool &&
@@ -191,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
 
 # 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' '
+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
        difftool_test_setup &&
        echo branch >expect &&
        GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&
@@ -200,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
 
 # 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' '
+test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
        difftool_test_setup &&
        test_config difftool.prompt false &&
        echo >input &&
@@ -210,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
 '
 
 # 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' '
+test_expect_success 'difftool.prompt config variable is false' '
        difftool_test_setup &&
        test_config difftool.prompt false &&
        echo branch >expect &&
@@ -219,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '
 '
 
 # Test that we don't have to pass --no-prompt when mergetool.prompt is false
-test_expect_success PERL 'difftool merge.prompt = false' '
+test_expect_success 'difftool merge.prompt = false' '
        difftool_test_setup &&
        test_might_fail git config --unset difftool.prompt &&
        test_config mergetool.prompt false &&
@@ -229,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '
 '
 
 # Test that the -y flag can override difftool.prompt = true
-test_expect_success PERL 'difftool.prompt can overridden with -y' '
+test_expect_success 'difftool.prompt can overridden with -y' '
        difftool_test_setup &&
        test_config difftool.prompt true &&
        echo branch >expect &&
@@ -238,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '
 '
 
 # Test that the --prompt flag can override difftool.prompt = false
-test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
+test_expect_success 'difftool.prompt can overridden with --prompt' '
        difftool_test_setup &&
        test_config difftool.prompt false &&
        echo >input &&
@@ -248,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
 '
 
 # Test that the last flag passed on the command-line wins
-test_expect_success PERL 'difftool last flag wins' '
+test_expect_success 'difftool last flag wins' '
        difftool_test_setup &&
        echo branch >expect &&
        git difftool --prompt --no-prompt branch >actual &&
@@ -261,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '
 
 # git-difftool falls back to git-mergetool config variables
 # so test that behavior here
-test_expect_success PERL 'difftool + mergetool config variables' '
+test_expect_success 'difftool + mergetool config variables' '
        test_config merge.tool test-tool &&
        test_config mergetool.test-tool.cmd "cat \$LOCAL" &&
        echo branch >expect &&
@@ -275,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '
        test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool.<tool>.path' '
+test_expect_success 'difftool.<tool>.path' '
        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' '
+test_expect_success 'difftool --extcmd=cat' '
        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' '
+test_expect_success 'difftool --extcmd cat' '
        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' '
+test_expect_success 'difftool -x cat' '
        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' '
+test_expect_success 'difftool --extcmd echo arg1' '
        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' '
+test_expect_success 'difftool --extcmd cat arg1' '
        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' '
+test_expect_success 'difftool --extcmd cat arg2' '
        echo branch >expect &&
        git difftool --no-prompt \
                --extcmd sh\ -c\ \"cat\ \$2\" branch >actual &&
@@ -325,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
 '
 
 # Create a second file on master and a different version on branch
-test_expect_success PERL 'setup with 2 files different' '
+test_expect_success 'setup with 2 files different' '
        echo m2 >file2 &&
        git add file2 &&
        git commit -m "added file2" &&
@@ -337,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '
        git checkout master
 '
 
-test_expect_success PERL 'say no to the first file' '
+test_expect_success 'say no to the first file' '
        (echo n && echo) >input &&
        git difftool -x cat branch <input >output &&
        grep m2 output &&
@@ -346,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '
        ! grep branch output
 '
 
-test_expect_success PERL 'say no to the second file' '
+test_expect_success 'say no to the second file' '
        (echo && echo n) >input &&
        git difftool -x cat branch <input >output &&
        grep master output &&
@@ -355,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '
        ! grep br2 output
 '
 
-test_expect_success PERL 'ending prompt input with EOF' '
+test_expect_success 'ending prompt input with EOF' '
        git difftool -x cat branch </dev/null >output &&
        ! grep master output &&
        ! grep branch output &&
@@ -363,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '
        ! grep br2 output
 '
 
-test_expect_success PERL 'difftool --tool-help' '
+test_expect_success 'difftool --tool-help' '
        git difftool --tool-help >output &&
        grep tool output
 '
 
-test_expect_success PERL 'setup change in subdirectory' '
+test_expect_success 'setup change in subdirectory' '
        git checkout master &&
        mkdir sub &&
        echo master >sub/sub &&
@@ -382,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '
 '
 
 run_dir_diff_test () {
-       test_expect_success PERL "$1 --no-symlinks" "
+       test_expect_success "$1 --no-symlinks" "
                symlinks=--no-symlinks &&
                $2
        "
-       test_expect_success PERL,SYMLINKS "$1 --symlinks" "
+       test_expect_success SYMLINKS "$1 --symlinks" "
                symlinks=--symlinks &&
                $2
        "
@@ -508,7 +508,7 @@ do
 done >actual
 EOF
 
-test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
+test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
        cat >expect <<-EOF &&
        file
        $PWD/file
@@ -545,7 +545,7 @@ write_script modify-file <<\EOF
 echo "new content" >file
 EOF
 
-test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '
+test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '
        echo "orig content" >file &&
        git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch &&
        echo "new content" >expect &&
@@ -558,7 +558,7 @@ echo "tmp content" >"$2/file" &&
 echo "$2" >tmpdir
 EOF
 
-test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
+test_expect_success 'difftool --no-symlinks detects conflict ' '
        (
                TMPDIR=$TRASH_DIRECTORY &&
                export TMPDIR &&
@@ -571,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
        )
 '
 
-test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
+test_expect_success 'difftool properly honors gitlink and core.worktree' '
        git submodule add ./. submod/ule &&
        test_config -C submod/ule diff.tool checktrees &&
        test_config -C submod/ule difftool.checktrees.cmd '\''
@@ -585,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
        )
 '
 
-test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
+test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '
        git init dirlinks &&
        (
                cd dirlinks &&
index cde7fc7fcf355d04708126980d099df820cd00ce..86d77c16dd3abcedd3fd4937a73b142a6b83b7b6 100644 (file)
@@ -966,7 +966,8 @@ yes () {
 }
 
 # Fix some commands on Windows
-case $(uname -s) in
+uname_s=$(uname -s)
+case $uname_s in
 *MINGW*)
        # Windows has its own (incompatible) sort and find
        sort () {
@@ -1141,6 +1142,7 @@ test_lazy_prereq SANITY '
        return $status
 '
 
+test FreeBSD != $uname_s || GIT_UNZIP=${GIT_UNZIP:-/usr/local/bin/unzip}
 GIT_UNZIP=${GIT_UNZIP:-unzip}
 test_lazy_prereq UNZIP '
        "$GIT_UNZIP" -v
diff --git a/tag.c b/tag.c
index d1dcd18cd7b53e21fa15bab9baad05cf16a3b9de..243d1fdbbcb1424b0082e1c999920f6a50b32d68 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -3,6 +3,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
+#include "gpg-interface.h"
 
 const char *tag_type = "tag";
 
@@ -24,7 +25,9 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)
 
        ret = check_signature(buf, payload_size, buf + payload_size,
                                size - payload_size, &sigc);
-       print_signature_buffer(&sigc, flags);
+
+       if (!(flags & GPG_VERIFY_OMIT_STATUS))
+               print_signature_buffer(&sigc, flags);
 
        signature_check_clear(&sigc);
        return ret;
index c86ba2eb897f4ee2e45dadd418587ee4cb76401e..d72e0894840fc384d67339b549a9a6bce7ba03ec 100644 (file)
@@ -1015,7 +1015,9 @@ int transport_push(struct transport *transport,
                        if (run_pre_push_hook(transport, remote_refs))
                                return -1;
 
-               if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
+               if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
+                             TRANSPORT_RECURSE_SUBMODULES_ONLY)) &&
+                   !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        struct sha1_array commits = SHA1_ARRAY_INIT;
 
@@ -1033,7 +1035,8 @@ int transport_push(struct transport *transport,
                }
 
                if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) ||
-                    ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) &&
+                    ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
+                               TRANSPORT_RECURSE_SUBMODULES_ONLY)) &&
                      !pretend)) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        struct string_list needs_pushing = STRING_LIST_INIT_DUP;
@@ -1052,7 +1055,10 @@ int transport_push(struct transport *transport,
                        sha1_array_clear(&commits);
                }
 
-               push_ret = transport->push_refs(transport, remote_refs, flags);
+               if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY))
+                       push_ret = transport->push_refs(transport, remote_refs, flags);
+               else
+                       push_ret = 0;
                err = push_had_errors(remote_refs);
                ret = push_ret | err;
 
@@ -1064,7 +1070,8 @@ int transport_push(struct transport *transport,
                if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
                        set_upstreams(transport, remote_refs, pretend);
 
-               if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
+               if (!(flags & (TRANSPORT_PUSH_DRY_RUN |
+                              TRANSPORT_RECURSE_SUBMODULES_ONLY))) {
                        struct ref *ref;
                        for (ref = remote_refs; ref; ref = ref->next)
                                transport_update_tracking_ref(transport->remote, ref, verbose);
index 9820f10b8e41c6aa6ff6131e5660866d30bf541a..e597b31b38d9d7bb374af203b80e4b6e83abbc3d 100644 (file)
@@ -131,21 +131,22 @@ struct transport {
        enum transport_family family;
 };
 
-#define TRANSPORT_PUSH_ALL 1
-#define TRANSPORT_PUSH_FORCE 2
-#define TRANSPORT_PUSH_DRY_RUN 4
-#define TRANSPORT_PUSH_MIRROR 8
-#define TRANSPORT_PUSH_PORCELAIN 16
-#define TRANSPORT_PUSH_SET_UPSTREAM 32
-#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
-#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_PUSH_CERT_ALWAYS 2048
-#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
-#define TRANSPORT_PUSH_ATOMIC 8192
-#define TRANSPORT_PUSH_OPTIONS 16384
+#define TRANSPORT_PUSH_ALL                     (1<<0)
+#define TRANSPORT_PUSH_FORCE                   (1<<1)
+#define TRANSPORT_PUSH_DRY_RUN                 (1<<2)
+#define TRANSPORT_PUSH_MIRROR                  (1<<3)
+#define TRANSPORT_PUSH_PORCELAIN               (1<<4)
+#define TRANSPORT_PUSH_SET_UPSTREAM            (1<<5)
+#define TRANSPORT_RECURSE_SUBMODULES_CHECK     (1<<6)
+#define TRANSPORT_PUSH_PRUNE                   (1<<7)
+#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND (1<<8)
+#define TRANSPORT_PUSH_NO_HOOK                 (1<<9)
+#define TRANSPORT_PUSH_FOLLOW_TAGS             (1<<10)
+#define TRANSPORT_PUSH_CERT_ALWAYS             (1<<11)
+#define TRANSPORT_PUSH_CERT_IF_ASKED           (1<<12)
+#define TRANSPORT_PUSH_ATOMIC                  (1<<13)
+#define TRANSPORT_PUSH_OPTIONS                 (1<<14)
+#define TRANSPORT_RECURSE_SUBMODULES_ONLY      (1<<15)
 
 extern int transport_summary_width(const struct ref *refs);
 
index 129d49ff4517ae0f6c843a9f53c1617f3c2cb3ca..3a8ee19fe837aae6939c3a6b902f6b067dcde9e4 100644 (file)
@@ -52,6 +52,41 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
          ? ((o)->msgs[(type)])      \
          : (unpack_plumbing_errors[(type)]) )
 
+static const char *super_prefixed(const char *path)
+{
+       /*
+        * It is necessary and sufficient to have two static buffers
+        * here, as the return value of this function is fed to
+        * error() using the unpack_*_errors[] templates we see above.
+        */
+       static struct strbuf buf[2] = {STRBUF_INIT, STRBUF_INIT};
+       static int super_prefix_len = -1;
+       static unsigned idx = ARRAY_SIZE(buf) - 1;
+
+       if (super_prefix_len < 0) {
+               const char *super_prefix = get_super_prefix();
+               if (!super_prefix) {
+                       super_prefix_len = 0;
+               } else {
+                       int i;
+                       for (i = 0; i < ARRAY_SIZE(buf); i++)
+                               strbuf_addstr(&buf[i], super_prefix);
+                       super_prefix_len = buf[0].len;
+               }
+       }
+
+       if (!super_prefix_len)
+               return path;
+
+       if (++idx >= ARRAY_SIZE(buf))
+               idx = 0;
+
+       strbuf_setlen(&buf[idx], super_prefix_len);
+       strbuf_addstr(&buf[idx], path);
+
+       return buf[idx].buf;
+}
+
 void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                                  const char *cmd)
 {
@@ -172,7 +207,7 @@ static int add_rejected_path(struct unpack_trees_options *o,
                             const char *path)
 {
        if (!o->show_all_errors)
-               return error(ERRORMSG(o, e), path);
+               return error(ERRORMSG(o, e), super_prefixed(path));
 
        /*
         * Otherwise, insert in a list for future display by
@@ -196,7 +231,7 @@ static void display_error_msgs(struct unpack_trees_options *o)
                        something_displayed = 1;
                        for (i = 0; i < rejects->nr; i++)
                                strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
-                       error(ERRORMSG(o, e), path.buf);
+                       error(ERRORMSG(o, e), super_prefixed(path.buf));
                        strbuf_release(&path);
                }
                string_list_clear(rejects, 0);
@@ -1925,7 +1960,9 @@ int bind_merge(const struct cache_entry * const *src,
                             o->merge_size);
        if (a && old)
                return o->gently ? -1 :
-                       error(ERRORMSG(o, ERROR_BIND_OVERLAP), a->name, old->name);
+                       error(ERRORMSG(o, ERROR_BIND_OVERLAP),
+                             super_prefixed(a->name),
+                             super_prefixed(old->name));
        if (!a)
                return keep_entry(old, o);
        else
diff --git a/usage.c b/usage.c
index 17f52c1b5ce631eed1cdf3447d80e7223a2e6f01..ad6d2910fb58e2f27a52646bd79a0442e689517f 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -7,21 +7,19 @@
 #include "cache.h"
 
 static FILE *error_handle;
-static int tweaked_error_buffering;
 
 void vreportf(const char *prefix, const char *err, va_list params)
 {
+       char msg[4096];
        FILE *fh = error_handle ? error_handle : stderr;
+       char *p;
 
-       fflush(fh);
-       if (!tweaked_error_buffering) {
-               setvbuf(fh, NULL, _IOLBF, 0);
-               tweaked_error_buffering = 1;
+       vsnprintf(msg, sizeof(msg), err, params);
+       for (p = msg; *p; p++) {
+               if (iscntrl(*p) && *p != '\t' && *p != '\n')
+                       *p = '?';
        }
-
-       fputs(prefix, fh);
-       vfprintf(fh, err, params);
-       fputc('\n', fh);
+       fprintf(fh, "%s%s\n", prefix, msg);
 }
 
 static NORETURN void usage_builtin(const char *err, va_list params)
@@ -93,7 +91,6 @@ void set_die_is_recursing_routine(int (*routine)(void))
 void set_error_handle(FILE *fh)
 {
        error_handle = fh;
-       tweaked_error_buffering = 0;
 }
 
 void NORETURN usagef(const char *err, ...)
index 53b4771c04a2b9de15379fdd213f20f213b1be60..d633761575bff4811fa4f3f3822807b1f77f5ecb 100644 (file)
@@ -145,7 +145,7 @@ static struct worktree *get_linked_worktree(const char *id)
 
 static void mark_current_worktree(struct worktree **worktrees)
 {
-       char *git_dir = xstrdup(absolute_path(get_git_dir()));
+       char *git_dir = absolute_pathdup(get_git_dir());
        int i;
 
        for (i = 0; worktrees[i]; i++) {
index a715e71906a9a2fbd62180dd017d140f68ad14a0..4dff0b3e2108bbd41e638c7beb69e62b20fa8740 100644 (file)
@@ -1135,14 +1135,17 @@ static void abbrev_sha1_in_line(struct strbuf *line)
        strbuf_list_free(split);
 }
 
-static void read_rebase_todolist(const char *fname, struct string_list *lines)
+static int read_rebase_todolist(const char *fname, struct string_list *lines)
 {
        struct strbuf line = STRBUF_INIT;
        FILE *f = fopen(git_path("%s", fname), "r");
 
-       if (!f)
+       if (!f) {
+               if (errno == ENOENT)
+                       return -1;
                die_errno("Could not open file %s for reading",
                          git_path("%s", fname));
+       }
        while (!strbuf_getline_lf(&line, f)) {
                if (line.len && line.buf[0] == comment_line_char)
                        continue;
@@ -1152,6 +1155,7 @@ static void read_rebase_todolist(const char *fname, struct string_list *lines)
                abbrev_sha1_in_line(&line);
                string_list_append(lines, line.buf);
        }
+       return 0;
 }
 
 static void show_rebase_information(struct wt_status *s,
@@ -1166,8 +1170,10 @@ static void show_rebase_information(struct wt_status *s,
                struct string_list yet_to_do = STRING_LIST_INIT_DUP;
 
                read_rebase_todolist("rebase-merge/done", &have_done);
-               read_rebase_todolist("rebase-merge/git-rebase-todo", &yet_to_do);
-
+               if (read_rebase_todolist("rebase-merge/git-rebase-todo",
+                                        &yet_to_do))
+                       status_printf_ln(s, color,
+                               _("git-rebase-todo is missing."));
                if (have_done.nr == 0)
                        status_printf_ln(s, color, _("No commands done."));
                else {