summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.mailmap1
-rw-r--r--.travis.yml12
-rw-r--r--Documentation/CodingGuidelines37
-rw-r--r--Documentation/Makefile20
-rw-r--r--Documentation/RelNotes/2.11.1.txt48
-rw-r--r--Documentation/RelNotes/2.12.0.txt166
-rw-r--r--Documentation/SubmittingPatches13
-rw-r--r--Documentation/asciidoctor-extensions.rb28
-rwxr-xr-xDocumentation/cat-texi.perl21
-rw-r--r--Documentation/config.txt15
-rw-r--r--Documentation/git-branch.txt3
-rw-r--r--Documentation/git-difftool.txt5
-rw-r--r--Documentation/git-push.txt13
-rw-r--r--Documentation/git-relink.txt30
-rw-r--r--Documentation/git-reset.txt38
-rw-r--r--Documentation/git-submodule.txt57
-rw-r--r--Documentation/git-tag.txt8
-rw-r--r--Documentation/git-verify-tag.txt2
-rw-r--r--Documentation/rev-list-options.txt4
-rw-r--r--Documentation/technical/api-hashmap.txt4
-rw-r--r--Documentation/technical/api-in-core-index.txt21
-rw-r--r--Documentation/texi.xsl26
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--Makefile17
-rw-r--r--abspath.c7
-rw-r--r--branch.c2
-rw-r--r--builtin.h1
-rw-r--r--builtin/blame.c4
-rw-r--r--builtin/checkout.c15
-rw-r--r--builtin/clone.c4
-rw-r--r--builtin/commit.c31
-rw-r--r--builtin/difftool.c692
-rw-r--r--builtin/fetch.c2
-rw-r--r--builtin/fsck.c164
-rw-r--r--builtin/init-db.c2
-rw-r--r--builtin/merge-index.c2
-rw-r--r--builtin/push.c2
-rw-r--r--builtin/read-tree.c36
-rw-r--r--builtin/receive-pack.c3
-rw-r--r--builtin/remote.c14
-rw-r--r--builtin/rev-list.c2
-rw-r--r--builtin/show-ref.c49
-rw-r--r--builtin/submodule--helper.c2
-rw-r--r--builtin/tag.c37
-rw-r--r--builtin/verify-tag.c22
-rw-r--r--builtin/worktree.c4
-rw-r--r--cache.h60
-rw-r--r--color.c12
-rw-r--r--command-list.txt1
-rw-r--r--compat/qsort_s.c69
-rw-r--r--compat/winansi.c11
-rw-r--r--config.c7
-rw-r--r--contrib/coccinelle/xstrdup_or_null.cocci6
-rw-r--r--contrib/completion/git-completion.bash8
-rw-r--r--contrib/convert-objects/convert-objects.c329
-rw-r--r--contrib/convert-objects/git-convert-objects.txt29
-rwxr-xr-xcontrib/examples/git-difftool.perl (renamed from git-difftool.perl)0
-rw-r--r--diff.c3
-rw-r--r--dir.c7
-rw-r--r--environment.c2
-rw-r--r--exec_cmd.c14
-rw-r--r--fsck.c4
-rw-r--r--git-compat-util.h11
-rwxr-xr-xgit-p4.py2
-rwxr-xr-xgit-relink.perl173
-rwxr-xr-xgit-submodule.sh15
-rw-r--r--git.c3
-rw-r--r--gpg-interface.h5
-rw-r--r--graph.c40
-rw-r--r--help.c21
-rw-r--r--read-cache.c1
-rw-r--r--ref-filter.c33
-rw-r--r--ref-filter.h7
-rw-r--r--refs.c15
-rw-r--r--refs.h2
-rw-r--r--refs/files-backend.c8
-rw-r--r--refs/refs-internal.h2
-rw-r--r--remote.c12
-rw-r--r--remote.h4
-rw-r--r--run-command.c8
-rw-r--r--sequencer.c1107
-rw-r--r--sequencer.h4
-rw-r--r--setup.c52
-rw-r--r--sha1_file.c189
-rw-r--r--string-list.c13
-rw-r--r--submodule-config.c2
-rw-r--r--submodule.c62
-rw-r--r--submodule.h1
-rw-r--r--t/Makefile6
-rw-r--r--t/helper/test-string-list.c25
-rwxr-xr-xt/perf/p0071-sort.sh26
-rwxr-xr-xt/perf/p5302-pack-index.sh7
-rwxr-xr-xt/t0001-init.sh3
-rwxr-xr-xt/t1000-read-tree-m-3way.sh648
-rwxr-xr-xt/t1001-read-tree-m-2way.sh649
-rwxr-xr-xt/t1400-update-ref.sh80
-rwxr-xr-xt/t1403-show-ref.sh42
-rwxr-xr-xt/t1450-fsck.sh160
-rwxr-xr-xt/t3404-rebase-interactive.sh16
-rwxr-xr-xt/t3701-add-interactive.sh14
-rwxr-xr-xt/t4026-color.sh7
-rwxr-xr-xt/t4202-log.sh70
-rwxr-xr-xt/t5505-remote.sh7
-rwxr-xr-xt/t5531-deep-submodule-push.sh21
-rwxr-xr-xt/t7004-tag.sh24
-rwxr-xr-xt/t7030-verify-tag.sh16
-rwxr-xr-xt/t7400-submodule-basic.sh14
-rwxr-xr-xt/t7406-submodule-update.sh12
-rwxr-xr-xt/t7412-submodule-absorbgitdirs.sh27
-rwxr-xr-xt/t7512-status-help.sh19
-rwxr-xr-xt/t7800-difftool.sh135
-rw-r--r--t/test-lib.sh4
-rw-r--r--tag.c5
-rw-r--r--transport.c15
-rw-r--r--transport.h31
-rw-r--r--unpack-trees.c43
-rw-r--r--usage.c17
-rw-r--r--worktree.c2
-rw-r--r--wt-status.c18
120 files changed, 4354 insertions, 1852 deletions
diff --git a/.gitignore b/.gitignore
index 6722f78f9a..b1020b875f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -118,7 +118,6 @@
/git-rebase--merge
/git-receive-pack
/git-reflog
-/git-relink
/git-remote
/git-remote-http
/git-remote-https
diff --git a/.mailmap b/.mailmap
index 9c87a3840b..ab59b2fac6 100644
--- 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>
diff --git a/.travis.yml b/.travis.yml
index 3843967a69..9c63c8c3f6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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)";
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 4cd95da6b1..a4191aa388 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -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.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index a9fb497b83..b5be2e2d3f 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -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 $@+ $@
diff --git a/Documentation/RelNotes/2.11.1.txt b/Documentation/RelNotes/2.11.1.txt
index 74b193f1a8..9cd14c8197 100644
--- a/Documentation/RelNotes/2.11.1.txt
+++ b/Documentation/RelNotes/2.11.1.txt
@@ -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.
diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt
index 17409fa147..1841b065e8 100644
--- a/Documentation/RelNotes/2.12.0.txt
+++ b/Documentation/RelNotes/2.12.0.txt
@@ -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,51 @@ 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.
+
+ * "git read-tree" and its underlying unpack_trees() machinery learned
+ to report problematic paths prefixed with the --super-prefix option.
+
+ * When a submodule "A", which has another submodule "B" nested within
+ it, is "absorbed" into the top-level superproject, the inner
+ submodule "B" used to be left in a strange state. The logic to
+ adjust the .git pointers in these submodules has been corrected.
+
+ * The user can specify a custom update method that is run when
+ "submodule update" updates an already checked out submodule. This
+ was ignored when checking the submodule out for the first time and
+ we instead always just checked out the commit that is bound to the
+ path in the superproject's index.
+
+ * The command line completion (in contrib/) learned that
+ "git diff --submodule=" can take "diff" as a recently added option.
+
+ * The "core.logAllRefUpdates" that used to be boolean has been
+ enhanced to take 'always' as well, to record ref updates to refs
+ other than the ones that are expected to be updated (i.e. branches,
+ remote-tracking branches and notes).
+
Performance, Internal Implementation, Development Support etc.
@@ -101,17 +147,14 @@ 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.
- (merge b4d065df03 jc/git-open-cloexec later to maint).
* "git diff" and its family had two experimental heuristics to shift
the contents of a hunk to make the patch easier to read. One of
them turns out to be better than the other, so leave only the
"--indent-heuristic" option and remove the other one.
- (merge 3cde4e02ee jc/retire-compaction-heuristics later to maint).
* A new submodule helper "git submodule embedgitdirs" to make it
easier to move embedded .git/ directory for submodules in a
@@ -131,6 +174,21 @@ 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.
+
+ * "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.
+
+ * "uchar [40]" to "struct object_id" conversion continues.
+
Also contains various documentation updates and code clean-ups.
@@ -148,7 +206,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 +328,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 +362,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 +372,90 @@ 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.
+
+ * "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.
+
+ * Correct command line completion (in contrib/) on "git svn"
+ (merge 2cbad17642 ew/complete-svn-authorship-options later to maint).
+
+ * Incorrect usage help message for "git worktree prune" has been fixed.
+ (merge 2488dcab22 ps/worktree-prune-help-fix later to maint).
+
+ * Adjust a perf test to new world order where commands that do
+ require a repository are really strict about having a repository.
+ (merge c86000c1a7 rs/p5302-create-repositories-before-tests later to maint).
+
+ * "git log --graph" did not work well with "--name-only", even though
+ other forms of "diff" output were handled correctly.
+ (merge f5022b5fed jk/log-graph-name-only 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).
+ (merge 209df269a6 nd/rev-list-all-includes-HEAD-doc later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 08352deaae..3faf7eb884 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -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
index 0000000000..ec83b4959e
--- /dev/null
+++ b/Documentation/asciidoctor-extensions.rb
@@ -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
diff --git a/Documentation/cat-texi.perl b/Documentation/cat-texi.perl
index 87437f8a95..14d2f83415 100755
--- a/Documentation/cat-texi.perl
+++ b/Documentation/cat-texi.perl
@@ -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/);
@@ -11,13 +14,13 @@ while (<STDIN>) {
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
@@ -28,16 +31,16 @@ printf '\input texinfo
@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";
diff --git a/Documentation/config.txt b/Documentation/config.txt
index af2ae4cc02..fc5a28a320 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -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
@@ -517,10 +520,12 @@ core.logAllRefUpdates::
"`$GIT_DIR/logs/<ref>`", by appending the new and old
SHA-1, the date/time and the reason of the update, but
only when the file exists. If this configuration
- variable is set to true, missing "`$GIT_DIR/logs/<ref>`"
+ variable is set to `true`, missing "`$GIT_DIR/logs/<ref>`"
file is automatically created for branch heads (i.e. under
- refs/heads/), remote refs (i.e. under refs/remotes/),
- note refs (i.e. under refs/notes/), and the symbolic ref HEAD.
+ `refs/heads/`), remote refs (i.e. under `refs/remotes/`),
+ note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.
+ If it is set to `always`, then a missing reflog is automatically
+ created for any ref under `refs/`.
+
This information can be used to determine what commit
was the tip of a branch "2 days ago".
@@ -2036,6 +2041,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-branch.txt b/Documentation/git-branch.txt
index 5516a47b54..28d46cc03b 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -91,6 +91,9 @@ OPTIONS
based sha1 expressions such as "<branchname>@\{yesterday}".
Note that in non-bare repositories, reflogs are usually
enabled by default by the `core.logallrefupdates` config option.
+ The negated form `--no-create-reflog` only overrides an earlier
+ `--create-reflog`, but currently does not negate the setting of
+ `core.logallrefupdates`.
-f::
--force::
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
index 224fb3090b..96c26e6aa8 100644
--- a/Documentation/git-difftool.txt
+++ b/Documentation/git-difftool.txt
@@ -86,10 +86,11 @@ instead. `--no-symlinks` is the default on Windows.
Additionally, `$BASE` is set in the environment.
-g::
---gui::
+--[no-]gui::
When 'git-difftool' is invoked with the `-g` or `--gui` option
the default diff tool will be read from the configured
- `diff.guitool` variable instead of `diff.tool`.
+ `diff.guitool` variable instead of `diff.tool`. The `--no-gui`
+ option can be used to override this setting.
--[no-]trust-exit-code::
'git-difftool' invokes a diff tool individually on each file.
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 8eefabd0d1..1624a35888 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -272,7 +272,7 @@ origin +master` to force a push to the `master` branch). See the
standard error stream is not directed to a terminal.
--no-recurse-submodules::
---recurse-submodules=check|on-demand|no::
+--recurse-submodules=check|on-demand|only|no::
May be used to make sure all submodule commits used by the
revisions to be pushed are available on a remote-tracking branch.
If 'check' is used Git will verify that all submodule commits that
@@ -280,11 +280,12 @@ origin +master` to force a push to the `master` branch). See the
remote of the submodule. If any commits are missing the push will
be aborted and exit with non-zero status. If 'on-demand' is used
all submodules that changed in the revisions to be pushed will be
- pushed. If on-demand was not able to push all necessary revisions
- it will also be aborted and exit with non-zero status. A value of
- 'no' or using `--no-recurse-submodules` can be used to override the
- push.recurseSubmodules configuration variable when no submodule
- recursion is required.
+ pushed. If on-demand was not able to push all necessary revisions it will
+ also be aborted and exit with non-zero status. If 'only' is used all
+ submodules will be recursively pushed while the superproject is left
+ unpushed. A value of 'no' or using `--no-recurse-submodules` can be used
+ to override the push.recurseSubmodules configuration variable when no
+ submodule recursion is required.
--[no-]verify::
Toggle the pre-push hook (see linkgit:githooks[5]). The
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
deleted file mode 100644
index 3b33c99510..0000000000
--- a/Documentation/git-relink.txt
+++ /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
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 25432d9257..add6220fce 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -292,6 +292,44 @@ $ git reset --keep start <3>
<3> But you can use "reset --keep" to remove the unwanted commit after
you switched to "branch2".
+Split a commit into two::
++
+Suppose that you have created a commit, but later decide that you want to break
+apart the changes into two logical chunks and commit each separately. You want
+to include part of the original commit into the first commit, while including
+the remainder in a second commit. You can use git reset to rewind the history
+without changing the index, and then use git add -p to interactively select
+which hunks to put into the first commit.
++
+------------
+$ git reset HEAD^ <1>
+$ git add -p <2>
+$ git diff --cached <3>
+$ git commit -c HEAD@{1} <4>
+...
+$ git add ... <5>
+$ git diff --cached <6>
+$ git commit ... <7>
+------------
++
+<1> First, reset the history back one commit so that we remove the original
+ commit, but leave the working tree with all the changes.
+<2> Now, interactively select hunks to add to a new commit using git add -p.
+ This will ask for each hunk separately and you can use simple commands like
+ "yes, include", "no don't include" or even "edit".
+<3> Once satisfied with the hunks, you should verify that it is what you
+ expected by using git diff --cached to show all changes in the index.
+<4> Next, commit the changes stored in the index. "-c" specifies to load the
+ editor with a commit message from a previous commit so that you can re-use the
+ original commit message. HEAD@{1} is special notation to reference what
+ HEAD used to be prior to the reset command. See linkgit:git-reflog[1] for
+ more details.
+<5> Now you've created the first commit, and can repeat steps 2-4 as often as
+ you like to break the work into any number of commits. Here we show a second
+ step which simply adds the remaining changes.
+<6> Then check again that the changes are what you expected to add.
+<7> And finally commit the remaining changes.
+
DISCUSSION
----------
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index 918bd1d1bd..4a4cede144 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -9,17 +9,12 @@ git-submodule - Initialize, update or inspect submodules
SYNOPSIS
--------
[verse]
-'git submodule' [--quiet] add [-b <branch>] [-f|--force] [--name <name>]
- [--reference <repository>] [--depth <depth>] [--] <repository> [<path>]
+'git submodule' [--quiet] add [<options>] [--] <repository> [<path>]
'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] deinit [-f|--force] (--all|[--] <path>...)
-'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]
- [--[no-]recommend-shallow] [-f|--force] [--rebase|--merge]
- [--reference <repository>] [--depth <depth>] [--recursive]
- [--jobs <n>] [--] [<path>...]
-'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
- [commit] [--] [<path>...]
+'git submodule' [--quiet] update [<options>] [--] [<path>...]
+'git submodule' [--quiet] summary [<options>] [--] [<path>...]
'git submodule' [--quiet] foreach [--recursive] <command>
'git submodule' [--quiet] sync [--recursive] [--] [<path>...]
'git submodule' [--quiet] absorbgitdirs [--] [<path>...]
@@ -63,7 +58,7 @@ if you choose to go that route.
COMMANDS
--------
-add::
+add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--depth <depth>] [--] <repository> [<path>]::
Add the given repository as a submodule at the given path
to the changeset to be committed next to the current
project: the current project is termed the "superproject".
@@ -104,7 +99,7 @@ together in the same relative location, and only the
superproject's URL needs to be provided: git-submodule will correctly
locate the submodule using the relative URL in .gitmodules.
-status::
+status [--cached] [--recursive] [--] [<path>...]::
Show the status of the submodules. This will print the SHA-1 of the
currently checked out commit for each submodule, along with the
submodule path and the output of 'git describe' for the
@@ -121,7 +116,7 @@ submodules with respect to the commit recorded in the index or the HEAD,
linkgit:git-status[1] and linkgit:git-diff[1] will provide that information
too (and can also report changes to a submodule's work tree).
-init::
+init [--] [<path>...]::
Initialize the submodules recorded in the index (which were
added and committed elsewhere) by copying submodule
names and urls from .gitmodules to .git/config.
@@ -136,7 +131,7 @@ init::
the explicit 'init' step if you do not intend to customize
any submodule locations.
-deinit::
+deinit [-f|--force] (--all|[--] <path>...)::
Unregister the given submodules, i.e. remove the whole
`submodule.$name` section from .git/config together with their work
tree. Further calls to `git submodule update`, `git submodule foreach`
@@ -152,20 +147,20 @@ instead of deinit-ing everything, to prevent mistakes.
If `--force` is specified, the submodule's working tree will
be removed even if it contains local modifications.
-update::
+update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...]::
+
--
Update the registered submodules to match what the superproject
expects by cloning missing submodules and updating the working tree of
the submodules. The "updating" can be done in several ways depending
on command line options and the value of `submodule.<name>.update`
-configuration variable. Supported update procedures are:
+configuration variable. The command line option takes precedence over
+the configuration variable. if neither is given, a checkout is performed.
+update procedures supported both from the command line as well as setting
+`submodule.<name>.update`:
checkout;; the commit recorded in the superproject will be
- checked out in the submodule on a detached HEAD. This is
- done when `--checkout` option is given, or no option is
- given, and `submodule.<name>.update` is unset, or if it is
- set to 'checkout'.
+ checked out in the submodule on a detached HEAD.
+
If `--force` is specified, the submodule will be checked out (using
`git checkout --force` if appropriate), even if the commit specified
@@ -173,23 +168,21 @@ in the index of the containing repository already matches the commit
checked out in the submodule.
rebase;; the current branch of the submodule will be rebased
- onto the commit recorded in the superproject. This is done
- when `--rebase` option is given, or no option is given, and
- `submodule.<name>.update` is set to 'rebase'.
+ onto the commit recorded in the superproject.
merge;; the commit recorded in the superproject will be merged
- into the current branch in the submodule. This is done
- when `--merge` option is given, or no option is given, and
- `submodule.<name>.update` is set to 'merge'.
+ into the current branch in the submodule.
+
+The following procedures are only available via the `submodule.<name>.update`
+configuration variable:
custom command;; arbitrary shell command that takes a single
argument (the sha1 of the commit recorded in the
- superproject) is executed. This is done when no option is
- given, and `submodule.<name>.update` has the form of
- '!command'.
+ superproject) is executed. When `submodule.<name>.update`
+ is set to '!command', the remainder after the exclamation mark
+ is the custom command.
-When no option is given and `submodule.<name>.update` is set to 'none',
-the submodule is not updated.
+ none;; the submodule is not updated.
If the submodule is not yet initialized, and you just want to use the
setting as stored in .gitmodules, you can automatically initialize the
@@ -198,7 +191,7 @@ submodule with the `--init` option.
If `--recursive` is specified, this command will recurse into the
registered submodules, and update any nested submodules within.
--
-summary::
+summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]::
Show commit summary between the given commit (defaults to HEAD) and
working tree/index. For a submodule in question, a series of commits
in the submodule between the given super project commit and the
@@ -211,7 +204,7 @@ summary::
Using the `--submodule=log` option with linkgit:git-diff[1] will provide that
information too.
-foreach::
+foreach [--recursive] <command>::
Evaluates an arbitrary shell command in each checked out submodule.
The command has access to the variables $name, $path, $sha1 and
$toplevel:
@@ -232,7 +225,7 @@ As an example, +git submodule foreach \'echo $path {backtick}git
rev-parse HEAD{backtick}'+ will show the path and currently checked out
commit for each submodule.
-sync::
+sync [--recursive] [--] [<path>...]::
Synchronizes submodules' remote URL configuration setting
to the value specified in .gitmodules. It will only affect those
submodules which already have a URL entry in .git/config (that is the
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 5055a96823..525737a5d8 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -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
-----------
@@ -150,7 +150,11 @@ This option is only applicable when listing tags without annotation lines.
'strip' removes both whitespace and commentary.
--create-reflog::
- Create a reflog for the tag.
+ Create a reflog for the tag. To globally enable reflogs for tags, see
+ `core.logAllRefUpdates` in linkgit:git-config[1].
+ The negated form `--no-create-reflog` only overrides an earlier
+ `--create-reflog`, but currently does not negate the setting of
+ `core.logallrefupdates`.
<tagname>::
The name of the tag to create, delete, or describe.
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
index d590edcebd..0b8075dad9 100644
--- a/Documentation/git-verify-tag.txt
+++ b/Documentation/git-verify-tag.txt
@@ -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
-----------
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 5da7cf5a8d..a02f7324c0 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -133,8 +133,8 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
for all following revision specifiers, up to the next `--not`.
--all::
- Pretend as if all the refs in `refs/` are listed on the
- command line as '<commit>'.
+ Pretend as if all the refs in `refs/`, along with `HEAD`, are
+ listed on the command line as '<commit>'.
--branches[=<pattern>]::
Pretend as if all the refs in `refs/heads` are listed
diff --git a/Documentation/technical/api-hashmap.txt b/Documentation/technical/api-hashmap.txt
index 28f5a8b715..a3f020cd9e 100644
--- a/Documentation/technical/api-hashmap.txt
+++ b/Documentation/technical/api-hashmap.txt
@@ -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
index adbdbf5d75..0000000000
--- a/Documentation/technical/api-in-core-index.txt
+++ /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
index 0000000000..0f8ff07eca
--- /dev/null
+++ b/Documentation/texi.xsl
@@ -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>
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index f95b04bb36..1eabbd88ac 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.11.GIT
+DEF_VER=v2.12.0-rc1
LF='
'
diff --git a/Makefile b/Makefile
index 27afd0f378..8e4081e061 100644
--- 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
diff --git a/abspath.c b/abspath.c
index fce40fddcc..2f0c26e0e2 100644
--- 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
diff --git a/branch.c b/branch.c
index c431cbf6a9..b955d4f316 100644
--- a/branch.c
+++ b/branch.c
@@ -298,7 +298,7 @@ void create_branch(const char *name, const char *start_name,
start_name);
if (reflog)
- log_all_ref_updates = 1;
+ log_all_ref_updates = LOG_REFS_NORMAL;
if (!dont_change_ref) {
struct ref_transaction *transaction;
diff --git a/builtin.h b/builtin.h
index b9122bc5f4..67f80519da 100644
--- 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);
diff --git a/builtin/blame.c b/builtin/blame.c
index 126b8c9e5b..cffc626540 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1901,7 +1901,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
struct origin *suspect = ent->suspect;
char hex[GIT_SHA1_HEXSZ + 1];
- sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
+ oid_to_hex_r(hex, &suspect->commit->object.oid);
printf("%s %d %d %d\n",
hex,
ent->s_lno + 1,
@@ -1941,7 +1941,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
get_commit_info(suspect->commit, &ci, 1);
- sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
+ oid_to_hex_r(hex, &suspect->commit->object.oid);
cp = nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index bfe685c198..f174f50303 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -56,8 +56,8 @@ static int post_checkout_hook(struct commit *old, struct commit *new,
int changed)
{
return run_hook_le(NULL, "post-checkout",
- sha1_to_hex(old ? old->object.oid.hash : null_sha1),
- sha1_to_hex(new ? new->object.oid.hash : null_sha1),
+ oid_to_hex(old ? &old->object.oid : &null_oid),
+ oid_to_hex(new ? &new->object.oid : &null_oid),
changed ? "1" : "0", NULL);
/* "new" can be NULL when checking out from the index before
a commit exists. */
@@ -612,22 +612,25 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
const char *old_desc, *reflog_msg;
if (opts->new_branch) {
if (opts->new_orphan_branch) {
- if (opts->new_branch_log && !log_all_ref_updates) {
+ char *refname;
+
+ refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
+ if (opts->new_branch_log &&
+ !should_autocreate_reflog(refname)) {
int ret;
- char *refname;
struct strbuf err = STRBUF_INIT;
- refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
ret = safe_create_reflog(refname, 1, &err);
- free(refname);
if (ret) {
fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
opts->new_orphan_branch, err.buf);
strbuf_release(&err);
+ free(refname);
return;
}
strbuf_release(&err);
}
+ free(refname);
}
else
create_branch(opts->new_branch, new->name,
diff --git a/builtin/clone.c b/builtin/clone.c
index 5ef81927a6..3f63edbbf9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -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
diff --git a/builtin/commit.c b/builtin/commit.c
index 711f96cc43..2de5f6cc64 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -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_env, name, 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
index 0000000000..d13350ce83
--- /dev/null
+++ b/builtin/difftool.c
@@ -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()
+ };
+
+ 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();
+
+ /* 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);
+
+ 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);
+}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index f1570e3464..b5ad09d046 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -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);
}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index f01b81eebf..1a5caccd0f 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -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;
}
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 76d68fad00..1d4d6a0078 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -262,7 +262,7 @@ static int create_default_files(const char *template_path,
const char *work_tree = get_git_work_tree();
git_config_set("core.bare", "false");
/* allow template config file to override the default */
- if (log_all_ref_updates == -1)
+ if (log_all_ref_updates == LOG_REFS_UNSET)
git_config_set("core.logallrefupdates", "true");
if (needs_work_tree_config(original_git_dir, work_tree))
git_config_set("core.worktree", work_tree);
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index ce356b1da1..2d1b6db6bd 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -22,7 +22,7 @@ static int merge_entry(int pos, const char *path)
if (strcmp(ce->name, path))
break;
found++;
- sha1_to_hex_r(hexbuf[stage], ce->oid.hash);
+ oid_to_hex_r(hexbuf[stage], &ce->oid);
xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode);
arguments[stage] = hexbuf[stage];
arguments[stage + 4] = ownbuf[stage];
diff --git a/builtin/push.c b/builtin/push.c
index 9307ad56a9..5c22e9f2e5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -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/*");
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index fa6edb35b2..8ba64bc785 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -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()
};
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 6b97cbdbe9..1dbb8a0692 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -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,
diff --git a/builtin/remote.c b/builtin/remote.c
index e52cf3925b..5339ed6ad1 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -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) {
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index c43decda70..0aa93d5891 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -237,7 +237,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
cnt = reaches;
if (revs->commits)
- sha1_to_hex_r(hex, revs->commits->item->object.oid.hash);
+ oid_to_hex_r(hex, &revs->commits->item->object.oid);
if (flags & BISECT_SHOW_ALL) {
traverse_commit_list(revs, show_commit, show_object, info);
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 6d4e669002..013d241abc 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -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);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 74614a951e..899dc334e3 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -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)) {
diff --git a/builtin/tag.c b/builtin/tag.c
index 73df728114..e40c4a9676 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -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)
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 99f8148cf7..5199553d91 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -12,9 +12,10 @@
#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/builtin/worktree.c b/builtin/worktree.c
index 9a97e37a3f..831fe058a5 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -125,9 +125,9 @@ static int prune(int ac, const char **av, const char *prefix)
{
struct option options[] = {
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
- OPT__VERBOSE(&verbose, N_("report pruned objects")),
+ OPT__VERBOSE(&verbose, N_("report pruned working trees")),
OPT_EXPIRY_DATE(0, "expire", &expire,
- N_("expire objects older than <time>")),
+ N_("expire working trees older than <time>")),
OPT_END()
};
diff --git a/cache.h b/cache.h
index 00a029af36..61fc86e6d7 100644
--- 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);
@@ -660,7 +695,6 @@ extern int minimum_abbrev, default_abbrev;
extern int ignore_case;
extern int assume_unchanged;
extern int prefer_symlink_refs;
-extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
extern int warn_on_object_refname_ambiguity;
extern const char *apply_default_whitespace;
@@ -728,6 +762,14 @@ enum hide_dotfiles_type {
};
extern enum hide_dotfiles_type hide_dotfiles;
+enum log_refs_config {
+ LOG_REFS_UNSET = -1,
+ LOG_REFS_NONE = 0,
+ LOG_REFS_NORMAL,
+ LOG_REFS_ALWAYS
+};
+extern enum log_refs_config log_all_ref_updates;
+
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
@@ -1069,6 +1111,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);
@@ -1143,6 +1186,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
* function does not respect replace references.
diff --git a/color.c b/color.c
index 1b95e6b2a7..dee61557e0 100644
--- 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;
}
diff --git a/command-list.txt b/command-list.txt
index 2a94137bbb..a1fad28fd8 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -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
index 0000000000..52d1f0a73d
--- /dev/null
+++ b/compat/qsort_s.c
@@ -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;
+}
diff --git a/compat/winansi.c b/compat/winansi.c
index 3c9ed3cfe0..82b89ab137 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -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).
diff --git a/config.c b/config.c
index b680f79732..c6b874a7bf 100644
--- a/config.c
+++ b/config.c
@@ -826,7 +826,12 @@ static int git_default_core_config(const char *var, const char *value)
}
if (!strcmp(var, "core.logallrefupdates")) {
- log_all_ref_updates = git_config_bool(var, value);
+ if (value && !strcasecmp(value, "always"))
+ log_all_ref_updates = LOG_REFS_ALWAYS;
+ else if (git_config_bool(var, value))
+ log_all_ref_updates = LOG_REFS_NORMAL;
+ else
+ log_all_ref_updates = LOG_REFS_NONE;
return 0;
}
diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci
index 3fceef132b..8e05d1ca4b 100644
--- a/contrib/coccinelle/xstrdup_or_null.cocci
+++ b/contrib/coccinelle/xstrdup_or_null.cocci
@@ -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/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 6721ff80fb..6c6e1c774d 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1206,7 +1206,7 @@ _git_describe ()
__git_diff_algorithms="myers minimal patience histogram"
-__git_diff_submodule_formats="log short"
+__git_diff_submodule_formats="diff log short"
__git_diff_common_options="--stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
@@ -1674,7 +1674,7 @@ _git_pull ()
__git_complete_remote_or_refspec
}
-__git_push_recurse_submodules="check on-demand"
+__git_push_recurse_submodules="check on-demand only"
__git_complete_force_with_lease ()
{
@@ -2587,14 +2587,14 @@ _git_svn ()
--no-metadata --use-svm-props --use-svnsync-props
--log-window-size= --no-checkout --quiet
--repack-flags --use-log-author --localtime
+ --add-author-from
--ignore-paths= --include-paths= $remote_opts
"
local init_opts="
--template= --shared= --trunk= --tags=
--branches= --stdlayout --minimize-url
--no-metadata --use-svm-props --use-svnsync-props
- --rewrite-root= --prefix= --use-log-author
- --add-author-from $remote_opts
+ --rewrite-root= --prefix= $remote_opts
"
local cmt_opts="
--edit --rmdir --find-copies-harder --copy-similarity=
diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c
deleted file mode 100644
index f3b57bf1d2..0000000000
--- a/contrib/convert-objects/convert-objects.c
+++ /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
index f871880cfb..0000000000
--- a/contrib/convert-objects/git-convert-objects.txt
+++ /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/git-difftool.perl b/contrib/examples/git-difftool.perl
index df59bdfe97..df59bdfe97 100755
--- a/git-difftool.perl
+++ b/contrib/examples/git-difftool.perl
diff --git a/diff.c b/diff.c
index 6c4f3f6b72..051761be40 100644
--- a/diff.c
+++ b/diff.c
@@ -3015,7 +3015,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
if (!one->oid_valid)
sha1_to_hex_r(temp->hex, null_sha1);
else
- sha1_to_hex_r(temp->hex, one->oid.hash);
+ oid_to_hex_r(temp->hex, &one->oid);
/* Even though we may sometimes borrow the
* contents from the work tree, we always want
* one->mode. mode is trustworthy even when
@@ -4450,6 +4450,7 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
name_a = p->two->path;
name_b = NULL;
strip_prefix(opt->prefix_length, &name_a, &name_b);
+ fprintf(opt->file, "%s", diff_line_prefix(opt));
write_name_quoted(name_a, opt->file, opt->line_termination);
}
}
diff --git a/dir.c b/dir.c
index 65c3e681b8..4541f9e146 100644
--- a/dir.c
+++ b/dir.c
@@ -174,20 +174,19 @@ char *common_prefix(const struct pathspec *pathspec)
int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)
{
- char *prefix;
+ const char *prefix;
size_t prefix_len;
/*
* Calculate common prefix for the pathspec, and
* use that to optimize the directory walk
*/
- prefix = common_prefix(pathspec);
- prefix_len = prefix ? strlen(prefix) : 0;
+ prefix_len = common_prefix_len(pathspec);
+ prefix = prefix_len ? pathspec->items[0].match : "";
/* Read the directory and prune it */
read_directory(dir, prefix, prefix_len, pathspec);
- free(prefix);
return prefix_len;
}
diff --git a/environment.c b/environment.c
index 8a83101d04..c07fb17fb7 100644
--- a/environment.c
+++ b/environment.c
@@ -21,7 +21,6 @@ int ignore_case;
int assume_unchanged;
int prefer_symlink_refs;
int is_bare_repository_cfg = -1; /* unspecified */
-int log_all_ref_updates = -1; /* unspecified */
int warn_ambiguous_refs = 1;
int warn_on_object_refname_ambiguity = 1;
int ref_paranoia = -1;
@@ -64,6 +63,7 @@ int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg;
enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
+enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET;
#ifndef PROTECT_HFS_DEFAULT
#define PROTECT_HFS_DEFAULT 0
diff --git a/exec_cmd.c b/exec_cmd.c
index 19ac2146d0..fb94aeba9c 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -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 4a3069e204..939792752b 100644
--- 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;
diff --git a/git-compat-util.h b/git-compat-util.h
index 66cd466eea..ef6d560e15 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -998,6 +998,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-p4.py b/git-p4.py
index 7bda915bd0..9695d2ed3e 100755
--- 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
index 236a3521a1..0000000000
--- a/git-relink.perl
+++ /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);
-}
diff --git a/git-submodule.sh b/git-submodule.sh
index 9788175979..136e26a2c8 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -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 b367cf6686..c887272b12 100644
--- 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 },
diff --git a/gpg-interface.h b/gpg-interface.h
index ea68885ad5..d2d4fd3a65 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -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 29b0f51dc5..8b9049dd2c 100644
--- 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 53e2a67e00..bc6cd19cf3 100644
--- 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);
}
diff --git a/read-cache.c b/read-cache.c
index 7a9a7de91e..9054369dd0 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -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];
diff --git a/ref-filter.c b/ref-filter.c
index 1a978405e6..3820b21cc7 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -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)
{
diff --git a/ref-filter.h b/ref-filter.h
index fc55fa3574..7b05592baf 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -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 */
diff --git a/refs.c b/refs.c
index 9bd0bc177b..cd36b64ed9 100644
--- a/refs.c
+++ b/refs.c
@@ -638,12 +638,17 @@ int copy_reflog_msg(char *buf, const char *msg)
int should_autocreate_reflog(const char *refname)
{
- if (!log_all_ref_updates)
+ switch (log_all_ref_updates) {
+ case LOG_REFS_ALWAYS:
+ return 1;
+ case LOG_REFS_NORMAL:
+ return starts_with(refname, "refs/heads/") ||
+ starts_with(refname, "refs/remotes/") ||
+ starts_with(refname, "refs/notes/") ||
+ !strcmp(refname, "HEAD");
+ default:
return 0;
- return starts_with(refname, "refs/heads/") ||
- starts_with(refname, "refs/remotes/") ||
- starts_with(refname, "refs/notes/") ||
- !strcmp(refname, "HEAD");
+ }
}
int is_branch(const char *refname)
diff --git a/refs.h b/refs.h
index 6947843913..9fbff90e79 100644
--- a/refs.h
+++ b/refs.h
@@ -64,6 +64,8 @@ int read_ref(const char *refname, unsigned char *sha1);
int ref_exists(const char *refname);
+int should_autocreate_reflog(const char *refname);
+
int is_branch(const char *refname);
extern int refs_init_db(struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index f9023939d5..c041d4ba21 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -697,7 +697,7 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
if (peel_entry(entry, 0))
return -1;
- hashcpy(peeled->hash, entry->u.value.peeled.hash);
+ oidcpy(peeled, &entry->u.value.peeled);
return 0;
}
@@ -2682,7 +2682,7 @@ static int files_rename_ref(struct ref_store *ref_store,
}
flag = log_all_ref_updates;
- log_all_ref_updates = 0;
+ log_all_ref_updates = LOG_REFS_NONE;
if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
commit_ref_update(refs, lock, orig_sha1, NULL, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
@@ -2835,8 +2835,8 @@ static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
{
int logfd, result, oflags = O_APPEND | O_WRONLY;
- if (log_all_ref_updates < 0)
- log_all_ref_updates = !is_bare_repository();
+ if (log_all_ref_updates == LOG_REFS_UNSET)
+ log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 708b26082a..25444cf5b0 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -133,8 +133,6 @@ int verify_refname_available(const char *newname,
*/
int copy_reflog_msg(char *buf, const char *msg);
-int should_autocreate_reflog(const char *refname);
-
/**
* Information needed for a single ref update. Set new_sha1 to the new
* value or to null_sha1 to delete the ref. To check the old value
diff --git a/remote.c b/remote.c
index d5eaec7374..bf1bf23091 100644
--- 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)
diff --git a/remote.h b/remote.h
index 924881169d..a5bbbe0ef9 100644
--- 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);
diff --git a/run-command.c b/run-command.c
index 73bfba7ef9..5227f78aea 100644
--- a/run-command.c
+++ b/run-command.c
@@ -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;
}
diff --git a/sequencer.c b/sequencer.c
index 9adb7bbf1d..1f729b053b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -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,6 +32,59 @@ 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
@@ -37,24 +92,57 @@ static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
*/
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)
diff --git a/sequencer.h b/sequencer.h
index 7a513c576b..f885b68395 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -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 1b534a7508..967f289f1e 100644
--- 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 */
diff --git a/sha1_file.c b/sha1_file.c
index b5e827ac9e..ec957db5e1 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -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;
+}
diff --git a/string-list.c b/string-list.c
index 8c83cac189..45016ad86d 100644
--- a/string-list.c
+++ b/string-list.c
@@ -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,
diff --git a/submodule-config.c b/submodule-config.c
index 4bf50f398a..93453909cf 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -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
diff --git a/submodule.c b/submodule.c
index 4c4f033e8a..3b98766a6b 100644
--- a/submodule.c
+++ b/submodule.c
@@ -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);
}
diff --git a/submodule.h b/submodule.h
index b7fe4d2027..05ab674f06 100644
--- a/submodule.h
+++ b/submodule.h
@@ -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,
diff --git a/t/Makefile b/t/Makefile
index d613935f14..1bb06c36f2 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -35,6 +35,12 @@ all: $(DEFAULT_TEST_TARGET)
test: pre-clean $(TEST_LINT)
$(MAKE) aggregate-results-and-cleanup
+failed:
+ @failed=$$(cd '$(TEST_RESULTS_DIRECTORY_SQ)' && \
+ grep -l '^failed [1-9]' *.counts | \
+ sed -n 's/\.counts$$/.sh/p') && \
+ test -z "$$failed" || $(MAKE) $$failed
+
prove: pre-clean $(TEST_LINT)
@echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
$(MAKE) clean-except-prove-cache
diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c
index 4a68967bd1..c502fa16d3 100644
--- a/t/helper/test-string-list.c
+++ b/t/helper/test-string-list.c
@@ -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
index 0000000000..7c9a35a646
--- /dev/null
+++ b/t/perf/p0071-sort.sh
@@ -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
diff --git a/t/perf/p5302-pack-index.sh b/t/perf/p5302-pack-index.sh
index 5ee9211f98..99bdb16c85 100755
--- a/t/perf/p5302-pack-index.sh
+++ b/t/perf/p5302-pack-index.sh
@@ -13,6 +13,13 @@ test_expect_success 'repack' '
export PACK
'
+test_expect_success 'create target repositories' '
+ for repo in t1 t2 t3 t4 t5 t6
+ do
+ git init --bare $repo
+ done
+'
+
test_perf 'index-pack 0 threads' '
GIT_DIR=t1 git index-pack --threads=1 --stdin < $PACK
'
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index b8fc588b19..e424de5363 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -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 &&
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
index a0b79b4839..3c4d2d6045 100755
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -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
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
index db1b6f5cf4..5ededd8e40 100755
--- a/t/t1001-read-tree-m-2way.sh
+++ b/t/t1001-read-tree-m-2way.sh
@@ -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 &&
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index d4fb977060..b0ffc0b573 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -8,23 +8,33 @@ test_description='Test git update-ref and basic ref logging'
Z=$_z40
-test_expect_success setup '
+m=refs/heads/master
+n_dir=refs/heads/gu
+n=$n_dir/fixes
+outside=refs/foo
+bare=bare-repo
+create_test_commits ()
+{
+ prfx="$1"
for name in A B C D E F
do
test_tick &&
T=$(git write-tree) &&
sha1=$(echo $name | git commit-tree $T) &&
- eval $name=$sha1
+ eval $prfx$name=$sha1
done
+}
+test_expect_success setup '
+ create_test_commits "" &&
+ mkdir $bare &&
+ cd $bare &&
+ git init --bare &&
+ create_test_commits "bare" &&
+ cd -
'
-m=refs/heads/master
-n_dir=refs/heads/gu
-n=$n_dir/fixes
-outside=refs/foo
-
test_expect_success \
"create $m" \
"git update-ref $m $A &&
@@ -93,6 +103,61 @@ test_expect_success 'update-ref creates reflogs with --create-reflog' '
git reflog exists $outside
'
+test_expect_success 'creates no reflog in bare repository' '
+ git -C $bare update-ref $m $bareA &&
+ git -C $bare rev-parse $bareA >expect &&
+ git -C $bare rev-parse $m >actual &&
+ test_cmp expect actual &&
+ test_must_fail git -C $bare reflog exists $m
+'
+
+test_expect_success 'core.logAllRefUpdates=true creates reflog in bare repository' '
+ test_when_finished "git -C $bare config --unset core.logAllRefUpdates && \
+ rm $bare/logs/$m" &&
+ git -C $bare config core.logAllRefUpdates true &&
+ git -C $bare update-ref $m $bareB &&
+ git -C $bare rev-parse $bareB >expect &&
+ git -C $bare rev-parse $m >actual &&
+ test_cmp expect actual &&
+ git -C $bare reflog exists $m
+'
+
+test_expect_success 'core.logAllRefUpdates=true does not create reflog by default' '
+ test_config core.logAllRefUpdates true &&
+ test_when_finished "git update-ref -d $outside" &&
+ git update-ref $outside $A &&
+ git rev-parse $A >expect &&
+ git rev-parse $outside >actual &&
+ test_cmp expect actual &&
+ test_must_fail git reflog exists $outside
+'
+
+test_expect_success 'core.logAllRefUpdates=always creates reflog by default' '
+ test_config core.logAllRefUpdates always &&
+ test_when_finished "git update-ref -d $outside" &&
+ git update-ref $outside $A &&
+ git rev-parse $A >expect &&
+ git rev-parse $outside >actual &&
+ test_cmp expect actual &&
+ git reflog exists $outside
+'
+
+test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' '
+ test_config core.logAllRefUpdates always &&
+ git update-ref ORIG_HEAD $A &&
+ test_must_fail git reflog exists ORIG_HEAD
+'
+
+test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' '
+ test_config core.logAllRefUpdates true &&
+ test_when_finished "git update-ref -d $outside" &&
+ git update-ref --no-create-reflog $outside $A &&
+ git rev-parse $A >expect &&
+ git rev-parse $outside >actual &&
+ test_cmp expect actual &&
+ test_must_fail git reflog exists $outside
+'
+
test_expect_success \
"create $m (by HEAD)" \
"git update-ref HEAD $A &&
@@ -501,6 +566,7 @@ test_expect_success 'stdin does not create reflogs by default' '
'
test_expect_success 'stdin creates reflogs with --create-reflog' '
+ test_when_finished "git update-ref -d $outside" &&
echo "create $outside $m" >stdin &&
git update-ref --create-reflog --stdin <stdin &&
git rev-parse $m >expect &&
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 7e10bcfe39..30354fd26c 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -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
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index ee7d4736db..33a51c9a67 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -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
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index c896a4c106..e2f18d11f6 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -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 &&
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index deae948c76..5ffe78e920 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -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
diff --git a/t/t4026-color.sh b/t/t4026-color.sh
index ec78c5e3ac..671e951ee5 100755
--- a/t/t4026-color.sh
+++ b/t/t4026-color.sh
@@ -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" = "$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"
'
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 1ccbd5948a..48b55bfd27 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -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
@@ -1190,6 +1212,54 @@ test_expect_success 'log --line-prefix="*** " --graph with diff and stats' '
test_i18ncmp expect actual.sanitized
'
+cat >expect <<-\EOF
+* reach
+|
+| A reach.t
+* Merge branch 'tangle'
+* Merge branch 'side'
+|\
+| * side-2
+|
+| A 2
+* Second
+|
+| A one
+* sixth
+
+ D a/two
+EOF
+
+test_expect_success 'log --graph with --name-status' '
+ git log --graph --format=%s --name-status tangle..reach >actual &&
+ sanitize_output <actual >actual.sanitized &&
+ test_cmp expect actual.sanitized
+'
+
+cat >expect <<-\EOF
+* reach
+|
+| reach.t
+* Merge branch 'tangle'
+* Merge branch 'side'
+|\
+| * side-2
+|
+| 2
+* Second
+|
+| one
+* sixth
+
+ a/two
+EOF
+
+test_expect_success 'log --graph with --name-only' '
+ git log --graph --format=%s --name-only tangle..reach >actual &&
+ sanitize_output <actual >actual.sanitized &&
+ test_cmp expect actual.sanitized
+'
+
test_expect_success 'dotdot is a parent directory' '
mkdir -p a/b &&
( echo sixth && echo fifth ) >expect &&
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 8198d8eb05..ba46e86de0 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -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
diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh
index 1524ff5ba6..f55137f76f 100755
--- a/t/t5531-deep-submodule-push.sh
+++ b/t/t5531-deep-submodule-push.sh
@@ -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
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 1cfa8a21d2..072e6c6b88 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -71,6 +71,7 @@ test_expect_success 'creating a tag for an unknown revision should fail' '
# commit used in the tests, test_tick is also called here to freeze the date:
test_expect_success 'creating a tag using default HEAD should succeed' '
+ test_config core.logAllRefUpdates true &&
test_tick &&
echo foo >foo &&
git add foo &&
@@ -90,6 +91,13 @@ test_expect_success '--create-reflog does not create reflog on failure' '
test_must_fail git reflog exists refs/tags/mytag
'
+test_expect_success 'option core.logAllRefUpdates=always creates reflog' '
+ test_when_finished "git tag -d tag_with_reflog" &&
+ test_config core.logAllRefUpdates always &&
+ git tag tag_with_reflog &&
+ git reflog exists refs/tags/tag_with_reflog
+'
+
test_expect_success 'listing all tags if one exists should succeed' '
git tag -l &&
git tag
@@ -871,6 +879,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
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
index 07079a41c4..d62ccbb98e 100755
--- a/t/t7030-verify-tag.sh
+++ b/t/t7030-verify-tag.sh
@@ -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
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index b77cce8e40..c09ce0d4c1 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -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 &&
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 725bbed1f8..347857fa7c 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -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 &&
diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh
index 1c47780e2b..e2bbb449b6 100755
--- a/t/t7412-submodule-absorbgitdirs.sh
+++ b/t/t7412-submodule-absorbgitdirs.sh
@@ -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 &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 5c3db656df..458608cc1e 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -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
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 99d4123461..25241f4096 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,8 +23,20 @@ prompt_given ()
test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
}
+test_expect_success 'basic usage requires no repo' '
+ test_expect_code 129 git difftool -h >output &&
+ grep ^usage: output &&
+ # create a ceiling directory to prevent Git from finding a repo
+ mkdir -p not/repo &&
+ test_when_finished rm -r not &&
+ test_expect_code 129 \
+ env GIT_CEILING_DIRECTORIES="$(pwd)/not" \
+ git -C not/repo difftool -h >output &&
+ grep ^usage: output
+'
+
# 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 +48,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 +61,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 +88,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 +136,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 +153,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 +167,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 +175,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 +185,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 +203,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 +212,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 +222,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 +231,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 +241,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 +250,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 +260,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 +273,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 +287,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
+ grep file output >grep-output &&
+ test_line_count = 1 grep-output
'
-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 +337,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 +349,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 +358,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 +367,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 +375,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 +394,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
"
@@ -416,9 +428,12 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory' '
git difftool --dir-diff $symlinks --extcmd ls branch >output &&
# "sub" must only exist in "right"
# "file" and "file2" must be listed in both "left" and "right"
- test "1" = $(grep sub output | wc -l) &&
- test "2" = $(grep file"$" output | wc -l) &&
- test "2" = $(grep file2 output | wc -l)
+ grep sub output > sub-output &&
+ test_line_count = 1 sub-output &&
+ grep file"$" output >file-output &&
+ test_line_count = 2 file-output &&
+ grep file2 output >file2-output &&
+ test_line_count = 2 file2-output
)
'
@@ -428,9 +443,11 @@ run_dir_diff_test 'difftool --dir-diff v1 from subdirectory' '
git difftool --dir-diff $symlinks --extcmd ls v1 >output &&
# "sub" and "file" exist in both v1 and HEAD.
# "file2" is unchanged.
- test "2" = $(grep sub output | wc -l) &&
- test "2" = $(grep file output | wc -l) &&
- test "0" = $(grep file2 output | wc -l)
+ grep sub output >sub-output &&
+ test_line_count = 2 sub-output &&
+ grep file output >file-output &&
+ test_line_count = 2 file-output &&
+ ! grep file2 output
)
'
@@ -440,8 +457,9 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory w/ pathspec' '
git difftool --dir-diff $symlinks --extcmd ls branch -- .>output &&
# "sub" only exists in "right"
# "file" and "file2" must not be listed
- test "1" = $(grep sub output | wc -l) &&
- test "0" = $(grep file output | wc -l)
+ grep sub output >sub-output &&
+ test_line_count = 1 sub-output &&
+ ! grep file output
)
'
@@ -451,8 +469,9 @@ run_dir_diff_test 'difftool --dir-diff v1 from subdirectory w/ pathspec' '
git difftool --dir-diff $symlinks --extcmd ls v1 -- .>output &&
# "sub" exists in v1 and HEAD
# "file" is filtered out by the pathspec
- test "2" = $(grep sub output | wc -l) &&
- test "0" = $(grep file output | wc -l)
+ grep sub output >sub-output &&
+ test_line_count = 2 sub-output &&
+ ! grep file output
)
'
@@ -508,7 +527,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 +564,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 +577,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 +590,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 +604,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 &&
diff --git a/t/test-lib.sh b/t/test-lib.sh
index cde7fc7fcf..86d77c16dd 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -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 d1dcd18cd7..243d1fdbbc 100644
--- 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;
diff --git a/transport.c b/transport.c
index c86ba2eb89..d72e089484 100644
--- a/transport.c
+++ b/transport.c
@@ -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);
diff --git a/transport.h b/transport.h
index 9820f10b8e..e597b31b38 100644
--- a/transport.h
+++ b/transport.h
@@ -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);
diff --git a/unpack-trees.c b/unpack-trees.c
index 129d49ff45..3a8ee19fe8 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -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 17f52c1b5c..ad6d2910fb 100644
--- 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, ...)
diff --git a/worktree.c b/worktree.c
index 53b4771c04..d633761575 100644
--- a/worktree.c
+++ b/worktree.c
@@ -145,7 +145,7 @@ done:
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++) {
diff --git a/wt-status.c b/wt-status.c
index a715e71906..d47012048f 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -628,7 +628,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
d->index_status = DIFF_STATUS_ADDED;
/* Leave {mode,oid}_head zero for adds. */
d->mode_index = ce->ce_mode;
- hashcpy(d->oid_index.hash, ce->oid.hash);
+ oidcpy(&d->oid_index, &ce->oid);
}
}
}
@@ -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 {
@@ -2096,7 +2102,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
if (strcmp(ce->name, it->string) || !stage)
break;
stages[stage - 1].mode = ce->ce_mode;
- hashcpy(stages[stage - 1].oid.hash, ce->oid.hash);
+ oidcpy(&stages[stage - 1].oid, &ce->oid);
sum |= (1 << (stage - 1));
}
if (sum != d->stagemask)