Merge branch 'js/no-more-multimail'
Remove multimail from contrib/
* js/no-more-multimail:
multimail: stop shipping a copy
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 2aae4c9..f5605b7 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -139,6 +139,7 @@
ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA) $(ASCIIDOC_CONF) \
-amanversion=$(GIT_VERSION) \
-amanmanual='Git Manual' -amansource='Git'
+ASCIIDOC_DEPS = asciidoc.conf GIT-ASCIIDOCFLAGS
TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML)
TXT_TO_XML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_DOCBOOK)
MANPAGE_XSL = manpage-normal.xsl
@@ -193,6 +194,7 @@
ASCIIDOC_EXTRA += -acompat-mode -atabsize=8
ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions
ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
+ASCIIDOC_DEPS = asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
DBLATEX_COMMON =
XMLTO_EXTRA += --skip-validation
XMLTO_EXTRA += -x manpage.xsl
@@ -294,9 +296,7 @@
cmd-list.made $(cmds_txt)
doc.dep : $(docdep_prereqs) $(DOC_DEP_TXT) build-docdep.perl
- $(QUIET_GEN)$(RM) $@+ $@ && \
- $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
- mv $@+ $@
+ $(QUIET_GEN)$(PERL_PATH) ./build-docdep.perl >$@ $(QUIET_STDERR)
ifneq ($(MAKECMDGOALS),clean)
-include doc.dep
@@ -316,8 +316,7 @@
$(cmds_txt): cmd-list.made
cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
- $(QUIET_GEN)$(RM) $@ && \
- $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(cmds_txt) $(QUIET_STDERR) && \
+ $(QUIET_GEN)$(PERL_PATH) ./cmd-list.perl ../command-list.txt $(cmds_txt) $(QUIET_STDERR) && \
date >$@
mergetools_txt = mergetools-diff.txt mergetools-merge.txt
@@ -325,7 +324,7 @@
$(mergetools_txt): mergetools-list.made
mergetools-list.made: ../git-mergetool--lib.sh $(wildcard ../mergetools/*)
- $(QUIET_GEN)$(RM) $@ && \
+ $(QUIET_GEN) \
$(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \
. ../git-mergetool--lib.sh && \
show_tool_names can_diff "* " || :' >mergetools-diff.txt && \
@@ -354,32 +353,23 @@
$(RM) manpage-base-url.xsl
$(RM) GIT-ASCIIDOCFLAGS
-$(MAN_HTML): %.html : %.txt asciidoc.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_HTML) -d manpage -o $@+ $< && \
- mv $@+ $@
+$(MAN_HTML): %.html : %.txt $(ASCIIDOC_DEPS)
+ $(QUIET_ASCIIDOC)$(TXT_TO_HTML) -d manpage -o $@ $<
-$(OBSOLETE_HTML): %.html : %.txto asciidoc.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_HTML) -o $@+ $< && \
- mv $@+ $@
+$(OBSOLETE_HTML): %.html : %.txto $(ASCIIDOC_DEPS)
+ $(QUIET_ASCIIDOC)$(TXT_TO_HTML) -o $@ $<
manpage-base-url.xsl: manpage-base-url.xsl.in
$(QUIET_GEN)sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
%.1 %.5 %.7 : %.xml manpage-base-url.xsl $(wildcard manpage*.xsl)
- $(QUIET_XMLTO)$(RM) $@ && \
- $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
+ $(QUIET_XMLTO)$(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
-%.xml : %.txt asciidoc.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_XML) -d manpage -o $@+ $< && \
- mv $@+ $@
+%.xml : %.txt $(ASCIIDOC_DEPS)
+ $(QUIET_ASCIIDOC)$(TXT_TO_XML) -d manpage -o $@ $<
user-manual.xml: user-manual.txt user-manual.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_XML) -d book -o $@+ $< && \
- mv $@+ $@
+ $(QUIET_ASCIIDOC)$(TXT_TO_XML) -d book -o $@ $<
technical/api-index.txt: technical/api-index-skel.txt \
technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
@@ -400,46 +390,35 @@
XSLTOPTS += --param generate.consistent.ids 1
user-manual.html: user-manual.xml $(XSLT)
- $(QUIET_XSLTPROC)$(RM) $@+ $@ && \
- xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \
- mv $@+ $@
+ $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
git.info: user-manual.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
user-manual.texi: user-manual.xml
- $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
- $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
- $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
- rm $@++ && \
- mv $@+ $@
+ $(QUIET_DB2TEXI)$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@+ && \
+ $(PERL_PATH) fix-texi.perl <$@+ >$@ && \
+ $(RM) $@+
user-manual.pdf: user-manual.xml
- $(QUIET_DBLATEX)$(RM) $@+ $@ && \
- $(DBLATEX) -o $@+ $(DBLATEX_COMMON) $< && \
- mv $@+ $@
+ $(QUIET_DBLATEX)$(DBLATEX) -o $@ $(DBLATEX_COMMON) $<
gitman.texi: $(MAN_XML) cat-texi.perl texi.xsl
- $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+ $(QUIET_DB2TEXI) \
($(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 $@+ $@
+ $(RM) $(xml)+ &&) true) > $@+ && \
+ $(PERL_PATH) cat-texi.perl $@ <$@+ >$@ && \
+ $(RM) $@+
gitman.info: gitman.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
- $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
- $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
- mv $@+ $@
+ $(QUIET_DB2TEXI)$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@
howto-index.txt: howto-index.sh $(HOWTO_TXT)
- $(QUIET_GEN)$(RM) $@+ $@ && \
- '$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(HOWTO_TXT)) >$@+ && \
- mv $@+ $@
+ $(QUIET_GEN)'$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(HOWTO_TXT)) >$@
$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
$(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt
@@ -448,10 +427,9 @@
howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
$(patsubst %.txt,%.html,$(HOWTO_TXT)): %.html : %.txt GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+ $(QUIET_ASCIIDOC) \
sed -e '1,/^$$/d' $< | \
- $(TXT_TO_HTML) - >$@+ && \
- mv $@+ $@
+ $(TXT_TO_HTML) - >$@
install-webdoc : html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
@@ -492,4 +470,7 @@
$(MAKE) -C po $@
endif
+# Delete the target file on error
+.DELETE_ON_ERROR:
+
.PHONY: FORCE
diff --git a/Documentation/RelNotes/2.33.0.txt b/Documentation/RelNotes/2.33.0.txt
new file mode 100644
index 0000000..57443c7
--- /dev/null
+++ b/Documentation/RelNotes/2.33.0.txt
@@ -0,0 +1,78 @@
+Git 2.33 Release Notes
+======================
+
+Backward compatibility notes
+----------------------------
+
+ * The "-m" option in "git log -m" that does not specify which format,
+ if any, of diff is desired did not have any visible effect; it now
+ implies some form of diff (by default "--patch") is produced.
+
+ You can disable the diff output with "git log -m --no-patch", but
+ then there probably isn't much point in passing "-m" in the first
+ place ;-).
+
+
+Updates since Git 2.32
+----------------------
+
+UI, Workflows & Features
+
+ * "git send-email" learned the "--sendmail-cmd" command line option
+ and the "sendemail.sendmailCmd" configuration variable, which is a
+ more sensible approach than the current way of repurposing the
+ "smtp-server" that is meant to name the server to instead name the
+ command to talk to the server.
+
+ * The "-m" option in "git log -m" that does not specify which format,
+ if any, of diff is desired did not have any visible effect; it now
+ implies some form of diff (by default "--patch") is produced.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The code to handle the "--format" option in "for-each-ref" and
+ friends made too many string comparisons on %(atom)s used in the
+ format string, which has been corrected by converting them into
+ enum when the format string is parsed.
+
+ * Use the hashfile API in the codepath that writes the index file to
+ reduce code duplication.
+
+ * Repeated rename detections in a sequence of mergy operations have
+ been optimize out.
+
+
+Fixes since v2.32
+-----------------
+
+ * We historically rejected a very short string as an author name
+ while accepting a patch e-mail, which has been loosened.
+ (merge 72ee47ceeb ef/mailinfo-short-name later to maint).
+
+ * The parallel checkout codepath did not initialize object ID field
+ used to talk to the worker processes in a futureproof way.
+
+ * Rewrite code that triggers undefined behaviour warning.
+ (merge aafa5df0df jn/size-t-casted-to-off-t-fix later to maint).
+
+ * The description of "fast-forward" in the glossary has been updated.
+ (merge e22f2daed0 ry/clarify-fast-forward-in-glossary later to maint).
+
+ * Recent "git clone" left a temporary directory behind when the
+ transport layer returned an failure.
+ (merge 6aacb7d861 jk/clone-clean-upon-transport-error later to maint).
+
+ * "git fetch" over protocol v2 left its side of the socket open after
+ it finished speaking, which unnecessarily wasted the resource on
+ the other side.
+ (merge ae1a7eefff jk/fetch-pack-v2-half-close-early later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge bfe35a6165 ah/doc-describe later to maint).
+ (merge f302c1e4aa jc/clarify-revision-range later to maint).
+ (merge 3127ff90ea tl/fix-packfile-uri-doc later to maint).
+ (merge a84216c684 jk/doc-color-pager later to maint).
+ (merge 4e0a64a713 ab/trace2-squelch-gcc-warning later to maint).
+ (merge 225f7fa847 ps/rev-list-object-type-filter later to maint).
+ (merge 5317dfeaed dd/honor-users-tar-in-tests later to maint).
diff --git a/Documentation/config/color.txt b/Documentation/config/color.txt
index d5daacb..e05d520 100644
--- a/Documentation/config/color.txt
+++ b/Documentation/config/color.txt
@@ -127,8 +127,9 @@
interactive commands.
color.pager::
- A boolean to enable/disable colored output when the pager is in
- use (default is true).
+ A boolean to specify whether `auto` color modes should colorize
+ output going to the pager. Defaults to true; set this to false
+ if your pager does not understand ANSI color codes.
color.push::
A boolean to enable/disable color in push errors. May be set to
diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt
index cb2ed58..6b66c83 100644
--- a/Documentation/config/merge.txt
+++ b/Documentation/config/merge.txt
@@ -14,7 +14,7 @@
branches at the remote named by `branch.<current branch>.remote`
are consulted, and then they are mapped via `remote.<remote>.fetch`
to their corresponding remote-tracking branches, and the tips of
- these tracking branches are merged.
+ these tracking branches are merged. Defaults to true.
merge.ff::
By default, Git does not create an extra merge commit when merging
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 530d115..32e6dee 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -49,10 +49,9 @@
--diff-merges=m:::
-m:::
This option makes diff output for merge commits to be shown in
- the default format. `-m` will produce the output only if `-p`
- is given as well. The default format could be changed using
+ the default format. The default format could be changed using
`log.diffMerges` configuration parameter, which default value
- is `separate`.
+ is `separate`. `-m` implies `-p`.
+
--diff-merges=first-parent:::
--diff-merges=1:::
@@ -62,7 +61,8 @@
--diff-merges=separate:::
This makes merge commits show the full diff with respect to
each of the parents. Separate log entry and diff is generated
- for each parent.
+ for each parent. This is the format that `-m` produced
+ historically.
+
--diff-merges=combined:::
--diff-merges=c:::
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index a88f6ae..c6a79c2 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -63,9 +63,10 @@
Automatically implies --tags.
--abbrev=<n>::
- Instead of using the default 7 hexadecimal digits as the
- abbreviated object name, use <n> digits, or as many digits
- as needed to form a unique object name. An <n> of 0
+ Instead of using the default number of hexadecimal digits (which
+ will vary according to the number of objects in the repository with
+ a default of 7) of the abbreviated object name, use <n> digits, or
+ as many digits as needed to form a unique object name. An <n> of 0
will suppress long format, only showing the closest tag.
--candidates=<n>::
@@ -139,8 +140,11 @@
The number of additional commits is the number
of commits which would be displayed by "git log v1.0.4..parent".
-The hash suffix is "-g" + unambiguous abbreviation for the tip commit
-of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`).
+The hash suffix is "-g" + an unambigous abbreviation for the tip commit
+of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`). The
+length of the abbreviation scales as the repository grows, using the
+approximate number of objects in the repository and a bit of math
+around the birthday paradox, and defaults to a minimum of 7.
The "g" prefix stands for "git" and is used to allow describing the version of
a software depending on the SCM the software is managed with. This is useful
in an environment where people may use different SCMs.
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 93708ae..3db4eab 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -167,6 +167,14 @@
`sendemail.envelopeSender` configuration variable; if that is
unspecified, choosing the envelope sender is left to your MTA.
+--sendmail-cmd=<command>::
+ Specify a command to run to send the email. The command should
+ be sendmail-like; specifically, it must support the `-i` option.
+ The command will be executed in the shell if necessary. Default
+ is the value of `sendemail.sendmailcmd`. If unspecified, and if
+ --smtp-server is also unspecified, git-send-email will search
+ for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH.
+
--smtp-encryption=<encryption>::
Specify the encryption to use, either 'ssl' or 'tls'. Any other
value reverts to plain SMTP. Default is the value of
@@ -211,13 +219,16 @@
--smtp-server=<host>::
If set, specifies the outgoing SMTP server to use (e.g.
- `smtp.example.com` or a raw IP address). Alternatively it can
- specify a full pathname of a sendmail-like program instead;
- the program must support the `-i` option. Default value can
- be specified by the `sendemail.smtpServer` configuration
- option; the built-in default is to search for `sendmail` in
- `/usr/sbin`, `/usr/lib` and $PATH if such program is
- available, falling back to `localhost` otherwise.
+ `smtp.example.com` or a raw IP address). If unspecified, and if
+ `--sendmail-cmd` is also unspecified, the default is to search
+ for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH if such a
+ program is available, falling back to `localhost` otherwise.
++
+For backward compatibility, this option can also specify a full pathname
+of a sendmail-like program instead; the program must support the `-i`
+option. This method does not support passing arguments or using plain
+command names. For those use cases, consider using `--sendmail-cmd`
+instead.
--smtp-server-port=<port>::
Specifies a port different from the default port (SMTP
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 67c7a50..c077971 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -146,8 +146,8 @@
<<def_revision,revision>> and you are "merging" another
<<def_branch,branch>>'s changes that happen to be a descendant of what
you have. In such a case, you do not make a new <<def_merge,merge>>
- <<def_commit,commit>> but instead just update to his
- revision. This will happen frequently on a
+ <<def_commit,commit>> but instead just update your branch to point at the same
+ revision as the branch you are merging. This will happen frequently on a
<<def_remote_tracking_branch,remote-tracking branch>> of a remote
<<def_repository,repository>>.
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index d9169c0..f5f17b6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -260,6 +260,9 @@
A commit's reachable set is the commit itself and the commits in
its ancestry chain.
+There are several notations to specify a set of connected commits
+(called a "revision range"), illustrated below.
+
Commit Exclusions
~~~~~~~~~~~~~~~~~
@@ -294,6 +297,26 @@
I forked from them?" Note that '..' would mean 'HEAD..HEAD' which is an
empty range that is both reachable and unreachable from HEAD.
+Commands that are specifically designed to take two distinct ranges
+(e.g. "git range-diff R1 R2" to compare two ranges) do exist, but
+they are exceptions. Unless otherwise noted, all "git" commands
+that operate on a set of commits work on a single revision range.
+In other words, writing two "two-dot range notation" next to each
+other, e.g.
+
+ $ git log A..B C..D
+
+does *not* specify two revision ranges for most commands. Instead
+it will name a single connected set of commits, i.e. those that are
+reachable from either B or D but are reachable from neither A or C.
+In a linear history like this:
+
+ ---A---B---o---o---C---D
+
+because A and B are reachable from C, the revision range specified
+by these two dotted ranges is a single commit D.
+
+
Other <rev>{caret} Parent Shorthand Notations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Three other shorthands exist, particularly useful for merge commits,
diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt
index 3f52f98..037a91c 100644
--- a/Documentation/technical/api-trace2.txt
+++ b/Documentation/technical/api-trace2.txt
@@ -396,14 +396,14 @@
}
------------
-`"discard"`::
+`"too_many_files"`::
This event is written to the git-trace2-discard sentinel file if there
are too many files in the target trace directory (see the
trace2.maxFiles config option).
+
------------
{
- "event":"discard",
+ "event":"too_many_files",
...
}
------------
diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.txt
index f7eabc6..1eb525f 100644
--- a/Documentation/technical/packfile-uri.txt
+++ b/Documentation/technical/packfile-uri.txt
@@ -35,13 +35,14 @@
at least so that we can test the client.
This is the implementation: a feature, marked experimental, that allows the
-server to be configured by one or more `uploadpack.blobPackfileUri=<sha1>
-<uri>` entries. Whenever the list of objects to be sent is assembled, all such
-blobs are excluded, replaced with URIs. As noted in "Future work" below, the
-server can evolve in the future to support excluding other objects (or other
-implementations of servers could be made that support excluding other objects)
-without needing a protocol change, so clients should not expect that packfiles
-downloaded in this way only contain single blobs.
+server to be configured by one or more `uploadpack.blobPackfileUri=
+<object-hash> <pack-hash> <uri>` entries. Whenever the list of objects to be
+sent is assembled, all such blobs are excluded, replaced with URIs. As noted
+in "Future work" below, the server can evolve in the future to support
+excluding other objects (or other implementations of servers could be made
+that support excluding other objects) without needing a protocol change, so
+clients should not expect that packfiles downloaded in this way only contain
+single blobs.
Client design
-------------
diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt
index 0780d30..a0dd7c6 100644
--- a/Documentation/technical/partial-clone.txt
+++ b/Documentation/technical/partial-clone.txt
@@ -242,8 +242,7 @@
repository and can satisfy all such requests.
- Repack essentially treats promisor and non-promisor packfiles as 2
- distinct partitions and does not mix them. Repack currently only works
- on non-promisor packfiles and loose objects.
+ distinct partitions and does not mix them.
- Dynamic object fetching invokes fetch-pack once *for each item*
because most algorithms stumble upon a missing object and need to have
@@ -273,9 +272,6 @@
The user might want to work in a triangular work flow with multiple
promisor remotes that each have an incomplete view of the repository.
-- Allow repack to work on promisor packfiles (while keeping them distinct
- from non-promisor packfiles).
-
- Allow non-pathname-based filters to make use of packfile bitmaps (when
present). This was just an omission during the initial implementation.
diff --git a/Documentation/technical/remembering-renames.txt b/Documentation/technical/remembering-renames.txt
new file mode 100644
index 0000000..2fd5cc8
--- /dev/null
+++ b/Documentation/technical/remembering-renames.txt
@@ -0,0 +1,671 @@
+Rebases and cherry-picks involve a sequence of merges whose results are
+recorded as new single-parent commits. The first parent side of those
+merges represent the "upstream" side, and often include a far larger set of
+changes than the second parent side. Traditionally, the renames on the
+first-parent side of that sequence of merges were repeatedly re-detected
+for every merge. This file explains why it is safe and effective during
+rebases and cherry-picks to remember renames on the upstream side of
+history as an optimization, assuming all merges are automatic and clean
+(i.e. no conflicts and not interrupted for user input or editing).
+
+Outline:
+
+ 0. Assumptions
+
+ 1. How rebasing and cherry-picking work
+
+ 2. Why the renames on MERGE_SIDE1 in any given pick are *always* a
+ superset of the renames on MERGE_SIDE1 for the next pick.
+
+ 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also
+ a rename on MERGE_SIDE1 for the next pick
+
+ 4. A detailed description of the the counter-examples to #3.
+
+ 5. Why the special cases in #4 are still fully reasonable to use to pair
+ up files for three-way content merging in the merge machinery, and why
+ they do not affect the correctness of the merge.
+
+ 6. Interaction with skipping of "irrelevant" renames
+
+ 7. Additional items that need to be cached
+
+ 8. How directory rename detection interacts with the above and why this
+ optimization is still safe even if merge.directoryRenames is set to
+ "true".
+
+
+=== 0. Assumptions ===
+
+There are two assumptions that will hold throughout this document:
+
+ * The upstream side where commits are transplanted to is treated as the
+ first parent side when rebase/cherry-pick call the merge machinery
+
+ * All merges are fully automatic
+
+and a third that will hold in sections 2-5 for simplicity, that I'll later
+address in section 8:
+
+ * No directory renames occur
+
+
+Let me explain more about each assumption and why I include it:
+
+
+The first assumption is merely for the purposes of making this document
+clearer; the optimization implementation does not actually depend upon it.
+However, the assumption does hold in all cases because it reflects the way
+that both rebase and cherry-pick were implemented; and the implementation
+of cherry-pick and rebase are not readily changeable for backwards
+compatibility reasons (see for example the discussion of the --ours and
+--theirs flag in the documentation of `git checkout`, particularly the
+comments about how they behave with rebase). The optimization avoids
+checking first-parent-ness, though. It checks the conditions that make the
+optimization valid instead, so it would still continue working if someone
+changed the parent ordering that cherry-pick and rebase use. But making
+this assumption does make this document much clearer and prevents me from
+having to repeat every example twice.
+
+If the second assumption is violated, then the optimization simply is
+turned off and thus isn't relevant to consider. The second assumption can
+also be stated as "there is no interruption for a user to resolve conflicts
+or to just further edit or tweak files". While real rebases and
+cherry-picks are often interrupted (either because it's an interactive
+rebase where the user requested to stop and edit, or because there were
+conflicts that the user needs to resolve), the cache of renames is not
+stored on disk, and thus is thrown away as soon as the rebase or cherry
+pick stops for the user to resolve the operation.
+
+The third assumption makes sections 2-5 simpler, and allows people to
+understand the basics of why this optimization is safe and effective, and
+then I can go back and address the specifics in section 8. It is probably
+also worth noting that if directory renames do occur, then the default of
+merge.directoryRenames being set to "conflict" means that the operation
+will stop for users to resolve the conflicts and the cache will be thrown
+away, and thus that there won't be an optimization to apply. So, the only
+reason we need to address directory renames specifically, is that some
+users will have set merge.directoryRenames to "true" to allow the merges to
+continue to proceed automatically. The optimization is still safe with
+this config setting, but we have to discuss a few more cases to show why;
+this discussion is deferred until section 8.
+
+
+=== 1. How rebasing and cherry-picking work ===
+
+Consider the following setup (from the git-rebase manpage):
+
+ A---B---C topic
+ /
+ D---E---F---G main
+
+After rebasing or cherry-picking topic onto main, this will appear as:
+
+ A'--B'--C' topic
+ /
+ D---E---F---G main
+
+The way the commits A', B', and C' are created is through a series of
+merges, where rebase or cherry-pick sequentially uses each of the three
+A-B-C commits in a special merge operation. Let's label the three commits
+in the merge operation as MERGE_BASE, MERGE_SIDE1, and MERGE_SIDE2. For
+this picture, the three commits for each of the three merges would be:
+
+To create A':
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+
+To create B':
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+
+To create C':
+ MERGE_BASE: B
+ MERGE_SIDE1: B'
+ MERGE_SIDE2: C
+
+Sometimes, folks are surprised that these three-way merges are done. It
+can be useful in understanding these three-way merges to view them in a
+slightly different light. For example, in creating C', you can view it as
+either:
+
+ * Apply the changes between B & C to B'
+ * Apply the changes between B & B' to C
+
+Conceptually the two statements above are the same as a three-way merge of
+B, B', and C, at least the parts before you decide to record a commit.
+
+
+=== 2. Why the renames on MERGE_SIDE1 in any given pick are always a ===
+=== superset of the renames on MERGE_SIDE1 for the next pick. ===
+
+The merge machinery uses the filenames it is fed from MERGE_BASE,
+MERGE_SIDE1, and MERGE_SIDE2. It will only move content to a different
+filename under one of three conditions:
+
+ * To make both pieces of a conflict available to a user during conflict
+ resolution (examples: directory/file conflict, add/add type conflict
+ such as symlink vs. regular file)
+
+ * When MERGE_SIDE1 renames the file.
+
+ * When MERGE_SIDE2 renames the file.
+
+First, let's remember what commits are involved in the first and second
+picks of the cherry-pick or rebase sequence:
+
+To create A':
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+
+To create B':
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+
+So, in particular, we need to show that the renames between E and G are a
+superset of those between A and A'.
+
+A' is created by the first merge. A' will only have renames for one of the
+three reasons listed above. The first case, a conflict, results in a
+situation where the cache is dropped and thus this optimization doesn't
+take effect, so we need not consider that case. The third case, a rename
+on MERGE_SIDE2 (i.e. from G to A), will show up in A' but it also shows up
+in A -- therefore when diffing A and A' that path does not show up as a
+rename. The only remaining way for renames to show up in A' is for the
+rename to come from MERGE_SIDE1. Therefore, all renames between A and A'
+are a subset of those between E and G. Equivalently, all renames between E
+and G are a superset of those between A and A'.
+
+
+=== 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ ===
+=== always also a rename on MERGE_SIDE1 for the next pick. ===
+
+Let's again look at the first two picks:
+
+To create A':
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+
+To create B':
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+
+Now let's look at any given rename from MERGE_SIDE1 of the first pick, i.e.
+any given rename from E to G. Let's use the filenames 'oldfile' and
+'newfile' for demonstration purposes. That first pick will function as
+follows; when the rename is detected, the merge machinery will do a
+three-way content merge of the following:
+ E:oldfile
+ G:newfile
+ A:oldfile
+and produce a new result:
+ A':newfile
+
+Note above that I've assumed that E->A did not rename oldfile. If that
+side did rename, then we most likely have a rename/rename(1to2) conflict
+that will cause the rebase or cherry-pick operation to halt and drop the
+in-memory cache of renames and thus doesn't need to be considered further.
+In the special case that E->A does rename the file but also renames it to
+newfile, then there is no conflict from the renaming and the merge can
+succeed. In this special case, the rename is not valid to cache because
+the second merge will find A:newfile in the MERGE_BASE (see also the new
+testcases in t6429 with "rename same file identically" in their
+description). So a rename/rename(1to1) needs to be specially handled by
+pruning renames from the cache and decrementing the dir_rename_counts in
+the current and leading directories associated with those renames. Or,
+since these are really rare, one could just take the easy way out and
+disable the remembering renames optimization when a rename/rename(1to1)
+happens.
+
+The previous paragraph handled the cases for E->A renaming oldfile, let's
+continue assuming that oldfile is not renamed in A.
+
+As per the diagram for creating B', MERGE_SIDE1 involves the changes from A
+to A'. So, we are curious whether A:oldfile and A':newfile will be viewed
+as renames. Note that:
+
+ * There will be no A':oldfile (because there could not have been a
+ G:oldfile as we do not do break detection in the merge machinery and
+ G:newfile was detected as a rename, and by the construction of the
+ rename above that merged cleanly, the merge machinery will ensure there
+ is no 'oldfile' in the result).
+
+ * There will be no A:newfile (if there had been, we would have had a
+ rename/add conflict).
+
+ * Clearly A:oldfile and A':newfile are "related" (A':newfile came from a
+ clean three-way content merge involving A:oldfile).
+
+We can also expound on the third point above, by noting that three-way
+content merges can also be viewed as applying the differences between the
+base and one side to the other side. Thus we can view A':newfile as
+having been created by taking the changes between E:oldfile and G:newfile
+(which were detected as being related, i.e. <50% changed) to A:oldfile.
+
+Thus A:oldfile and A':newfile are just as related as E:oldfile and
+G:newfile are -- they have exactly identical differences. Since the latter
+were detected as renames, A:oldfile and A':newfile should also be
+detectable as renames almost always.
+
+
+=== 4. A detailed description of the counter-examples to #3. ===
+
+We already noted in section 3 that rename/rename(1to1) (i.e. both sides
+renaming a file the same way) was one counter-example. The more
+interesting bit, though, is why did we need to use the "almost" qualifier
+when stating that A:oldfile and A':newfile are "almost" always detectable
+as renames?
+
+Let's repeat an earlier point that section 3 made:
+
+ A':newfile was created by applying the changes between E:oldfile and
+ G:newfile to A:oldfile. The changes between E:oldfile and G:newfile were
+ <50% of the size of E:oldfile.
+
+If those changes that were <50% of the size of E:oldfile are also <50% of
+the size of A:oldfile, then A:oldfile and A':newfile will be detectable as
+renames. However, if there is a dramatic size reduction between E:oldfile
+and A:oldfile (but the changes between E:oldfile, G:newfile, and A:oldfile
+still somehow merge cleanly), then traditional rename detection would not
+detect A:oldfile and A':newfile as renames.
+
+Here's an example where that can happen:
+ * E:oldfile had 20 lines
+ * G:newfile added 10 new lines at the beginning of the file
+ * A:oldfile kept the first 3 lines of the file, and deleted all the rest
+then
+ => A':newfile would have 13 lines, 3 of which matches those in A:oldfile.
+E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and
+A':newfile would not be.
+
+
+=== 5. Why the special cases in #4 are still fully reasonable to use to ===
+=== pair up files for three-way content merging in the merge machinery, ===
+=== and why they do not affect the correctness of the merge. ===
+
+In the rename/rename(1to1) case, A:newfile and A':newfile are not renames
+since they use the *same* filename. However, files with the same filename
+are obviously fine to pair up for three-way content merging (the merge
+machinery has never employed break detection). The interesting
+counter-example case is thus not the rename/rename(1to1) case, but the case
+where A did not rename oldfile. That was the case that we spent most of
+the time discussing in sections 3 and 4. The remainder of this section
+will be devoted to that case as well.
+
+So, even if A:oldfile and A':newfile aren't detectable as renames, why is
+it still reasonable to pair them up for three-way content merging in the
+merge machinery? There are multiple reasons:
+
+ * As noted in sections 3 and 4, the diff between A:oldfile and A':newfile
+ is *exactly* the same as the diff between E:oldfile and G:newfile. The
+ latter pair were detected as renames, so it seems unlikely to surprise
+ users for us to treat A:oldfile and A':newfile as renames.
+
+ * In fact, "oldfile" and "newfile" were at one point detected as renames
+ due to how they were constructed in the E..G chain. And we used that
+ information once already in this rebase/cherry-pick. I think users
+ would be unlikely to be surprised at us continuing to treat the files
+ as renames and would quickly understand why we had done so.
+
+ * Marking or declaring files as renames is *not* the end goal for merges.
+ Merges use renames to determine which files make sense to be paired up
+ for three-way content merges.
+
+ * A:oldfile and A':newfile were _already_ paired up in a three-way
+ content merge; that is how A':newfile was created. In fact, that
+ three-way content merge was clean. So using them again in a later
+ three-way content merge seems very reasonable.
+
+However, the above is focusing on the common scenarios. Let's try to look
+at all possible unusual scenarios and compare without the optimization to
+with the optimization. Consider the following theoretical cases; we will
+then dive into each to determine which of them are possible,
+and if so, what they mean:
+
+ 1. Without the optimization, the second merge results in a conflict.
+ With the optimization, the second merge also results in a conflict.
+ Questions: Are the conflicts confusingly different? Better in one case?
+
+ 2. Without the optimization, the second merge results in NO conflict.
+ With the optimization, the second merge also results in NO conflict.
+ Questions: Are the merges the same?
+
+ 3. Without the optimization, the second merge results in a conflict.
+ With the optimization, the second merge results in NO conflict.
+ Questions: Possible? Bug, bugfix, or something else?
+
+ 4. Without the optimization, the second merge results in NO conflict.
+ With the optimization, the second merge results in a conflict.
+ Questions: Possible? Bug, bugfix, or something else?
+
+I'll consider all four cases, but out of order.
+
+The fourth case is impossible. For the code without the remembering
+renames optimization to not get a conflict, B:oldfile would need to exactly
+match A:oldfile -- if it doesn't, there would be a modify/delete conflict.
+If A:oldfile matches B:oldfile exactly, then a three-way content merge
+between A:oldfile, A':newfile, and B:oldfile would have no conflict and
+just give us the version of newfile from A' as the result.
+
+From the same logic as the above paragraph, the second case would indeed
+result in identical merges. When A:oldfile exactly matches B:oldfile, an
+undetected rename would say, "Oh, I see one side didn't modify 'oldfile'
+and the other side deleted it. I'll delete it. And I see you have this
+brand new file named 'newfile' in A', so I'll keep it." That gives the
+same results as three-way content merging A:oldfile, A':newfile, and
+B:oldfile -- a removal of oldfile with the version of newfile from A'
+showing up in the result.
+
+The third case is interesting. It means that A:oldfile and A':newfile were
+not just similar enough, but that the changes between them did not conflict
+with the changes between A:oldfile and B:oldfile. This would validate our
+hunch that the files were similar enough to be used in a three-way content
+merge, and thus seems entirely correct for us to have used them that way.
+(Sidenote: One particular example here may be enlightening. Let's say that
+B was an immediate revert of A. B clearly would have been a clean revert
+of A, since A was B's immediate parent. One would assume that if you can
+pick a commit, you should also be able to cherry-pick its immediate revert.
+However, this is one of those funny corner cases; without this
+optimization, we just successfully picked a commit cleanly, but we are
+unable to cherry-pick its immediate revert due to the size differences
+between E:oldfile and A:oldfile.)
+
+That leaves only the first case to consider -- when we get conflicts both
+with or without the optimization. Without the optimization, we'll have a
+modify/delete conflict, where both A':newfile and B:oldfile are left in the
+tree for the user to deal with and no hints about the potential similarity
+between the two. With the optimization, we'll have a three-way content
+merged A:oldfile, A':newfile, and B:oldfile with conflict markers
+suggesting we thought the files were related but giving the user the chance
+to resolve. As noted above, I don't think users will find us treating
+'oldfile' and 'newfile' as related as a surprise since they were between E
+and G. In any event, though, this case shouldn't be concerning since we
+hit a conflict in both cases, told the user what we know, and asked them to
+resolve it.
+
+So, in summary, case 4 is impossible, case 2 yields the same behavior, and
+cases 1 and 3 seem to provide as good or better behavior with the
+optimization than without.
+
+
+=== 6. Interaction with skipping of "irrelevant" renames ===
+
+Previous optimizations involved skipping rename detection for paths
+considered to be "irrelevant". See for example the following commits:
+
+ * 32a56dfb99 ("merge-ort: precompute subset of sources for which we
+ need rename detection", 2021-03-11)
+ * 2fd9eda462 ("merge-ort: precompute whether directory rename
+ detection is needed", 2021-03-11)
+ * 9bd342137e ("diffcore-rename: determine which relevant_sources are
+ no longer relevant", 2021-03-13)
+
+Relevance is always determined by what the _other_ side of history has
+done, in terms of modifing a file that our side renamed, or adding a
+file to a directory which our side renamed. This means that a path
+that is "irrelevant" when picking the first commit of a series in a
+rebase or cherry-pick, may suddenly become "relevant" when picking the
+next commit.
+
+The upshot of this is that we can only cache rename detection results
+for relevant paths, and need to re-check relevance in subsequent
+commits. If those subsequent commits have additional paths that are
+relevant for rename detection, then we will need to redo rename
+detection -- though we can limit it to the paths for which we have not
+already detected renames.
+
+
+=== 7. Additional items that need to be cached ===
+
+It turns out we have to cache more than just renames; we also cache:
+
+ A) non-renames (i.e. unpaired deletes)
+ B) counts of renames within directories
+ C) sources that were marked as RELEVANT_LOCATION, but which were
+ downgraded to RELEVANT_NO_MORE
+ D) the toplevel trees involved in the merge
+
+These are all stored in struct rename_info, and respectively appear in
+ * cached_pairs (along side actual renames, just with a value of NULL)
+ * dir_rename_counts
+ * cached_irrelevant
+ * merge_trees
+
+The reason for (A) comes from the irrelevant renames skipping
+optimization discussed in section 6. The fact that irrelevant renames
+are skipped means we only get a subset of the potential renames
+detected and subsequent commits may need to run rename detection on
+the upstream side on a subset of the remaining renames (to get the
+renames that are relevant for that later commit). Since unpaired
+deletes are involved in rename detection too, we don't want to
+repeatedly check that those paths remain unpaired on the upstream side
+with every commit we are transplanting.
+
+The reason for (B) is that diffcore_rename_extended() is what
+generates the counts of renames by directory which is needed in
+directory rename detection, and if we don't run
+diffcore_rename_extended() again then we need to have the output from
+it, including dir_rename_counts, from the previous run.
+
+The reason for (C) is that merge-ort's tree traversal will again think
+those paths are relevant (marking them as RELEVANT_LOCATION), but the
+fact that they were downgraded to RELEVANT_NO_MORE means that
+dir_rename_counts already has the information we need for directory
+rename detection. (A path which becomes RELEVANT_CONTENT in a
+subsequent commit will be removed from cached_irrelevant.)
+
+The reason for (D) is that is how we determine whether the remember
+renames optimization can be used. In particular, remembering that our
+sequence of merges looks like:
+
+ Merge 1:
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+ => Creates A'
+
+ Merge 2:
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+ => Creates B'
+
+It is the fact that the trees A and A' appear both in Merge 1 and in
+Merge 2, with A as a parent of A' that allows this optimization. So
+we store the trees to compare with what we are asked to merge next
+time.
+
+
+=== 8. How directory rename detection interacts with the above and ===
+=== why this optimization is still safe even if ===
+=== merge.directoryRenames is set to "true". ===
+
+As noted in the assumptions section:
+
+ """
+ ...if directory renames do occur, then the default of
+ merge.directoryRenames being set to "conflict" means that the operation
+ will stop for users to resolve the conflicts and the cache will be
+ thrown away, and thus that there won't be an optimization to apply.
+ So, the only reason we need to address directory renames specifically,
+ is that some users will have set merge.directoryRenames to "true" to
+ allow the merges to continue to proceed automatically.
+ """
+
+Let's remember that we need to look at how any given pick affects the next
+one. So let's again use the first two picks from the diagram in section
+one:
+
+ First pick does this three-way merge:
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+ => creates A'
+
+ Second pick does this three-way merge:
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+ => creates B'
+
+Now, directory rename detection exists so that if one side of history
+renames a directory, and the other side adds a new file to the old
+directory, then the merge (with merge.directoryRenames=true) can move the
+file into the new directory. There are two qualitatively different ways to
+add a new file to an old directory: create a new file, or rename a file
+into that directory. Also, directory renames can be done on either side of
+history, so there are four cases to consider:
+
+ * MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir
+ * MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir
+ * MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir
+ * MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir
+
+One last note before we consider these four cases: There are some
+important properties about how we implement this optimization with
+respect to directory rename detection that we need to bear in mind
+while considering all of these cases:
+
+ * rename caching occurs *after* applying directory renames
+
+ * a rename created by directory rename detection is recorded for the side
+ of history that did the directory rename.
+
+ * dir_rename_counts, the nested map of
+ {oldname => {newname => count}},
+ is cached between runs as well. This basically means that directory
+ rename detection is also cached, though only on the side of history
+ that we cache renames for (MERGE_SIDE1 as far as this document is
+ concerned; see the assumptions section). Two interesting sub-notes
+ about these counts:
+
+ * If we need to perform rename-detection again on the given side (e.g.
+ some paths are relevant for rename detection that weren't before),
+ then we clear dir_rename_counts and recompute it, making use of
+ cached_pairs. The reason it is important to do this is optimizations
+ around RELEVANT_LOCATION exist to prevent us from computing
+ unnecessary renames for directory rename detection and from computing
+ dir_rename_counts for irrelevant directories; but those same renames
+ or directories may become necessary for subsequent merges. The
+ easiest way to "fix up" dir_rename_counts in such cases is to just
+ recompute it.
+
+ * If we prune rename/rename(1to1) entries from the cache, then we also
+ need to update dir_rename_counts to decrement the counts for the
+ involved directory and any relevant parent directories (to undo what
+ update_dir_rename_counts() in diffcore-rename.c incremented when the
+ rename was initially found). If we instead just disable the
+ remembering renames optimization when the exceedingly rare
+ rename/rename(1to1) cases occur, then dir_rename_counts will get
+ re-computed the next time rename detection occurs, as noted above.
+
+ * the side with multiple commits to pick, is the side of history that we
+ do NOT cache renames for. Thus, there are no additional commits to
+ change the number of renames in a directory, except for those done by
+ directory rename detection (which always pad the majority).
+
+ * the "renames" we cache are modified slightly by any directory rename,
+ as noted below.
+
+Now, with those notes out of the way, let's go through the four cases
+in order:
+
+Case 1: MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir
+
+ This case looks like this:
+
+ MERGE_BASE: E, Has olddir/
+ MERGE_SIDE1: G, Renames olddir/ -> newdir/
+ MERGE_SIDE2: A, Adds olddir/newfile
+ => creates A', With newdir/newfile
+
+ MERGE_BASE: A, Has olddir/newfile
+ MERGE_SIDE1: A', Has newdir/newfile
+ MERGE_SIDE2: B, Modifies olddir/newfile
+ => expected B', with threeway-merged newdir/newfile from above
+
+ In this case, with the optimization, note that after the first commit:
+ * MERGE_SIDE1 remembers olddir/ -> newdir/
+ * MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
+ Given the cached rename noted above, the second merge can proceed as
+ expected without needing to perform rename detection from A -> A'.
+
+Case 2: MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir
+
+ This case looks like this:
+ MERGE_BASE: E oldfile, olddir/
+ MERGE_SIDE1: G oldfile, olddir/ -> newdir/
+ MERGE_SIDE2: A oldfile -> olddir/newfile
+ => creates A', With newdir/newfile representing original oldfile
+
+ MERGE_BASE: A olddir/newfile
+ MERGE_SIDE1: A' newdir/newfile
+ MERGE_SIDE2: B modify olddir/newfile
+ => expected B', with threeway-merged newdir/newfile from above
+
+ In this case, with the optimization, note that after the first commit:
+ * MERGE_SIDE1 remembers olddir/ -> newdir/
+ * MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
+ (NOT oldfile -> newdir/newfile; compare to case with
+ (p->status == 'R' && new_path) in possibly_cache_new_pair())
+
+ Given the cached rename noted above, the second merge can proceed as
+ expected without needing to perform rename detection from A -> A'.
+
+Case 3: MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir
+
+ This case looks like this:
+
+ MERGE_BASE: E, Has olddir/
+ MERGE_SIDE1: G, Adds olddir/newfile
+ MERGE_SIDE2: A, Renames olddir/ -> newdir/
+ => creates A', With newdir/newfile
+
+ MERGE_BASE: A, Has newdir/, but no notion of newdir/newfile
+ MERGE_SIDE1: A', Has newdir/newfile
+ MERGE_SIDE2: B, Has newdir/, but no notion of newdir/newfile
+ => expected B', with newdir/newfile from A'
+
+ In this case, with the optimization, note that after the first commit there
+ were no renames on MERGE_SIDE1, and any renames on MERGE_SIDE2 are tossed.
+ But the second merge didn't need any renames so this is fine.
+
+Case 4: MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir
+
+ This case looks like this:
+
+ MERGE_BASE: E, Has olddir/
+ MERGE_SIDE1: G, Renames oldfile -> olddir/newfile
+ MERGE_SIDE2: A, Renames olddir/ -> newdir/
+ => creates A', With newdir/newfile representing original oldfile
+
+ MERGE_BASE: A, Has oldfile
+ MERGE_SIDE1: A', Has newdir/newfile
+ MERGE_SIDE2: B, Modifies oldfile
+ => expected B', with threeway-merged newdir/newfile from above
+
+ In this case, with the optimization, note that after the first commit:
+ * MERGE_SIDE1 remembers oldfile -> newdir/newfile
+ (NOT oldfile -> olddir/newfile; compare to case of second
+ block under p->status == 'R' in possibly_cache_new_pair())
+ * MERGE_SIDE2 renames are tossed because only MERGE_SIDE1 is remembered
+
+ Given the cached rename noted above, the second merge can proceed as
+ expected without needing to perform rename detection from A -> A'.
+
+Finally, I'll just note here that interactions with the
+skip-irrelevant-renames optimization means we sometimes don't detect
+renames for any files within a directory that was renamed, in which
+case we will not have been able to detect any rename for the directory
+itself. In such a case, we do not know whether the directory was
+renamed; we want to be careful to avoid cacheing some kind of "this
+directory was not renamed" statement. If we did, then a subsequent
+commit being rebased could add a file to the old directory, and the
+user would expect it to end up in the correct directory -- something
+our erroneous "this directory was not renamed" cache would preclude.
diff --git a/Makefile b/Makefile
index c3565fc..502e0c9 100644
--- a/Makefile
+++ b/Makefile
@@ -398,6 +398,10 @@
# with a different indexfile format version. If it isn't set the index
# file format used is index-v[23].
#
+# Define GIT_TEST_UTF8_LOCALE to preferred utf-8 locale for testing.
+# If it isn't set, fallback to $LC_ALL, $LANG or use the first utf-8
+# locale returned by "locale -a".
+#
# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime.
#
# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC.
@@ -2802,6 +2806,9 @@
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@+
endif
+ifdef GIT_TEST_UTF8_LOCALE
+ @echo GIT_TEST_UTF8_LOCALE=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_UTF8_LOCALE)))'\' >>$@+
+endif
@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@+
ifdef GIT_PERF_REPEAT_COUNT
@echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@+
diff --git a/RelNotes b/RelNotes
index aece21e..f071367 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.32.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.33.0.txt
\ No newline at end of file
diff --git a/builtin/clone.c b/builtin/clone.c
index eeb74c0..66fe666 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1320,9 +1320,8 @@
}
if (!is_local && !complete_refs_before_fetch) {
- err = transport_fetch_refs(transport, mapped_refs);
- if (err)
- goto cleanup;
+ if (transport_fetch_refs(transport, mapped_refs))
+ die(_("remote transport reported error"));
}
remote_head = find_ref_by_name(refs, "HEAD");
@@ -1380,9 +1379,8 @@
if (is_local)
clone_local(path, git_dir);
else if (refs && complete_refs_before_fetch) {
- err = transport_fetch_refs(transport, mapped_refs);
- if (err)
- goto cleanup;
+ if (transport_fetch_refs(transport, mapped_refs))
+ die(_("remote transport reported error"));
}
update_remote_refs(refs, mapped_refs, remote_head_points_at,
@@ -1410,7 +1408,6 @@
junk_mode = JUNK_LEAVE_REPO;
err = checkout(submodule_progress);
-cleanup:
free(remote_name);
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 176fe7f..cf09559 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -2,6 +2,7 @@
#include "cache.h"
#include "config.h"
#include "diff.h"
+#include "diff-merges.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
@@ -27,6 +28,12 @@
rev.abbrev = 0;
prefix = precompose_argv_prefix(argc, argv, prefix);
+ /*
+ * We need no diff for merges options, and we need to avoid conflict
+ * with our own meaning of "-m".
+ */
+ diff_merges_suppress_options_parsing();
+
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -35,6 +42,8 @@
option |= DIFF_INDEX_CACHED;
else if (!strcmp(arg, "--merge-base"))
option |= DIFF_INDEX_MERGE_BASE;
+ else if (!strcmp(arg, "-m"))
+ rev.match_missing = 1;
else
usage(diff_cache_usage);
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index dfde96a..9191620 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1126,7 +1126,7 @@
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
if (want_status == FETCH_HEAD_MERGE)
- warning(_("reject %s because shallow roots are not allowed to be updated"),
+ warning(_("rejected %s because shallow roots are not allowed to be updated"),
rm->peer_ref ? rm->peer_ref->name : rm->name);
continue;
}
diff --git a/builtin/merge.c b/builtin/merge.c
index eddb8ae..a8a843b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -56,8 +56,8 @@
static const char * const builtin_merge_usage[] = {
N_("git merge [<options>] [<commit>...]"),
- N_("git merge --abort"),
- N_("git merge --continue"),
+ "git merge --abort",
+ "git merge --continue",
NULL
};
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 7af8dab..22c4e1a 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -435,11 +435,11 @@
/* get the usage up to the first line with a -- on it */
for (;;) {
if (strbuf_getline(&sb, stdin) == EOF)
- die("premature end of input");
+ die(_("premature end of input"));
ALLOC_GROW(usage, unb + 1, usz);
if (!strcmp("--", sb.buf)) {
if (unb < 1)
- die("no usage string given before the `--' separator");
+ die(_("no usage string given before the `--' separator"));
usage[unb] = NULL;
break;
}
@@ -545,7 +545,7 @@
if (quiet)
exit(1);
else
- die("Needed a single revision");
+ die(_("Needed a single revision"));
}
static const char builtin_rev_parse_usage[] =
@@ -709,10 +709,10 @@
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir = argv[++i];
if (!gitdir)
- die("--resolve-git-dir requires an argument");
+ die(_("--resolve-git-dir requires an argument"));
gitdir = resolve_gitdir(gitdir);
if (!gitdir)
- die("not a gitdir '%s'", argv[i]);
+ die(_("not a gitdir '%s'"), argv[i]);
puts(gitdir);
continue;
}
@@ -736,7 +736,7 @@
if (!seen_end_of_options && *arg == '-') {
if (!strcmp(arg, "--git-path")) {
if (!argv[i + 1])
- die("--git-path requires an argument");
+ die(_("--git-path requires an argument"));
strbuf_reset(&buf);
print_path(git_path("%s", argv[i + 1]), prefix,
format,
@@ -746,7 +746,7 @@
}
if (!strcmp(arg,"-n")) {
if (++i >= argc)
- die("-n requires an argument");
+ die(_("-n requires an argument"));
if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
show(arg);
show(argv[i]);
@@ -760,26 +760,26 @@
}
if (opt_with_value(arg, "--path-format", &arg)) {
if (!arg)
- die("--path-format requires an argument");
+ die(_("--path-format requires an argument"));
if (!strcmp(arg, "absolute")) {
format = FORMAT_CANONICAL;
} else if (!strcmp(arg, "relative")) {
format = FORMAT_RELATIVE;
} else {
- die("unknown argument to --path-format: %s", arg);
+ die(_("unknown argument to --path-format: %s"), arg);
}
continue;
}
if (!strcmp(arg, "--default")) {
def = argv[++i];
if (!def)
- die("--default requires an argument");
+ die(_("--default requires an argument"));
continue;
}
if (!strcmp(arg, "--prefix")) {
prefix = argv[++i];
if (!prefix)
- die("--prefix requires an argument");
+ die(_("--prefix requires an argument"));
startup_info->prefix = prefix;
output_prefix = 1;
continue;
@@ -848,7 +848,7 @@
else if (!strcmp(arg, "loose"))
abbrev_ref_strict = 0;
else
- die("unknown mode for --abbrev-ref: %s",
+ die(_("unknown mode for --abbrev-ref: %s"),
arg);
}
continue;
@@ -892,7 +892,7 @@
if (work_tree)
print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
else
- die("this operation must be run in a work tree");
+ die(_("this operation must be run in a work tree"));
continue;
}
if (!strcmp(arg, "--show-superproject-working-tree")) {
@@ -1020,7 +1020,7 @@
if (strcmp(val, "storage") &&
strcmp(val, "input") &&
strcmp(val, "output"))
- die("unknown mode for --show-object-format: %s",
+ die(_("unknown mode for --show-object-format: %s"),
arg);
puts(the_hash_algo->name);
continue;
@@ -1058,7 +1058,7 @@
if (verify)
die_no_single_rev(quiet);
if (has_dashdash)
- die("bad revision '%s'", arg);
+ die(_("bad revision '%s'"), arg);
as_is = 1;
if (!show_file(arg, output_prefix))
continue;
diff --git a/builtin/stash.c b/builtin/stash.c
index 01066d7..9c72e4b 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -26,7 +26,7 @@
N_("git stash drop [-q|--quiet] [<stash>]"),
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
N_("git stash branch <branchname> [<stash>]"),
- N_("git stash clear"),
+ "git stash clear",
N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
" [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
@@ -67,7 +67,7 @@
};
static const char * const git_stash_clear_usage[] = {
- N_("git stash clear"),
+ "git stash clear",
NULL
};
@@ -761,7 +761,7 @@
cp.git_cmd = 1;
strvec_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
- "--first-parent", "-m", NULL);
+ "--first-parent", NULL);
strvec_pushv(&cp.args, argv);
strvec_push(&cp.args, ref_stash);
strvec_push(&cp.args, "--");
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index d55f626..ae6174a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1300,7 +1300,7 @@
OPT_BOOL(0, "cached", &cached,
N_("use the commit stored in the index instead of the submodule HEAD")),
OPT_BOOL(0, "files", &files,
- N_("to compare the commit in the index with that in the submodule HEAD")),
+ N_("compare the commit in the index with that in the submodule HEAD")),
OPT_BOOL(0, "for-status", &for_status,
N_("skip submodules with 'ignore_config' value set to 'all'")),
OPT_INTEGER('n', "summary-limit", &summary_limit,
diff --git a/chunk-format.c b/chunk-format.c
index da191e5..1c3dca6 100644
--- a/chunk-format.c
+++ b/chunk-format.c
@@ -58,9 +58,11 @@
int write_chunkfile(struct chunkfile *cf, void *data)
{
- int i;
+ int i, result = 0;
uint64_t cur_offset = hashfile_total(cf->f);
+ trace2_region_enter("chunkfile", "write", the_repository);
+
/* Add the table of contents to the current offset */
cur_offset += (cf->chunks_nr + 1) * CHUNK_TOC_ENTRY_SIZE;
@@ -77,10 +79,10 @@
for (i = 0; i < cf->chunks_nr; i++) {
off_t start_offset = hashfile_total(cf->f);
- int result = cf->chunks[i].write_fn(cf->f, data);
+ result = cf->chunks[i].write_fn(cf->f, data);
if (result)
- return result;
+ goto cleanup;
if (hashfile_total(cf->f) - start_offset != cf->chunks[i].size)
BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead",
@@ -88,7 +90,9 @@
hashfile_total(cf->f) - start_offset);
}
- return 0;
+cleanup:
+ trace2_region_leave("chunkfile", "write", the_repository);
+ return result;
}
int read_table_of_contents(struct chunkfile *cf,
diff --git a/ci/lib.sh b/ci/lib.sh
index d848c03..476c3f3 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -229,6 +229,7 @@
CC=gcc
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=/usr/bin/python3 USE_LIBPCRE2=Yes"
MAKEFLAGS="$MAKEFLAGS NO_REGEX=Yes ICONV_OMITS_BOM=Yes"
+ MAKEFLAGS="$MAKEFLAGS GIT_TEST_UTF8_LOCALE=C.UTF-8"
;;
esac
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index b50c5d0..4073d67 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1729,6 +1729,7 @@
--indent-heuristic --no-indent-heuristic
--textconv --no-textconv
--patch --no-patch
+ --anchored=
"
__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index b06782b..7f767b5 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -5,8 +5,12 @@
# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
#
-if test -z "$GIT_EXEC_PATH" || test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup"
+if test -z "$GIT_EXEC_PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup" || {
+ test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" &&
+ test ! "$GIT_EXEC_PATH" -ef "${PATH%%:*}" 2>/dev/null
+}
then
+ basename=${0##*[/\\]}
echo >&2 'It looks like either your git installation or your'
echo >&2 'git-subtree installation is broken.'
echo >&2
@@ -14,10 +18,10 @@
echo >&2 " - If \`git --exec-path\` does not print the correct path to"
echo >&2 " your git install directory, then set the GIT_EXEC_PATH"
echo >&2 " environment variable to the correct directory."
- echo >&2 " - Make sure that your \`${0##*/}\` file is either in your"
+ echo >&2 " - Make sure that your \`$basename\` file is either in your"
echo >&2 " PATH or in your git exec path (\`$(git --exec-path)\`)."
- echo >&2 " - You should run git-subtree as \`git ${0##*/git-}\`,"
- echo >&2 " not as \`${0##*/}\`." >&2
+ echo >&2 " - You should run git-subtree as \`git ${basename#git-}\`,"
+ echo >&2 " not as \`$basename\`." >&2
exit 126
fi
diff --git a/csum-file.c b/csum-file.c
index 7510950..3487d28 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -11,35 +11,33 @@
#include "progress.h"
#include "csum-file.h"
+static void verify_buffer_or_die(struct hashfile *f,
+ const void *buf,
+ unsigned int count)
+{
+ ssize_t ret = read_in_full(f->check_fd, f->check_buffer, count);
+
+ if (ret < 0)
+ die_errno("%s: sha1 file read error", f->name);
+ if (ret != count)
+ die("%s: sha1 file truncated", f->name);
+ if (memcmp(buf, f->check_buffer, count))
+ die("sha1 file '%s' validation error", f->name);
+}
+
static void flush(struct hashfile *f, const void *buf, unsigned int count)
{
- if (0 <= f->check_fd && count) {
- unsigned char check_buffer[8192];
- ssize_t ret = read_in_full(f->check_fd, check_buffer, count);
+ if (0 <= f->check_fd && count)
+ verify_buffer_or_die(f, buf, count);
- if (ret < 0)
- die_errno("%s: sha1 file read error", f->name);
- if (ret != count)
- die("%s: sha1 file truncated", f->name);
- if (memcmp(buf, check_buffer, count))
- die("sha1 file '%s' validation error", f->name);
- }
-
- for (;;) {
- int ret = xwrite(f->fd, buf, count);
- if (ret > 0) {
- f->total += ret;
- display_throughput(f->tp, f->total);
- buf = (char *) buf + ret;
- count -= ret;
- if (count)
- continue;
- return;
- }
- if (!ret)
+ if (write_in_full(f->fd, buf, count) < 0) {
+ if (errno == ENOSPC)
die("sha1 file '%s' write error. Out of diskspace", f->name);
die_errno("sha1 file '%s' write error", f->name);
}
+
+ f->total += count;
+ display_throughput(f->tp, f->total);
}
void hashflush(struct hashfile *f)
@@ -53,6 +51,13 @@
}
}
+static void free_hashfile(struct hashfile *f)
+{
+ free(f->buffer);
+ free(f->check_buffer);
+ free(f);
+}
+
int finalize_hashfile(struct hashfile *f, unsigned char *result, unsigned int flags)
{
int fd;
@@ -82,20 +87,20 @@
if (close(f->check_fd))
die_errno("%s: sha1 file error on close", f->name);
}
- free(f);
+ free_hashfile(f);
return fd;
}
void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
{
while (count) {
- unsigned left = sizeof(f->buffer) - f->offset;
+ unsigned left = f->buffer_len - f->offset;
unsigned nr = count > left ? left : count;
if (f->do_crc)
f->crc32 = crc32(f->crc32, buf, nr);
- if (nr == sizeof(f->buffer)) {
+ if (nr == f->buffer_len) {
/*
* Flush a full batch worth of data directly
* from the input, skipping the memcpy() to
@@ -121,11 +126,6 @@
}
}
-struct hashfile *hashfd(int fd, const char *name)
-{
- return hashfd_throughput(fd, name, NULL);
-}
-
struct hashfile *hashfd_check(const char *name)
{
int sink, check;
@@ -139,10 +139,14 @@
die_errno("unable to open '%s'", name);
f = hashfd(sink, name);
f->check_fd = check;
+ f->check_buffer = xmalloc(f->buffer_len);
+
return f;
}
-struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp)
+static struct hashfile *hashfd_internal(int fd, const char *name,
+ struct progress *tp,
+ size_t buffer_len)
{
struct hashfile *f = xmalloc(sizeof(*f));
f->fd = fd;
@@ -153,9 +157,35 @@
f->name = name;
f->do_crc = 0;
the_hash_algo->init_fn(&f->ctx);
+
+ f->buffer_len = buffer_len;
+ f->buffer = xmalloc(buffer_len);
+ f->check_buffer = NULL;
+
return f;
}
+struct hashfile *hashfd(int fd, const char *name)
+{
+ /*
+ * Since we are not going to use a progress meter to
+ * measure the rate of data passing through this hashfile,
+ * use a larger buffer size to reduce fsync() calls.
+ */
+ return hashfd_internal(fd, name, NULL, 128 * 1024);
+}
+
+struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp)
+{
+ /*
+ * Since we are expecting to report progress of the
+ * write into this hashfile, use a smaller buffer
+ * size so the progress indicators arrive at a more
+ * frequent rate.
+ */
+ return hashfd_internal(fd, name, tp, 8 * 1024);
+}
+
void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
{
hashflush(f);
diff --git a/csum-file.h b/csum-file.h
index e54d53d..3044bd1 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -16,7 +16,9 @@
const char *name;
int do_crc;
uint32_t crc32;
- unsigned char buffer[8192];
+ size_t buffer_len;
+ unsigned char *buffer;
+ unsigned char *check_buffer;
};
/* Checkpoint */
diff --git a/diff-merges.c b/diff-merges.c
index f3a9dae..0dfcaa1 100644
--- a/diff-merges.c
+++ b/diff-merges.c
@@ -6,6 +6,7 @@
static void set_separate(struct rev_info *revs);
static diff_merges_setup_func_t set_to_default = set_separate;
+static int suppress_parsing;
static void suppress(struct rev_info *revs)
{
@@ -14,7 +15,7 @@
revs->combine_merges = 0;
revs->dense_combined_merges = 0;
revs->combined_all_paths = 0;
- revs->combined_imply_patch = 0;
+ revs->merges_imply_patch = 0;
revs->merges_need_diff = 0;
}
@@ -30,17 +31,6 @@
revs->first_parent_merges = 1;
}
-static void set_m(struct rev_info *revs)
-{
- /*
- * To "diff-index", "-m" means "match missing", and to the "log"
- * family of commands, it means "show default diff for merges". Set
- * both fields appropriately.
- */
- set_to_default(revs);
- revs->match_missing = 1;
-}
-
static void set_combined(struct rev_info *revs)
{
suppress(revs);
@@ -101,20 +91,29 @@
return 0;
}
+void diff_merges_suppress_options_parsing(void)
+{
+ suppress_parsing = 1;
+}
+
int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
{
int argcount = 1;
const char *optarg;
const char *arg = argv[0];
+ if (suppress_parsing)
+ return 0;
+
if (!strcmp(arg, "-m")) {
- set_m(revs);
+ set_to_default(revs);
+ revs->merges_imply_patch = 1;
} else if (!strcmp(arg, "-c")) {
set_combined(revs);
- revs->combined_imply_patch = 1;
+ revs->merges_imply_patch = 1;
} else if (!strcmp(arg, "--cc")) {
set_dense_combined(revs);
- revs->combined_imply_patch = 1;
+ revs->merges_imply_patch = 1;
} else if (!strcmp(arg, "--no-diff-merges")) {
suppress(revs);
} else if (!strcmp(arg, "--combined-all-paths")) {
@@ -155,15 +154,18 @@
void diff_merges_setup_revs(struct rev_info *revs)
{
+ if (suppress_parsing)
+ return;
+
if (revs->combine_merges == 0)
revs->dense_combined_merges = 0;
if (revs->separate_merges == 0)
revs->first_parent_merges = 0;
if (revs->combined_all_paths && !revs->combine_merges)
die("--combined-all-paths makes no sense without -c or --cc");
- if (revs->combined_imply_patch)
+ if (revs->merges_imply_patch)
revs->diff = 1;
- if (revs->combined_imply_patch || revs->merges_need_diff) {
+ if (revs->merges_imply_patch || revs->merges_need_diff) {
if (!revs->diffopt.output_format)
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
}
diff --git a/diff-merges.h b/diff-merges.h
index 09d9a6c..b5d57f6 100644
--- a/diff-merges.h
+++ b/diff-merges.h
@@ -11,6 +11,8 @@
int diff_merges_config(const char *value);
+void diff_merges_suppress_options_parsing(void);
+
int diff_merges_parse_opts(struct rev_info *revs, const char **argv);
void diff_merges_suppress(struct rev_info *revs);
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 963ca58..3375e24 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -568,7 +568,8 @@
static void initialize_dir_rename_info(struct dir_rename_info *info,
struct strintmap *relevant_sources,
struct strintmap *dirs_removed,
- struct strmap *dir_rename_count)
+ struct strmap *dir_rename_count,
+ struct strmap *cached_pairs)
{
struct hashmap_iter iter;
struct strmap_entry *entry;
@@ -633,6 +634,17 @@
rename_dst[i].p->two->path);
}
+ /* Add cached_pairs to counts */
+ strmap_for_each_entry(cached_pairs, &iter, entry) {
+ const char *old_name = entry->key;
+ const char *new_name = entry->value;
+ if (!new_name)
+ /* known delete; ignore it */
+ continue;
+
+ update_dir_rename_counts(info, dirs_removed, old_name, new_name);
+ }
+
/*
* Now we collapse
* dir_rename_count: old_directory -> {new_directory -> count}
@@ -1247,7 +1259,8 @@
void diffcore_rename_extended(struct diff_options *options,
struct strintmap *relevant_sources,
struct strintmap *dirs_removed,
- struct strmap *dir_rename_count)
+ struct strmap *dir_rename_count,
+ struct strmap *cached_pairs)
{
int detect_rename = options->detect_rename;
int minimum_score = options->rename_score;
@@ -1363,7 +1376,8 @@
/* Preparation for basename-driven matching. */
trace2_region_enter("diff", "dir rename setup", options->repo);
initialize_dir_rename_info(&info, relevant_sources,
- dirs_removed, dir_rename_count);
+ dirs_removed, dir_rename_count,
+ cached_pairs);
trace2_region_leave("diff", "dir rename setup", options->repo);
/* Utilize file basenames to quickly find renames. */
@@ -1560,5 +1574,5 @@
void diffcore_rename(struct diff_options *options)
{
- diffcore_rename_extended(options, NULL, NULL, NULL);
+ diffcore_rename_extended(options, NULL, NULL, NULL, NULL);
}
diff --git a/diffcore.h b/diffcore.h
index f5c6de4..533b30e 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -181,7 +181,8 @@
void diffcore_rename_extended(struct diff_options *options,
struct strintmap *relevant_sources,
struct strintmap *dirs_removed,
- struct strmap *dir_rename_count);
+ struct strmap *dir_rename_count,
+ struct strmap *cached_pairs);
void diffcore_merge_broken(void);
void diffcore_pickaxe(struct diff_options *);
void diffcore_order(const char *orderfile);
diff --git a/fetch-pack.c b/fetch-pack.c
index c135635..b0c7be7 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1645,6 +1645,15 @@
if (process_section_header(&reader, "packfile-uris", 1))
receive_packfile_uris(&reader, &packfile_uris);
process_section_header(&reader, "packfile", 0);
+
+ /*
+ * this is the final request we'll make of the server;
+ * do a half-duplex shutdown to indicate that they can
+ * hang up as soon as the pack is sent.
+ */
+ close(fd[1]);
+ fd[1] = -1;
+
if (get_pack(args, fd, pack_lockfiles,
packfile_uris.nr ? &index_pack_args : NULL,
sought, nr_sought, &fsck_options.gitmodules_found))
diff --git a/git-compat-util.h b/git-compat-util.h
index a508dbe..fb6e9af 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -986,11 +986,9 @@
static inline size_t xsize_t(off_t len)
{
- size_t size = (size_t) len;
-
- if (len != (off_t) size)
+ if (len < 0 || (uintmax_t) len > SIZE_MAX)
die("Cannot handle files this big");
- return size;
+ return (size_t) len;
}
__attribute__((format (printf, 3, 4)))
diff --git a/git-send-email.perl b/git-send-email.perl
index 25be2eb..7ba0b34 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -70,6 +70,7 @@
Sending:
--envelope-sender <str> * Email envelope sender.
+ --sendmail-cmd <str> * Command to run to send email.
--smtp-server <str:int> * Outgoing SMTP server to use. The port
is optional. Default 'localhost'.
--smtp-server-option <str> * Outgoing SMTP server option to use.
@@ -262,6 +263,7 @@
my (@suppress_cc);
my ($auto_8bit_encoding);
my ($compose_encoding);
+my ($sendmail_cmd);
# Variables with corresponding config settings & hardcoded defaults
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
my $thread = 1;
@@ -309,6 +311,7 @@
"assume8bitencoding" => \$auto_8bit_encoding,
"composeencoding" => \$compose_encoding,
"transferencoding" => \$target_xfer_encoding,
+ "sendmailcmd" => \$sendmail_cmd,
);
my %config_path_settings = (
@@ -442,6 +445,7 @@
"no-bcc" => \$no_bcc,
"chain-reply-to!" => \$chain_reply_to,
"no-chain-reply-to" => sub {$chain_reply_to = 0},
+ "sendmail-cmd=s" => \$sendmail_cmd,
"smtp-server=s" => \$smtp_server,
"smtp-server-option=s" => \@smtp_server_options,
"smtp-server-port=s" => \$smtp_server_port,
@@ -1013,16 +1017,19 @@
$reply_to = sanitize_address($reply_to);
}
-if (!defined $smtp_server) {
+if (!defined $sendmail_cmd && !defined $smtp_server) {
my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );
push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};
foreach (@sendmail_paths) {
if (-x $_) {
- $smtp_server = $_;
+ $sendmail_cmd = $_;
last;
}
}
- $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
+
+ if (!defined $sendmail_cmd) {
+ $smtp_server = 'localhost'; # could be 127.0.0.1, too... *shrug*
+ }
}
if ($compose && $compose > 0) {
@@ -1502,11 +1509,17 @@
if ($dry_run) {
# We don't want to send the email.
- } elsif (file_name_is_absolute($smtp_server)) {
+ } elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
- exec($smtp_server, @sendmail_parameters) or die $!;
+ if (defined $sendmail_cmd) {
+ exec ("sh", "-c", "$sendmail_cmd \"\$@\"", "-", @sendmail_parameters)
+ or die $!;
+ } else {
+ exec ($smtp_server, @sendmail_parameters)
+ or die $!;
+ }
}
print $sm "$header\n$message";
close $sm or die $!;
@@ -1602,14 +1615,21 @@
printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject);
} else {
print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n"));
- if (!file_name_is_absolute($smtp_server)) {
+ if (!defined $sendmail_cmd && !file_name_is_absolute($smtp_server)) {
print "Server: $smtp_server\n";
print "MAIL FROM:<$raw_from>\n";
foreach my $entry (@recipients) {
print "RCPT TO:<$entry>\n";
}
} else {
- print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
+ my $sm;
+ if (defined $sendmail_cmd) {
+ $sm = $sendmail_cmd;
+ } else {
+ $sm = $smtp_server;
+ }
+
+ print "Sendmail: $sm ".join(' ',@sendmail_parameters)."\n";
}
print $header, "\n";
if ($smtp) {
diff --git a/hash.h b/hash.h
index 2986f99..9c6df4d 100644
--- a/hash.h
+++ b/hash.h
@@ -263,6 +263,22 @@
dst->algo = src->algo;
}
+/* Like oidcpy() but zero-pads the unused bytes in dst's hash array. */
+static inline void oidcpy_with_padding(struct object_id *dst,
+ struct object_id *src)
+{
+ size_t hashsz;
+
+ if (!src->algo)
+ hashsz = the_hash_algo->rawsz;
+ else
+ hashsz = hash_algos[src->algo].rawsz;
+
+ memcpy(dst->hash, src->hash, hashsz);
+ memset(dst->hash + hashsz, 0, GIT_MAX_RAWSZ - hashsz);
+ dst->algo = src->algo;
+}
+
static inline struct object_id *oiddup(const struct object_id *src)
{
struct object_id *dst = xmalloc(sizeof(struct object_id));
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
index 96a605c..fd8d59f 100644
--- a/list-objects-filter-options.c
+++ b/list-objects-filter-options.c
@@ -102,7 +102,7 @@
} else if (skip_prefix(arg, "object:type=", &v0)) {
int type = type_from_string_gently(v0, strlen(v0), 1);
if (type < 0) {
- strbuf_addf(errbuf, _("'%s' for 'object:type=<type>' is"
+ strbuf_addf(errbuf, _("'%s' for 'object:type=<type>' is "
"not a valid object type"), v0);
return 1;
}
diff --git a/mailinfo.c b/mailinfo.c
index ccc6beb..184ed8d 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -19,7 +19,7 @@
static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
{
struct strbuf *src = name;
- if (name->len < 3 || 60 < name->len || strpbrk(name->buf, "@<>"))
+ if (!name->len || 60 < name->len || strpbrk(name->buf, "@<>"))
src = email;
else if (name == out)
return;
diff --git a/merge-ort.c b/merge-ort.c
index 4a9ce2a..b954f71 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -53,6 +53,8 @@
MERGE_SIDE2 = 2
};
+static unsigned RESULT_INITIALIZED = 0x1abe11ed; /* unlikely accidental value */
+
struct traversal_callback_data {
unsigned long mask;
unsigned long dirmask;
@@ -141,6 +143,72 @@
char *callback_data_traverse_path;
/*
+ * merge_trees: trees passed to the merge algorithm for the merge
+ *
+ * merge_trees records the trees passed to the merge algorithm. But,
+ * this data also is stored in merge_result->priv. If a sequence of
+ * merges are being done (such as when cherry-picking or rebasing),
+ * the next merge can look at this and re-use information from
+ * previous merges under certain circumstances.
+ *
+ * See also all the cached_* variables.
+ */
+ struct tree *merge_trees[3];
+
+ /*
+ * cached_pairs_valid_side: which side's cached info can be reused
+ *
+ * See the description for merge_trees. For repeated merges, at most
+ * only one side's cached information can be used. Valid values:
+ * MERGE_SIDE2: cached data from side2 can be reused
+ * MERGE_SIDE1: cached data from side1 can be reused
+ * 0: no cached data can be reused
+ */
+ int cached_pairs_valid_side;
+
+ /*
+ * cached_pairs: Caching of renames and deletions.
+ *
+ * These are mappings recording renames and deletions of individual
+ * files (not directories). They are thus a map from an old
+ * filename to either NULL (for deletions) or a new filename (for
+ * renames).
+ */
+ struct strmap cached_pairs[3];
+
+ /*
+ * cached_target_names: just the destinations from cached_pairs
+ *
+ * We sometimes want a fast lookup to determine if a given filename
+ * is one of the destinations in cached_pairs. cached_target_names
+ * is thus duplicative information, but it provides a fast lookup.
+ */
+ struct strset cached_target_names[3];
+
+ /*
+ * cached_irrelevant: Caching of rename_sources that aren't relevant.
+ *
+ * If we try to detect a rename for a source path and succeed, it's
+ * part of a rename. If we try to detect a rename for a source path
+ * and fail, then it's a delete. If we do not try to detect a rename
+ * for a path, then we don't know if it's a rename or a delete. If
+ * merge-ort doesn't think the path is relevant, then we just won't
+ * cache anything for that path. But there's a slight problem in
+ * that merge-ort can think a path is RELEVANT_LOCATION, but due to
+ * commit 9bd342137e ("diffcore-rename: determine which
+ * relevant_sources are no longer relevant", 2021-03-13),
+ * diffcore-rename can downgrade the path to RELEVANT_NO_MORE. To
+ * avoid excessive calls to diffcore_rename_extended() we still need
+ * to cache such paths, though we cannot record them as either
+ * renames or deletes. So we cache them here as a "turned out to be
+ * irrelevant *for this commit*" as they are often also irrelevant
+ * for subsequent commits, though we will have to do some extra
+ * checking to see whether such paths become relevant for rename
+ * detection when cherry-picking/rebasing subsequent commits.
+ */
+ struct strset cached_irrelevant[3];
+
+ /*
* needed_limit: value needed for inexact rename detection to run
*
* If the current rename limit wasn't high enough for inexact
@@ -382,6 +450,8 @@
reinitialize ? strmap_partial_clear : strmap_clear;
void (*strintmap_func)(struct strintmap *) =
reinitialize ? strintmap_partial_clear : strintmap_clear;
+ void (*strset_func)(struct strset *) =
+ reinitialize ? strset_partial_clear : strset_clear;
/*
* We marked opti->paths with strdup_strings = 0, so that we
@@ -417,15 +487,21 @@
/* Free memory used by various renames maps */
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
strintmap_func(&renames->dirs_removed[i]);
-
- partial_clear_dir_rename_count(&renames->dir_rename_count[i]);
- if (!reinitialize)
- strmap_clear(&renames->dir_rename_count[i], 1);
-
strmap_func(&renames->dir_renames[i], 0);
-
strintmap_func(&renames->relevant_sources[i]);
+ if (!reinitialize)
+ assert(renames->cached_pairs_valid_side == 0);
+ if (i != renames->cached_pairs_valid_side) {
+ strset_func(&renames->cached_target_names[i]);
+ strmap_func(&renames->cached_pairs[i], 1);
+ strset_func(&renames->cached_irrelevant[i]);
+ partial_clear_dir_rename_count(&renames->dir_rename_count[i]);
+ if (!reinitialize)
+ strmap_clear(&renames->dir_rename_count[i], 1);
+ }
}
+ renames->cached_pairs_valid_side = 0;
+ renames->dir_rename_mask = 0;
if (!reinitialize) {
struct hashmap_iter iter;
@@ -448,8 +524,6 @@
strmap_clear(&opti->output, 0);
}
- renames->dir_rename_mask = 0;
-
/* Clean out callback_data as well. */
FREE_AND_NULL(renames->callback_data);
renames->callback_data_nr = renames->callback_data_alloc = 0;
@@ -690,15 +764,48 @@
struct rename_info *renames = &opt->priv->renames;
int names_idx = is_add ? side : 0;
- if (!is_add) {
+ if (is_add) {
+ if (strset_contains(&renames->cached_target_names[side],
+ pathname))
+ return;
+ } else {
unsigned content_relevant = (match_mask == 0);
unsigned location_relevant = (dir_rename_mask == 0x07);
+ /*
+ * If pathname is found in cached_irrelevant[side] due to
+ * previous pick but for this commit content is relevant,
+ * then we need to remove it from cached_irrelevant.
+ */
+ if (content_relevant)
+ /* strset_remove is no-op if strset doesn't have key */
+ strset_remove(&renames->cached_irrelevant[side],
+ pathname);
+
+ /*
+ * We do not need to re-detect renames for paths that we already
+ * know the pairing, i.e. for cached_pairs (or
+ * cached_irrelevant). However, handle_deferred_entries() needs
+ * to loop over the union of keys from relevant_sources[side] and
+ * cached_pairs[side], so for simplicity we set relevant_sources
+ * for all the cached_pairs too and then strip them back out in
+ * prune_cached_from_relevant() at the beginning of
+ * detect_regular_renames().
+ */
if (content_relevant || location_relevant) {
/* content_relevant trumps location_relevant */
strintmap_set(&renames->relevant_sources[side], pathname,
content_relevant ? RELEVANT_CONTENT : RELEVANT_LOCATION);
}
+
+ /*
+ * Avoid creating pair if we've already cached rename results.
+ * Note that we do this after setting relevant_sources[side]
+ * as noted in the comment above.
+ */
+ if (strmap_contains(&renames->cached_pairs[side], pathname) ||
+ strset_contains(&renames->cached_irrelevant[side], pathname))
+ return;
}
one = alloc_filespec(pathname);
@@ -2037,6 +2144,9 @@
VERIFY_CI(side2);
if (!strcmp(pathnames[1], pathnames[2])) {
+ struct rename_info *ri = &opt->priv->renames;
+ int j;
+
/* Both sides renamed the same way */
assert(side1 == side2);
memcpy(&side1->stages[0], &base->stages[0],
@@ -2046,6 +2156,16 @@
base->merged.is_null = 1;
base->merged.clean = 1;
+ /*
+ * Disable remembering renames optimization;
+ * rename/rename(1to1) is incredibly rare, and
+ * just disabling the optimization is easier
+ * than purging cached_pairs,
+ * cached_target_names, and dir_rename_counts.
+ */
+ for (j = 0; j < 3; j++)
+ ri->merge_trees[j] = NULL;
+
/* We handled both renames, i.e. i+1 handled */
i++;
/* Move to next rename */
@@ -2273,7 +2393,9 @@
static inline int possible_renames(struct rename_info *renames)
{
return possible_side_renames(renames, 1) ||
- possible_side_renames(renames, 2);
+ possible_side_renames(renames, 2) ||
+ !strmap_empty(&renames->cached_pairs[1]) ||
+ !strmap_empty(&renames->cached_pairs[2]);
}
static void resolve_diffpair_statuses(struct diff_queue_struct *q)
@@ -2297,6 +2419,112 @@
}
}
+static void prune_cached_from_relevant(struct rename_info *renames,
+ unsigned side)
+{
+ /* Reason for this function described in add_pair() */
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ /* Remove from relevant_sources all entries in cached_pairs[side] */
+ strmap_for_each_entry(&renames->cached_pairs[side], &iter, entry) {
+ strintmap_remove(&renames->relevant_sources[side],
+ entry->key);
+ }
+ /* Remove from relevant_sources all entries in cached_irrelevant[side] */
+ strset_for_each_entry(&renames->cached_irrelevant[side], &iter, entry) {
+ strintmap_remove(&renames->relevant_sources[side],
+ entry->key);
+ }
+}
+
+static void use_cached_pairs(struct merge_options *opt,
+ struct strmap *cached_pairs,
+ struct diff_queue_struct *pairs)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ /*
+ * Add to side_pairs all entries from renames->cached_pairs[side_index].
+ * (Info in cached_irrelevant[side_index] is not relevant here.)
+ */
+ strmap_for_each_entry(cached_pairs, &iter, entry) {
+ struct diff_filespec *one, *two;
+ const char *old_name = entry->key;
+ const char *new_name = entry->value;
+ if (!new_name)
+ new_name = old_name;
+
+ /* We don't care about oid/mode, only filenames and status */
+ one = alloc_filespec(old_name);
+ two = alloc_filespec(new_name);
+ diff_queue(pairs, one, two);
+ pairs->queue[pairs->nr-1]->status = entry->value ? 'R' : 'D';
+ }
+}
+
+static void cache_new_pair(struct rename_info *renames,
+ int side,
+ char *old_path,
+ char *new_path,
+ int free_old_value)
+{
+ char *old_value;
+ new_path = xstrdup(new_path);
+ old_value = strmap_put(&renames->cached_pairs[side],
+ old_path, new_path);
+ strset_add(&renames->cached_target_names[side], new_path);
+ if (free_old_value)
+ free(old_value);
+ else
+ assert(!old_value);
+}
+
+static void possibly_cache_new_pair(struct rename_info *renames,
+ struct diff_filepair *p,
+ unsigned side,
+ char *new_path)
+{
+ int dir_renamed_side = 0;
+
+ if (new_path) {
+ /*
+ * Directory renames happen on the other side of history from
+ * the side that adds new files to the old directory.
+ */
+ dir_renamed_side = 3 - side;
+ } else {
+ int val = strintmap_get(&renames->relevant_sources[side],
+ p->one->path);
+ if (val == RELEVANT_NO_MORE) {
+ assert(p->status == 'D');
+ strset_add(&renames->cached_irrelevant[side],
+ p->one->path);
+ }
+ if (val <= 0)
+ return;
+ }
+
+ if (p->status == 'D') {
+ /*
+ * If we already had this delete, we'll just set it's value
+ * to NULL again, so no harm.
+ */
+ strmap_put(&renames->cached_pairs[side], p->one->path, NULL);
+ } else if (p->status == 'R') {
+ if (!new_path)
+ new_path = p->two->path;
+ else
+ cache_new_pair(renames, dir_renamed_side,
+ p->two->path, new_path, 0);
+ cache_new_pair(renames, side, p->one->path, new_path, 1);
+ } else if (p->status == 'A' && new_path) {
+ cache_new_pair(renames, dir_renamed_side,
+ p->two->path, new_path, 0);
+ }
+}
+
static int compare_pairs(const void *a_, const void *b_)
{
const struct diff_filepair *a = *((const struct diff_filepair **)a_);
@@ -2312,6 +2540,7 @@
struct diff_options diff_opts;
struct rename_info *renames = &opt->priv->renames;
+ prune_cached_from_relevant(renames, side_index);
if (!possible_side_renames(renames, side_index)) {
/*
* No rename detection needed for this side, but we still need
@@ -2322,6 +2551,7 @@
return;
}
+ partial_clear_dir_rename_count(&renames->dir_rename_count[side_index]);
repo_diff_setup(opt->repo, &diff_opts);
diff_opts.flags.recursive = 1;
diff_opts.flags.rename_empty = 0;
@@ -2339,7 +2569,8 @@
diffcore_rename_extended(&diff_opts,
&renames->relevant_sources[side_index],
&renames->dirs_removed[side_index],
- &renames->dir_rename_count[side_index]);
+ &renames->dir_rename_count[side_index],
+ &renames->cached_pairs[side_index]);
trace2_region_leave("diff", "diffcore_rename", opt->repo);
resolve_diffpair_statuses(&diff_queued_diff);
@@ -2379,6 +2610,7 @@
char *new_path; /* non-NULL only with directory renames */
if (p->status != 'A' && p->status != 'R') {
+ possibly_cache_new_pair(renames, p, side_index, NULL);
diff_free_filepair(p);
continue;
}
@@ -2390,6 +2622,7 @@
&collisions,
&clean);
+ possibly_cache_new_pair(renames, p, side_index, new_path);
if (p->status != 'R' && !new_path) {
diff_free_filepair(p);
continue;
@@ -2445,6 +2678,8 @@
trace2_region_enter("merge", "regular renames", opt->repo);
detect_regular_renames(opt, MERGE_SIDE1);
detect_regular_renames(opt, MERGE_SIDE2);
+ use_cached_pairs(opt, &renames->cached_pairs[1], &renames->pairs[1]);
+ use_cached_pairs(opt, &renames->cached_pairs[2], &renames->pairs[2]);
trace2_region_leave("merge", "regular renames", opt->repo);
trace2_region_enter("merge", "directory renames", opt->repo);
@@ -3635,6 +3870,10 @@
assert(opt->obuf.len == 0);
assert(opt->priv == NULL);
+ if (result->_properly_initialized != 0 &&
+ result->_properly_initialized != RESULT_INITIALIZED)
+ BUG("struct merge_result passed to merge_incore_*recursive() must be zeroed or filled with values from a previous run");
+ assert(!!result->priv == !!result->_properly_initialized);
if (result->priv) {
opt->priv = result->priv;
result->priv = NULL;
@@ -3674,8 +3913,22 @@
NULL, 1);
strmap_init_with_options(&renames->dir_renames[i],
NULL, 0);
+ /*
+ * relevant_sources uses -1 for the default, because we need
+ * to be able to distinguish not-in-strintmap from valid
+ * relevant_source values from enum file_rename_relevance.
+ * In particular, possibly_cache_new_pair() expects a negative
+ * value for not-found entries.
+ */
strintmap_init_with_options(&renames->relevant_sources[i],
- 0, NULL, 0);
+ -1 /* explicitly invalid */,
+ NULL, 0);
+ strmap_init_with_options(&renames->cached_pairs[i],
+ NULL, 1);
+ strset_init_with_options(&renames->cached_irrelevant[i],
+ NULL, 1);
+ strset_init_with_options(&renames->cached_target_names[i],
+ NULL, 0);
}
/*
@@ -3701,6 +3954,50 @@
trace2_region_leave("merge", "allocate/init", opt->repo);
}
+static void merge_check_renames_reusable(struct merge_options *opt,
+ struct merge_result *result,
+ struct tree *merge_base,
+ struct tree *side1,
+ struct tree *side2)
+{
+ struct rename_info *renames;
+ struct tree **merge_trees;
+ struct merge_options_internal *opti = result->priv;
+
+ if (!opti)
+ return;
+
+ renames = &opti->renames;
+ merge_trees = renames->merge_trees;
+
+ /*
+ * Handle case where previous merge operation did not want cache to
+ * take effect, e.g. because rename/rename(1to1) makes it invalid.
+ */
+ if (!merge_trees[0]) {
+ assert(!merge_trees[0] && !merge_trees[1] && !merge_trees[2]);
+ renames->cached_pairs_valid_side = 0; /* neither side valid */
+ return;
+ }
+
+ /*
+ * Handle other cases; note that merge_trees[0..2] will only
+ * be NULL if opti is, or if all three were manually set to
+ * NULL by e.g. rename/rename(1to1) handling.
+ */
+ assert(merge_trees[0] && merge_trees[1] && merge_trees[2]);
+
+ /* Check if we meet a condition for re-using cached_pairs */
+ if (oideq(&merge_base->object.oid, &merge_trees[2]->object.oid) &&
+ oideq(&side1->object.oid, &result->tree->object.oid))
+ renames->cached_pairs_valid_side = MERGE_SIDE1;
+ else if (oideq(&merge_base->object.oid, &merge_trees[1]->object.oid) &&
+ oideq(&side2->object.oid, &result->tree->object.oid))
+ renames->cached_pairs_valid_side = MERGE_SIDE2;
+ else
+ renames->cached_pairs_valid_side = 0; /* neither side valid */
+}
+
/*** Function Grouping: merge_incore_*() and their internal variants ***/
/*
@@ -3751,6 +4048,7 @@
result->clean &= strmap_empty(&opt->priv->conflicted);
if (!opt->priv->call_depth) {
result->priv = opt->priv;
+ result->_properly_initialized = RESULT_INITIALIZED;
opt->priv = NULL;
}
}
@@ -3848,7 +4146,16 @@
trace2_region_enter("merge", "merge_start", opt->repo);
assert(opt->ancestor != NULL);
+ merge_check_renames_reusable(opt, result, merge_base, side1, side2);
merge_start(opt, result);
+ /*
+ * Record the trees used in this merge, so if there's a next merge in
+ * a cherry-pick or rebase sequence it might be able to take advantage
+ * of the cached_pairs in that next merge.
+ */
+ opt->priv->renames.merge_trees[0] = merge_base;
+ opt->priv->renames.merge_trees[1] = side1;
+ opt->priv->renames.merge_trees[2] = side2;
trace2_region_leave("merge", "merge_start", opt->repo);
merge_ort_nonrecursive_internal(opt, merge_base, side1, side2, result);
diff --git a/merge-ort.h b/merge-ort.h
index d53a0a3..c011864 100644
--- a/merge-ort.h
+++ b/merge-ort.h
@@ -29,6 +29,8 @@
* !clean) and to print "CONFLICT" messages. Not for external use.
*/
void *priv;
+ /* Also private */
+ unsigned _properly_initialized;
};
/*
diff --git a/parallel-checkout.c b/parallel-checkout.c
index 6b1af32..ddc0ff3 100644
--- a/parallel-checkout.c
+++ b/parallel-checkout.c
@@ -411,7 +411,7 @@
len_data = sizeof(struct pc_item_fixed_portion) + name_len +
working_tree_encoding_len;
- data = xcalloc(1, len_data);
+ data = xmalloc(len_data);
fixed_portion = (struct pc_item_fixed_portion *)data;
fixed_portion->id = pc_item->id;
@@ -421,13 +421,12 @@
fixed_portion->name_len = name_len;
fixed_portion->working_tree_encoding_len = working_tree_encoding_len;
/*
- * We use hashcpy() instead of oidcpy() because the hash[] positions
- * after `the_hash_algo->rawsz` might not be initialized. And Valgrind
- * would complain about passing uninitialized bytes to a syscall
- * (write(2)). There is no real harm in this case, but the warning could
- * hinder the detection of actual errors.
+ * We pad the unused bytes in the hash array because, otherwise,
+ * Valgrind would complain about passing uninitialized bytes to a
+ * write() syscall. The warning doesn't represent any real risk here,
+ * but it could hinder the detection of actual errors.
*/
- hashcpy(fixed_portion->oid.hash, pc_item->ce->oid.hash);
+ oidcpy_with_padding(&fixed_portion->oid, &pc_item->ce->oid);
variant = data + sizeof(*fixed_portion);
if (working_tree_encoding_len) {
diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm
index f6f1dc0..35ff5a6 100644
--- a/perl/Git/SVN.pm
+++ b/perl/Git/SVN.pm
@@ -1636,7 +1636,7 @@
my $commit = shift;
my @revs = split / /, command_oneline(
- qw(rev-list --parents -1 -m), $commit);
+ qw(rev-list --parents -1), $commit);
# Commits with no parents, e.g. the start of a partial branch,
# have changes by definition.
diff --git a/read-cache.c b/read-cache.c
index 1b3c2eb..77961a3 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -26,6 +26,7 @@
#include "thread-utils.h"
#include "progress.h"
#include "sparse-index.h"
+#include "csum-file.h"
/* Mask for the name length in ce_flags in the on-disk index */
@@ -2521,80 +2522,23 @@
}
}
-#define WRITE_BUFFER_SIZE (128 * 1024)
-static unsigned char write_buffer[WRITE_BUFFER_SIZE];
-static unsigned long write_buffer_len;
-
-static int ce_write_flush(git_hash_ctx *context, int fd)
+static int write_index_ext_header(struct hashfile *f,
+ git_hash_ctx *eoie_f,
+ unsigned int ext,
+ unsigned int sz)
{
- unsigned int buffered = write_buffer_len;
- if (buffered) {
- the_hash_algo->update_fn(context, write_buffer, buffered);
- if (write_in_full(fd, write_buffer, buffered) < 0)
- return -1;
- write_buffer_len = 0;
+ hashwrite_be32(f, ext);
+ hashwrite_be32(f, sz);
+
+ if (eoie_f) {
+ ext = htonl(ext);
+ sz = htonl(sz);
+ the_hash_algo->update_fn(eoie_f, &ext, sizeof(ext));
+ the_hash_algo->update_fn(eoie_f, &sz, sizeof(sz));
}
return 0;
}
-static int ce_write(git_hash_ctx *context, int fd, void *data, unsigned int len)
-{
- while (len) {
- unsigned int buffered = write_buffer_len;
- unsigned int partial = WRITE_BUFFER_SIZE - buffered;
- if (partial > len)
- partial = len;
- memcpy(write_buffer + buffered, data, partial);
- buffered += partial;
- if (buffered == WRITE_BUFFER_SIZE) {
- write_buffer_len = buffered;
- if (ce_write_flush(context, fd))
- return -1;
- buffered = 0;
- }
- write_buffer_len = buffered;
- len -= partial;
- data = (char *) data + partial;
- }
- return 0;
-}
-
-static int write_index_ext_header(git_hash_ctx *context, git_hash_ctx *eoie_context,
- int fd, unsigned int ext, unsigned int sz)
-{
- ext = htonl(ext);
- sz = htonl(sz);
- if (eoie_context) {
- the_hash_algo->update_fn(eoie_context, &ext, 4);
- the_hash_algo->update_fn(eoie_context, &sz, 4);
- }
- return ((ce_write(context, fd, &ext, 4) < 0) ||
- (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0;
-}
-
-static int ce_flush(git_hash_ctx *context, int fd, unsigned char *hash)
-{
- unsigned int left = write_buffer_len;
-
- if (left) {
- write_buffer_len = 0;
- the_hash_algo->update_fn(context, write_buffer, left);
- }
-
- /* Flush first if not enough space for hash signature */
- if (left + the_hash_algo->rawsz > WRITE_BUFFER_SIZE) {
- if (write_in_full(fd, write_buffer, left) < 0)
- return -1;
- left = 0;
- }
-
- /* Append the hash signature at the end */
- the_hash_algo->final_fn(write_buffer + left, context);
- hashcpy(hash, write_buffer + left);
- left += the_hash_algo->rawsz;
- return (write_in_full(fd, write_buffer, left) < 0) ? -1 : 0;
-}
-
static void ce_smudge_racily_clean_entry(struct index_state *istate,
struct cache_entry *ce)
{
@@ -2673,11 +2617,10 @@
}
}
-static int ce_write_entry(git_hash_ctx *c, int fd, struct cache_entry *ce,
+static int ce_write_entry(struct hashfile *f, struct cache_entry *ce,
struct strbuf *previous_name, struct ondisk_cache_entry *ondisk)
{
int size;
- int result;
unsigned int saved_namelen;
int stripped_name = 0;
static unsigned char padding[8] = { 0x00 };
@@ -2693,11 +2636,9 @@
if (!previous_name) {
int len = ce_namelen(ce);
copy_cache_entry_to_ondisk(ondisk, ce);
- result = ce_write(c, fd, ondisk, size);
- if (!result)
- result = ce_write(c, fd, ce->name, len);
- if (!result)
- result = ce_write(c, fd, padding, align_padding_size(size, len));
+ hashwrite(f, ondisk, size);
+ hashwrite(f, ce->name, len);
+ hashwrite(f, padding, align_padding_size(size, len));
} else {
int common, to_remove, prefix_size;
unsigned char to_remove_vi[16];
@@ -2711,13 +2652,10 @@
prefix_size = encode_varint(to_remove, to_remove_vi);
copy_cache_entry_to_ondisk(ondisk, ce);
- result = ce_write(c, fd, ondisk, size);
- if (!result)
- result = ce_write(c, fd, to_remove_vi, prefix_size);
- if (!result)
- result = ce_write(c, fd, ce->name + common, ce_namelen(ce) - common);
- if (!result)
- result = ce_write(c, fd, padding, 1);
+ hashwrite(f, ondisk, size);
+ hashwrite(f, to_remove_vi, prefix_size);
+ hashwrite(f, ce->name + common, ce_namelen(ce) - common);
+ hashwrite(f, padding, 1);
strbuf_splice(previous_name, common, to_remove,
ce->name + common, ce_namelen(ce) - common);
@@ -2727,7 +2665,7 @@
ce->ce_flags &= ~CE_STRIP_NAME;
}
- return result;
+ return 0;
}
/*
@@ -2839,8 +2777,8 @@
int strip_extensions)
{
uint64_t start = getnanotime();
- int newfd = tempfile->fd;
- git_hash_ctx c, eoie_c;
+ struct hashfile *f;
+ git_hash_ctx *eoie_c = NULL;
struct cache_header hdr;
int i, err = 0, removed, extended, hdr_version;
struct cache_entry **cache = istate->cache;
@@ -2854,6 +2792,8 @@
struct index_entry_offset_table *ieot = NULL;
int nr, nr_threads;
+ f = hashfd(tempfile->fd, tempfile->filename.buf);
+
for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE)
removed++;
@@ -2882,9 +2822,7 @@
hdr.hdr_version = htonl(hdr_version);
hdr.hdr_entries = htonl(entries - removed);
- the_hash_algo->init_fn(&c);
- if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
- return -1;
+ hashwrite(f, &hdr, sizeof(hdr));
if (!HAVE_THREADS || git_config_get_index_threads(&nr_threads))
nr_threads = 1;
@@ -2919,12 +2857,8 @@
}
}
- offset = lseek(newfd, 0, SEEK_CUR);
- if (offset < 0) {
- free(ieot);
- return -1;
- }
- offset += write_buffer_len;
+ offset = hashfile_total(f);
+
nr = 0;
previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
@@ -2959,14 +2893,10 @@
if (previous_name)
previous_name->buf[0] = 0;
nr = 0;
- offset = lseek(newfd, 0, SEEK_CUR);
- if (offset < 0) {
- free(ieot);
- return -1;
- }
- offset += write_buffer_len;
+
+ offset = hashfile_total(f);
}
- if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
+ if (ce_write_entry(f, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
err = -1;
if (err)
@@ -2985,14 +2915,16 @@
return err;
}
- /* Write extension data here */
- offset = lseek(newfd, 0, SEEK_CUR);
- if (offset < 0) {
- free(ieot);
- return -1;
+ offset = hashfile_total(f);
+
+ /*
+ * The extension headers must be hashed on their own for the
+ * EOIE extension. Create a hashfile here to compute that hash.
+ */
+ if (offset && record_eoie()) {
+ CALLOC_ARRAY(eoie_c, 1);
+ the_hash_algo->init_fn(eoie_c);
}
- offset += write_buffer_len;
- the_hash_algo->init_fn(&eoie_c);
/*
* Lets write out CACHE_EXT_INDEXENTRYOFFSETTABLE first so that we
@@ -3005,8 +2937,8 @@
struct strbuf sb = STRBUF_INIT;
write_ieot_extension(&sb, ieot);
- err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0
- || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ err = write_index_ext_header(f, eoie_c, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
free(ieot);
if (err)
@@ -3018,9 +2950,9 @@
struct strbuf sb = STRBUF_INIT;
err = write_link_extension(&sb, istate) < 0 ||
- write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_LINK,
- sb.len) < 0 ||
- ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ write_index_ext_header(f, eoie_c, CACHE_EXT_LINK,
+ sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
if (err)
return -1;
@@ -3029,8 +2961,8 @@
struct strbuf sb = STRBUF_INIT;
cache_tree_write(&sb, istate->cache_tree);
- err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_TREE, sb.len) < 0
- || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ err = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
if (err)
return -1;
@@ -3039,9 +2971,9 @@
struct strbuf sb = STRBUF_INIT;
resolve_undo_write(&sb, istate->resolve_undo);
- err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_RESOLVE_UNDO,
- sb.len) < 0
- || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ err = write_index_ext_header(f, eoie_c, CACHE_EXT_RESOLVE_UNDO,
+ sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
if (err)
return -1;
@@ -3050,9 +2982,9 @@
struct strbuf sb = STRBUF_INIT;
write_untracked_extension(&sb, istate->untracked);
- err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_UNTRACKED,
- sb.len) < 0 ||
- ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ err = write_index_ext_header(f, eoie_c, CACHE_EXT_UNTRACKED,
+ sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
if (err)
return -1;
@@ -3061,14 +2993,14 @@
struct strbuf sb = STRBUF_INIT;
write_fsmonitor_extension(&sb, istate);
- err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0
- || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ err = write_index_ext_header(f, eoie_c, CACHE_EXT_FSMONITOR, sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
if (err)
return -1;
}
if (istate->sparse_index) {
- if (write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)
+ if (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)
return -1;
}
@@ -3078,19 +3010,18 @@
* read. Write it out regardless of the strip_extensions parameter as we need it
* when loading the shared index.
*/
- if (offset && record_eoie()) {
+ if (eoie_c) {
struct strbuf sb = STRBUF_INIT;
- write_eoie_extension(&sb, &eoie_c, offset);
- err = write_index_ext_header(&c, NULL, newfd, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0
- || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ write_eoie_extension(&sb, eoie_c, offset);
+ err = write_index_ext_header(f, NULL, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0;
+ hashwrite(f, sb.buf, sb.len);
strbuf_release(&sb);
if (err)
return -1;
}
- if (ce_flush(&c, newfd, istate->oid.hash))
- return -1;
+ finalize_hashfile(f, istate->oid.hash, CSUM_HASH_IN_STREAM);
if (close_tempfile_gently(tempfile)) {
error(_("could not close '%s'"), get_tempfile_path(tempfile));
return -1;
diff --git a/ref-filter.c b/ref-filter.c
index 97116e1..4db0e40 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -109,6 +109,56 @@
} ref_to_worktree_map;
/*
+ * The enum atom_type is used as the index of valid_atom array.
+ * In the atom parsing stage, it will be passed to used_atom.atom_type
+ * as the identifier of the atom type. We can check the type of used_atom
+ * entry by `if (used_atom[i].atom_type == ATOM_*)`.
+ */
+enum atom_type {
+ ATOM_REFNAME,
+ ATOM_OBJECTTYPE,
+ ATOM_OBJECTSIZE,
+ ATOM_OBJECTNAME,
+ ATOM_DELTABASE,
+ ATOM_TREE,
+ ATOM_PARENT,
+ ATOM_NUMPARENT,
+ ATOM_OBJECT,
+ ATOM_TYPE,
+ ATOM_TAG,
+ ATOM_AUTHOR,
+ ATOM_AUTHORNAME,
+ ATOM_AUTHOREMAIL,
+ ATOM_AUTHORDATE,
+ ATOM_COMMITTER,
+ ATOM_COMMITTERNAME,
+ ATOM_COMMITTEREMAIL,
+ ATOM_COMMITTERDATE,
+ ATOM_TAGGER,
+ ATOM_TAGGERNAME,
+ ATOM_TAGGEREMAIL,
+ ATOM_TAGGERDATE,
+ ATOM_CREATOR,
+ ATOM_CREATORDATE,
+ ATOM_SUBJECT,
+ ATOM_BODY,
+ ATOM_TRAILERS,
+ ATOM_CONTENTS,
+ ATOM_UPSTREAM,
+ ATOM_PUSH,
+ ATOM_SYMREF,
+ ATOM_FLAG,
+ ATOM_HEAD,
+ ATOM_COLOR,
+ ATOM_WORKTREEPATH,
+ ATOM_ALIGN,
+ ATOM_END,
+ ATOM_IF,
+ ATOM_THEN,
+ ATOM_ELSE,
+};
+
+/*
* An atom is a valid field atom listed below, possibly prefixed with
* a "*" to denote deref_tag().
*
@@ -119,6 +169,7 @@
* array.
*/
static struct used_atom {
+ enum atom_type atom_type;
const char *name;
cmp_type type;
info_source source;
@@ -146,6 +197,9 @@
enum { O_FULL, O_LENGTH, O_SHORT } option;
unsigned int length;
} oid;
+ struct {
+ enum { O_SIZE, O_SIZE_DISK } option;
+ } objectsize;
struct email_option {
enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
} email_option;
@@ -269,11 +323,13 @@
const char *arg, struct strbuf *err)
{
if (!arg) {
+ atom->u.objectsize.option = O_SIZE;
if (*atom->name == '*')
oi_deref.info.sizep = &oi_deref.size;
else
oi.info.sizep = &oi.size;
} else if (!strcmp(arg, "disk")) {
+ atom->u.objectsize.option = O_SIZE_DISK;
if (*atom->name == '*')
oi_deref.info.disk_sizep = &oi_deref.disk_size;
else
@@ -501,47 +557,47 @@
int (*parser)(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err);
} valid_atom[] = {
- { "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
- { "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
- { "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
- { "objectname", SOURCE_OTHER, FIELD_STR, oid_atom_parser },
- { "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser },
- { "tree", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
- { "parent", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
- { "numparent", SOURCE_OBJ, FIELD_ULONG },
- { "object", SOURCE_OBJ },
- { "type", SOURCE_OBJ },
- { "tag", SOURCE_OBJ },
- { "author", SOURCE_OBJ },
- { "authorname", SOURCE_OBJ },
- { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
- { "authordate", SOURCE_OBJ, FIELD_TIME },
- { "committer", SOURCE_OBJ },
- { "committername", SOURCE_OBJ },
- { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
- { "committerdate", SOURCE_OBJ, FIELD_TIME },
- { "tagger", SOURCE_OBJ },
- { "taggername", SOURCE_OBJ },
- { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
- { "taggerdate", SOURCE_OBJ, FIELD_TIME },
- { "creator", SOURCE_OBJ },
- { "creatordate", SOURCE_OBJ, FIELD_TIME },
- { "subject", SOURCE_OBJ, FIELD_STR, subject_atom_parser },
- { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
- { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
- { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
- { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
- { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
- { "symref", SOURCE_NONE, FIELD_STR, refname_atom_parser },
- { "flag", SOURCE_NONE },
- { "HEAD", SOURCE_NONE, FIELD_STR, head_atom_parser },
- { "color", SOURCE_NONE, FIELD_STR, color_atom_parser },
- { "worktreepath", SOURCE_NONE },
- { "align", SOURCE_NONE, FIELD_STR, align_atom_parser },
- { "end", SOURCE_NONE },
- { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
- { "then", SOURCE_NONE },
- { "else", SOURCE_NONE },
+ [ATOM_REFNAME] = { "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
+ [ATOM_OBJECTTYPE] = { "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
+ [ATOM_OBJECTSIZE] = { "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
+ [ATOM_OBJECTNAME] = { "objectname", SOURCE_OTHER, FIELD_STR, oid_atom_parser },
+ [ATOM_DELTABASE] = { "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser },
+ [ATOM_TREE] = { "tree", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
+ [ATOM_PARENT] = { "parent", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
+ [ATOM_NUMPARENT] = { "numparent", SOURCE_OBJ, FIELD_ULONG },
+ [ATOM_OBJECT] = { "object", SOURCE_OBJ },
+ [ATOM_TYPE] = { "type", SOURCE_OBJ },
+ [ATOM_TAG] = { "tag", SOURCE_OBJ },
+ [ATOM_AUTHOR] = { "author", SOURCE_OBJ },
+ [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ },
+ [ATOM_AUTHOREMAIL] = { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
+ [ATOM_AUTHORDATE] = { "authordate", SOURCE_OBJ, FIELD_TIME },
+ [ATOM_COMMITTER] = { "committer", SOURCE_OBJ },
+ [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ },
+ [ATOM_COMMITTEREMAIL] = { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
+ [ATOM_COMMITTERDATE] = { "committerdate", SOURCE_OBJ, FIELD_TIME },
+ [ATOM_TAGGER] = { "tagger", SOURCE_OBJ },
+ [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ },
+ [ATOM_TAGGEREMAIL] = { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
+ [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME },
+ [ATOM_CREATOR] = { "creator", SOURCE_OBJ },
+ [ATOM_CREATORDATE] = { "creatordate", SOURCE_OBJ, FIELD_TIME },
+ [ATOM_SUBJECT] = { "subject", SOURCE_OBJ, FIELD_STR, subject_atom_parser },
+ [ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
+ [ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
+ [ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
+ [ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
+ [ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
+ [ATOM_SYMREF] = { "symref", SOURCE_NONE, FIELD_STR, refname_atom_parser },
+ [ATOM_FLAG] = { "flag", SOURCE_NONE },
+ [ATOM_HEAD] = { "HEAD", SOURCE_NONE, FIELD_STR, head_atom_parser },
+ [ATOM_COLOR] = { "color", SOURCE_NONE, FIELD_STR, color_atom_parser },
+ [ATOM_WORKTREEPATH] = { "worktreepath", SOURCE_NONE },
+ [ATOM_ALIGN] = { "align", SOURCE_NONE, FIELD_STR, align_atom_parser },
+ [ATOM_END] = { "end", SOURCE_NONE },
+ [ATOM_IF] = { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
+ [ATOM_THEN] = { "then", SOURCE_NONE },
+ [ATOM_ELSE] = { "else", SOURCE_NONE },
/*
* Please update $__git_ref_fieldlist in git-completion.bash
* when you add new atoms
@@ -623,6 +679,7 @@
at = used_atom_cnt;
used_atom_cnt++;
REALLOC_ARRAY(used_atom, used_atom_cnt);
+ used_atom[at].atom_type = i;
used_atom[at].name = xmemdupz(atom, ep - atom);
used_atom[at].type = valid_atom[i].cmp_type;
used_atom[at].source = valid_atom[i].source;
@@ -647,7 +704,7 @@
return -1;
if (*atom == '*')
need_tagged = 1;
- if (!strcmp(valid_atom[i].name, "symref"))
+ if (i == ATOM_SYMREF)
need_symref = 1;
return at;
}
@@ -960,22 +1017,25 @@
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i].name;
+ enum atom_type atom_type = used_atom[i].atom_type;
struct atom_value *v = &val[i];
if (!!deref != (*name == '*'))
continue;
if (deref)
name++;
- if (!strcmp(name, "objecttype"))
+ if (atom_type == ATOM_OBJECTTYPE)
v->s = xstrdup(type_name(oi->type));
- else if (!strcmp(name, "objectsize:disk")) {
- v->value = oi->disk_size;
- v->s = xstrfmt("%"PRIuMAX, (uintmax_t)oi->disk_size);
- } else if (!strcmp(name, "objectsize")) {
- v->value = oi->size;
- v->s = xstrfmt("%"PRIuMAX , (uintmax_t)oi->size);
- } else if (!strcmp(name, "deltabase"))
+ else if (atom_type == ATOM_OBJECTSIZE) {
+ if (used_atom[i].u.objectsize.option == O_SIZE_DISK) {
+ v->value = oi->disk_size;
+ v->s = xstrfmt("%"PRIuMAX, (uintmax_t)oi->disk_size);
+ } else if (used_atom[i].u.objectsize.option == O_SIZE) {
+ v->value = oi->size;
+ v->s = xstrfmt("%"PRIuMAX , (uintmax_t)oi->size);
+ }
+ } else if (atom_type == ATOM_DELTABASE)
v->s = xstrdup(oid_to_hex(&oi->delta_base_oid));
- else if (deref)
+ else if (atom_type == ATOM_OBJECTNAME && deref)
grab_oid(name, "objectname", &oi->oid, v, &used_atom[i]);
}
}
@@ -988,16 +1048,17 @@
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i].name;
+ enum atom_type atom_type = used_atom[i].atom_type;
struct atom_value *v = &val[i];
if (!!deref != (*name == '*'))
continue;
if (deref)
name++;
- if (!strcmp(name, "tag"))
+ if (atom_type == ATOM_TAG)
v->s = xstrdup(tag->tag);
- else if (!strcmp(name, "type") && tag->tagged)
+ else if (atom_type == ATOM_TYPE && tag->tagged)
v->s = xstrdup(type_name(tag->tagged->type));
- else if (!strcmp(name, "object") && tag->tagged)
+ else if (atom_type == ATOM_OBJECT && tag->tagged)
v->s = xstrdup(oid_to_hex(&tag->tagged->oid));
}
}
@@ -1010,18 +1071,20 @@
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i].name;
+ enum atom_type atom_type = used_atom[i].atom_type;
struct atom_value *v = &val[i];
if (!!deref != (*name == '*'))
continue;
if (deref)
name++;
- if (grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
+ if (atom_type == ATOM_TREE &&
+ grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
continue;
- if (!strcmp(name, "numparent")) {
+ if (atom_type == ATOM_NUMPARENT) {
v->value = commit_list_count(commit->parents);
v->s = xstrfmt("%lu", (unsigned long)v->value);
}
- else if (starts_with(name, "parent")) {
+ else if (atom_type == ATOM_PARENT) {
struct commit_list *parents;
struct strbuf s = STRBUF_INIT;
for (parents = commit->parents; parents; parents = parents->next) {
@@ -1201,15 +1264,16 @@
return;
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i].name;
+ enum atom_type atom_type = used_atom[i].atom_type;
struct atom_value *v = &val[i];
if (!!deref != (*name == '*'))
continue;
if (deref)
name++;
- if (starts_with(name, "creatordate"))
+ if (atom_type == ATOM_CREATORDATE)
grab_date(wholine, v, name);
- else if (!strcmp(name, "creator"))
+ else if (atom_type == ATOM_CREATOR)
v->s = copy_line(wholine);
}
}
@@ -1689,6 +1753,7 @@
/* Fill in specials first */
for (i = 0; i < used_atom_cnt; i++) {
struct used_atom *atom = &used_atom[i];
+ enum atom_type atom_type = atom->atom_type;
const char *name = used_atom[i].name;
struct atom_value *v = &ref->value[i];
int deref = 0;
@@ -1703,18 +1768,18 @@
name++;
}
- if (starts_with(name, "refname"))
+ if (atom_type == ATOM_REFNAME)
refname = get_refname(atom, ref);
- else if (!strcmp(name, "worktreepath")) {
+ else if (atom_type == ATOM_WORKTREEPATH) {
if (ref->kind == FILTER_REFS_BRANCHES)
v->s = get_worktree_path(atom, ref);
else
v->s = xstrdup("");
continue;
}
- else if (starts_with(name, "symref"))
+ else if (atom_type == ATOM_SYMREF)
refname = get_symref(atom, ref);
- else if (starts_with(name, "upstream")) {
+ else if (atom_type == ATOM_UPSTREAM) {
const char *branch_name;
/* only local branches may have an upstream */
if (!skip_prefix(ref->refname, "refs/heads/",
@@ -1730,7 +1795,7 @@
else
v->s = xstrdup("");
continue;
- } else if (!strcmp(atom->name, "push") || starts_with(atom->name, "push:")) {
+ } else if (atom_type == ATOM_PUSH && atom->u.remote_ref.push) {
const char *branch_name;
v->s = xstrdup("");
if (!skip_prefix(ref->refname, "refs/heads/",
@@ -1749,10 +1814,10 @@
free((char *)v->s);
fill_remote_ref_details(atom, refname, branch, &v->s);
continue;
- } else if (starts_with(name, "color:")) {
+ } else if (atom_type == ATOM_COLOR) {
v->s = xstrdup(atom->u.color);
continue;
- } else if (!strcmp(name, "flag")) {
+ } else if (atom_type == ATOM_FLAG) {
char buf[256], *cp = buf;
if (ref->flag & REF_ISSYMREF)
cp = copy_advance(cp, ",symref");
@@ -1765,23 +1830,24 @@
v->s = xstrdup(buf + 1);
}
continue;
- } else if (!deref && grab_oid(name, "objectname", &ref->objectname, v, atom)) {
- continue;
- } else if (!strcmp(name, "HEAD")) {
+ } else if (!deref && atom_type == ATOM_OBJECTNAME &&
+ grab_oid(name, "objectname", &ref->objectname, v, atom)) {
+ continue;
+ } else if (atom_type == ATOM_HEAD) {
if (atom->u.head && !strcmp(ref->refname, atom->u.head))
v->s = xstrdup("*");
else
v->s = xstrdup(" ");
continue;
- } else if (starts_with(name, "align")) {
+ } else if (atom_type == ATOM_ALIGN) {
v->handler = align_atom_handler;
v->s = xstrdup("");
continue;
- } else if (!strcmp(name, "end")) {
+ } else if (atom_type == ATOM_END) {
v->handler = end_atom_handler;
v->s = xstrdup("");
continue;
- } else if (starts_with(name, "if")) {
+ } else if (atom_type == ATOM_IF) {
const char *s;
if (skip_prefix(name, "if:", &s))
v->s = xstrdup(s);
@@ -1789,11 +1855,11 @@
v->s = xstrdup("");
v->handler = if_atom_handler;
continue;
- } else if (!strcmp(name, "then")) {
+ } else if (atom_type == ATOM_THEN) {
v->handler = then_atom_handler;
v->s = xstrdup("");
continue;
- } else if (!strcmp(name, "else")) {
+ } else if (atom_type == ATOM_ELSE) {
v->handler = else_atom_handler;
v->s = xstrdup("");
continue;
diff --git a/revision.h b/revision.h
index 93aa012..17698cb 100644
--- a/revision.h
+++ b/revision.h
@@ -193,10 +193,10 @@
/* Diff-merge flags */
explicit_diff_merges: 1,
merges_need_diff: 1,
+ merges_imply_patch:1,
separate_merges: 1,
combine_merges:1,
combined_all_paths:1,
- combined_imply_patch:1,
dense_combined_merges:1,
first_parent_merges:1;
diff --git a/setup.c b/setup.c
index 59e2fac..ead2f80 100644
--- a/setup.c
+++ b/setup.c
@@ -666,7 +666,9 @@
if (format->version >= 1 && format->unknown_extensions.nr) {
int i;
- strbuf_addstr(err, _("unknown repository extensions found:"));
+ strbuf_addstr(err, Q_("unknown repository extension found:",
+ "unknown repository extensions found:",
+ format->unknown_extensions.nr));
for (i = 0; i < format->unknown_extensions.nr; i++)
strbuf_addf(err, "\n\t%s",
@@ -678,7 +680,9 @@
int i;
strbuf_addstr(err,
- _("repo version is 0, but v1-only extensions found:"));
+ Q_("repo version is 0, but v1-only extension found:",
+ "repo version is 0, but v1-only extensions found:",
+ format->v1_only_extensions.nr));
for (i = 0; i < format->v1_only_extensions.nr; i++)
strbuf_addf(err, "\n\t%s",
diff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c
index 3732122..fc2d460 100644
--- a/t/helper/test-fast-rebase.c
+++ b/t/helper/test-fast-rebase.c
@@ -91,7 +91,6 @@
struct commit *last_commit = NULL, *last_picked_commit = NULL;
struct object_id head;
struct lock_file lock = LOCK_INIT;
- int clean = 1;
struct strvec rev_walk_args = STRVEC_INIT;
struct rev_info revs;
struct commit *commit;
@@ -124,7 +123,8 @@
assert(oideq(&onto->object.oid, &head));
hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
- assert(repo_read_index(the_repository) >= 0);
+ if (repo_read_index(the_repository) < 0)
+ BUG("Could not read index");
repo_init_revisions(the_repository, &revs, NULL);
revs.verbose_header = 1;
@@ -175,11 +175,10 @@
free((char*)merge_opt.ancestor);
merge_opt.ancestor = NULL;
if (!result.clean)
- die("Aborting: Hit a conflict and restarting is not implemented.");
+ break;
last_picked_commit = commit;
last_commit = create_commit(result.tree, commit, last_commit);
}
- fprintf(stderr, "\nDone.\n");
/* TODO: There should be some kind of rev_info_free(&revs) call... */
memset(&revs, 0, sizeof(revs));
@@ -188,24 +187,39 @@
if (result.clean < 0)
exit(128);
- strbuf_addf(&reflog_msg, "finish rebase %s onto %s",
- oid_to_hex(&last_picked_commit->object.oid),
- oid_to_hex(&last_commit->object.oid));
- if (update_ref(reflog_msg.buf, branch_name.buf,
- &last_commit->object.oid,
- &last_picked_commit->object.oid,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
- error(_("could not update %s"), argv[4]);
- die("Failed to update %s", argv[4]);
- }
- if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
- die(_("unable to update HEAD"));
- strbuf_release(&reflog_msg);
- strbuf_release(&branch_name);
+ if (result.clean) {
+ fprintf(stderr, "\nDone.\n");
+ strbuf_addf(&reflog_msg, "finish rebase %s onto %s",
+ oid_to_hex(&last_picked_commit->object.oid),
+ oid_to_hex(&last_commit->object.oid));
+ if (update_ref(reflog_msg.buf, branch_name.buf,
+ &last_commit->object.oid,
+ &last_picked_commit->object.oid,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
+ error(_("could not update %s"), argv[4]);
+ die("Failed to update %s", argv[4]);
+ }
+ if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
+ die(_("unable to update HEAD"));
+ strbuf_release(&reflog_msg);
+ strbuf_release(&branch_name);
- prime_cache_tree(the_repository, the_repository->index, result.tree);
+ prime_cache_tree(the_repository, the_repository->index,
+ result.tree);
+ } else {
+ fprintf(stderr, "\nAborting: Hit a conflict.\n");
+ strbuf_addf(&reflog_msg, "rebase progress up to %s",
+ oid_to_hex(&last_picked_commit->object.oid));
+ if (update_ref(reflog_msg.buf, "HEAD",
+ &last_commit->object.oid,
+ &head,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
+ error(_("could not update %s"), argv[4]);
+ die("Failed to update %s", argv[4]);
+ }
+ }
if (write_locked_index(&the_index, &lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("unable to write %s"), get_index_file());
- return (clean == 0);
+ return (result.clean == 0);
}
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 547eb3c..2fde235 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -121,12 +121,22 @@
--listen-host 127.0.0.1 &
}
-prepare_a_utf8_locale () {
- a_utf8_locale=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{
- p
- q
-}')
- if test -n "$a_utf8_locale"
+prepare_utf8_locale () {
+ if test -z "$GIT_TEST_UTF8_LOCALE"
+ then
+ case "${LC_ALL:-$LANG}" in
+ *.[Uu][Tt][Ff]8 | *.[Uu][Tt][Ff]-8)
+ GIT_TEST_UTF8_LOCALE="${LC_ALL:-$LANG}"
+ ;;
+ *)
+ GIT_TEST_UTF8_LOCALE=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{
+ p
+ q
+ }')
+ ;;
+ esac
+ fi
+ if test -n "$GIT_TEST_UTF8_LOCALE"
then
test_set_prereq UTF8
else
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
index 4b714e9..f7c7df0 100644
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -63,6 +63,7 @@
git init submodule_update_repo &&
(
cd submodule_update_repo &&
+ branch=$(git symbolic-ref --short HEAD) &&
echo "expect" >>.gitignore &&
echo "actual" >>.gitignore &&
echo "x" >file1 &&
@@ -144,7 +145,7 @@
git checkout -b valid_sub1 &&
git revert HEAD &&
- git checkout "${GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME-master}"
+ git checkout "$branch"
)
}
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 705d62c..2c6e34b 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -84,10 +84,6 @@
passing metrics
'
- # Tell the framework that we are self-testing to make sure
- # it yields a stable result.
- GIT_TEST_FRAMEWORK_SELFTEST=t &&
-
# Point to the t/test-lib.sh, which isn't in ../ as usual
. "\$TEST_DIRECTORY"/test-lib.sh
EOF
diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh
index 002e6d3..930dce0 100755
--- a/t/t1307-config-blob.sh
+++ b/t/t1307-config-blob.sh
@@ -65,9 +65,7 @@
'
test_expect_success 'can parse blob ending with CR' '
- printf "[some]key = value\\r" >config &&
- git add config &&
- git commit -m CR &&
+ test_commit --printf CR config "[some]key = value\\r" &&
echo value >expect &&
git config --blob=HEAD:config some.key >actual &&
test_cmp expect actual
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 6ce62f8..17d3cc1 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -7,11 +7,9 @@
. ./test-lib.sh
test_expect_success setup '
- test_commit A &&
- git tag -f -a -m "annotated A" A &&
+ test_commit --annotate A &&
git checkout -b side &&
- test_commit B &&
- git tag -f -a -m "annotated B" B &&
+ test_commit --annotate B &&
git checkout main &&
test_commit C &&
git branch B A^0
diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh
index be6c84c..f691e6d 100755
--- a/t/t2030-unresolve-info.sh
+++ b/t/t2030-unresolve-info.sh
@@ -179,8 +179,7 @@
test_expect_success 'rerere forget (binary)' '
git checkout -f side &&
- printf "a\0c" >binary &&
- git commit -a -m binary &&
+ test_commit --printf binary binary "a\0c" &&
test_must_fail git merge second &&
git rerere forget binary
'
diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh
index 74cd96e..8bfe3ed 100755
--- a/t/t3513-revert-submodule.sh
+++ b/t/t3513-revert-submodule.sh
@@ -14,7 +14,7 @@
git_revert () {
git status -su >expect &&
ls -1pR * >>expect &&
- tar cf "$TRASH_DIRECTORY/tmp.tar" * &&
+ "$TAR" cf "$TRASH_DIRECTORY/tmp.tar" * &&
may_only_be_test_must_fail "$2" &&
$2 git checkout "$1" &&
if test -n "$2"
@@ -23,7 +23,7 @@
fi &&
git revert HEAD &&
rm -rf * &&
- tar xf "$TRASH_DIRECTORY/tmp.tar" &&
+ "$TAR" xf "$TRASH_DIRECTORY/tmp.tar" &&
git status -su >actual &&
ls -1pR * >>actual &&
test_cmp expect actual &&
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 5f282ec..873aa56 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -859,7 +859,7 @@
git stash
'
-test_expect_success 'stash list implies --first-parent -m' '
+test_expect_success 'stash list -p shows simple diff' '
cat >expect <<-EOF &&
stash@{0}
diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh
index 275ce5f..6cdee2a 100755
--- a/t/t4006-diff-mode.sh
+++ b/t/t4006-diff-mode.sh
@@ -26,10 +26,8 @@
'
test_expect_success 'prepare binary file' '
- git commit -m rezrov &&
- printf "\00\01\02\03\04\05\06" >binbin &&
- git add binbin &&
- git commit -m binbin
+ git commit -m one &&
+ test_commit --printf two binbin "\00\01\02\03\04\05\06"
'
test_expect_success '--stat output after text chmod' '
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 87def81..7fadc98 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -293,6 +293,7 @@
diff-tree --summary initial mode
diff-tree master
+diff-tree -m master
diff-tree -p master
diff-tree -p -m master
diff-tree -c master
@@ -337,6 +338,8 @@
log -m -p master
log --cc -m -p master
log -c -m -p master
+log -m --raw master
+log -m --stat master
log -SF master
log -S F master
log -SF -p master
@@ -452,6 +455,14 @@
diff-tree -R --stat --compact-summary initial mode
EOF
+test_expect_success 'log -m matches log -m -p' '
+ git log -m -p master >result &&
+ process_diffs result >expected &&
+ git log -m >result &&
+ process_diffs result >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'log --diff-merges=on matches --diff-merges=separate' '
git log -p --diff-merges=separate master >result &&
process_diffs result >expected &&
@@ -483,6 +494,19 @@
test_cmp expected actual
'
+# -m in "git diff-index" means "match missing", that differs
+# from its meaning in "git diff". Let's check it in diff-index.
+# The line in the output for removed file should disappear when
+# we provide -m in diff-index.
+test_expect_success 'git diff-index -m' '
+ rm -f file1 &&
+ git diff-index HEAD >without-m &&
+ lines_count=$(wc -l <without-m) &&
+ git diff-index -m HEAD >with-m &&
+ git restore file1 &&
+ test_line_count = $((lines_count - 1)) with-m
+'
+
test_expect_success 'log -S requires an argument' '
test_must_fail git log -S
'
diff --git a/t/t4013/diff.diff-tree_-m_master b/t/t4013/diff.diff-tree_-m_master
new file mode 100644
index 0000000..6d0a220
--- /dev/null
+++ b/t/t4013/diff.diff-tree_-m_master
@@ -0,0 +1,11 @@
+$ git diff-tree -m master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+:040000 040000 65f5c9dd60ce3b2b3324b618ac7accf8d912c113 0564e026437809817a64fff393079714b6dd4628 M dir
+:100644 100644 b414108e81e5091fe0974a1858b4d0d22b107f70 10a8a9f3657f91a156b9f0184ed79a20adef9f7f M file0
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+:040000 040000 f977ed46ae6873c1c30ab878e15a4accedc3618b 0564e026437809817a64fff393079714b6dd4628 M dir
+:100644 100644 f4615da674c09df322d6ba8d6b21ecfb1b1ba510 10a8a9f3657f91a156b9f0184ed79a20adef9f7f M file0
+:000000 100644 0000000000000000000000000000000000000000 b1e67221afe8461efd244b487afca22d46b95eb8 A file1
+:100644 000000 01e79c32a8c99c557f0757da7cb6d65b3414466d 0000000000000000000000000000000000000000 D file2
+:100644 000000 7289e35bff32727c08dda207511bec138fdb9ea5 0000000000000000000000000000000000000000 D file3
+$
diff --git a/t/t4013/diff.log_-m_--raw_master b/t/t4013/diff.log_-m_--raw_master
new file mode 100644
index 0000000..cd2ecc4
--- /dev/null
+++ b/t/t4013/diff.log_-m_--raw_master
@@ -0,0 +1,61 @@
+$ git log -m --raw master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+:100644 100644 cead32e... 992913c... M dir/sub
+:100644 100644 b414108... 10a8a9f... M file0
+
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+:100644 100644 7289e35... 992913c... M dir/sub
+:100644 100644 f4615da... 10a8a9f... M file0
+:000000 100644 0000000... b1e6722... A file1
+:100644 000000 01e79c3... 0000000... D file2
+:100644 000000 7289e35... 0000000... D file3
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+:000000 100644 0000000... b1e6722... A file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+:100644 100644 35d242b... 8422d40... M dir/sub
+:100644 100644 01e79c3... b414108... M file0
+:100644 000000 01e79c3... 0000000... D file2
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_-m_--stat_master b/t/t4013/diff.log_-m_--stat_master
new file mode 100644
index 0000000..c7db084
--- /dev/null
+++ b/t/t4013/diff.log_-m_--stat_master
@@ -0,0 +1,66 @@
+$ git log -m --stat master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
+
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+ dir/sub | 4 ++++
+ file0 | 3 +++
+ file1 | 3 +++
+ file2 | 3 ---
+ file3 | 4 ----
+ 5 files changed, 10 insertions(+), 7 deletions(-)
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh
index c906320..a39a626 100755
--- a/t/t4030-diff-textconv.sh
+++ b/t/t4030-diff-textconv.sh
@@ -26,12 +26,8 @@
chmod +x hexdump
test_expect_success 'setup binary file with history' '
- printf "\\0\\n" >file &&
- git add file &&
- git commit -m one &&
- printf "\\01\\n" >>file &&
- git add file &&
- git commit -m two
+ test_commit --printf one file "\\0\\n" &&
+ test_commit --printf --append two file "\\01\\n"
'
test_expect_success 'file is considered binary by porcelain' '
diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh
index ff06f99..5c509db 100755
--- a/t/t5406-remote-rejects.sh
+++ b/t/t5406-remote-rejects.sh
@@ -5,7 +5,6 @@
. ./test-lib.sh
test_expect_success 'setup' '
- mkdir .git/hooks &&
write_script .git/hooks/update <<-\EOF &&
exit 1
EOF
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 5bb23cc..6da8d76 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -20,8 +20,6 @@
git checkout main
'
-mkdir .git/hooks
-
cat >.git/hooks/post-rewrite <<EOF
#!/bin/sh
echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
index 5d8f401..9f1a483 100755
--- a/t/t5409-colorize-remote-messages.sh
+++ b/t/t5409-colorize-remote-messages.sh
@@ -5,7 +5,6 @@
. ./test-lib.sh
test_expect_success 'setup' '
- mkdir .git/hooks &&
write_script .git/hooks/update <<-\EOF &&
echo error: error
echo ERROR: also highlighted
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index a094113..e2c0c51 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -746,14 +746,8 @@
'
test_expect_success 'setup for detecting upstreamed changes' '
- mkdir src &&
- (
- cd src &&
- git init &&
- printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff &&
- git add stuff &&
- git commit -m "Initial revision"
- ) &&
+ test_create_repo src &&
+ test_commit -C src --printf one stuff "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" &&
git clone src dst &&
(
cd src &&
diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh
index 4a1a912..5bf1026 100755
--- a/t/t5600-clone-fail-cleanup.sh
+++ b/t/t5600-clone-fail-cleanup.sh
@@ -97,4 +97,11 @@
test_dir_is_empty empty-wt
'
+test_expect_success 'transport failure cleans up directory' '
+ test_must_fail git clone --no-local \
+ -u "f() { git-upload-pack \"\$@\"; return 1; }; f" \
+ foo broken-clone &&
+ test_path_is_missing broken-clone
+'
+
test_done
diff --git a/t/t6041-bisect-submodule.sh b/t/t6041-bisect-submodule.sh
index df1eff0..82013fc 100755
--- a/t/t6041-bisect-submodule.sh
+++ b/t/t6041-bisect-submodule.sh
@@ -8,7 +8,7 @@
git_bisect () {
git status -su >expect &&
ls -1pR * >>expect &&
- tar cf "$TRASH_DIRECTORY/tmp.tar" * &&
+ "$TAR" cf "$TRASH_DIRECTORY/tmp.tar" * &&
GOOD=$(git rev-parse --verify HEAD) &&
may_only_be_test_must_fail "$2" &&
$2 git checkout "$1" &&
@@ -25,7 +25,7 @@
git bisect start &&
git bisect good $GOOD &&
rm -rf * &&
- tar xf "$TRASH_DIRECTORY/tmp.tar" &&
+ "$TAR" xf "$TRASH_DIRECTORY/tmp.tar" &&
git status -su >actual &&
ls -1pR * >>actual &&
test_cmp expect actual &&
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index e89b674..88fddc9 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -31,64 +31,32 @@
}
test_expect_success setup '
+ test_commit initial file one &&
+ test_commit second file two &&
+ test_commit third file three &&
+ test_commit --annotate A file A &&
+ test_commit c file c &&
- test_tick &&
- echo one >file && git add file && git commit -m initial &&
- one=$(git rev-parse HEAD) &&
-
- git describe --always HEAD &&
-
- test_tick &&
- echo two >file && git add file && git commit -m second &&
- two=$(git rev-parse HEAD) &&
-
- test_tick &&
- echo three >file && git add file && git commit -m third &&
-
- test_tick &&
- echo A >file && git add file && git commit -m A &&
- test_tick &&
- git tag -a -m A A &&
-
- test_tick &&
- echo c >file && git add file && git commit -m c &&
- test_tick &&
- git tag c &&
-
- git reset --hard $two &&
- test_tick &&
- echo B >side && git add side && git commit -m B &&
- test_tick &&
- git tag -a -m B B &&
+ git reset --hard second &&
+ test_commit --annotate B side B &&
test_tick &&
git merge -m Merged c &&
merged=$(git rev-parse HEAD) &&
- git reset --hard $two &&
- test_tick &&
- echo D >another && git add another && git commit -m D &&
- test_tick &&
- git tag -a -m D D &&
+ git reset --hard second &&
+ test_commit --no-tag D another D &&
+
test_tick &&
git tag -a -m R R &&
- test_tick &&
- echo DD >another && git commit -a -m another &&
-
- test_tick &&
- git tag e &&
-
- test_tick &&
- echo DDD >another && git commit -a -m "yet another" &&
+ test_commit e another DD &&
+ test_commit --no-tag "yet another" another DDD &&
test_tick &&
git merge -m Merged $merged &&
- test_tick &&
- echo X >file && echo X >side && git add file side &&
- git commit -m x
-
+ test_commit --no-tag x file
'
check_describe A-* HEAD
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index 7134769..be84d22 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -4966,6 +4966,64 @@
)
'
+# Testcase 12h, Testcase with two kinds of "relevant" renames
+# Commit O: olddir/{a_1, b}
+# Commit A: newdir/{a_2, b}
+# Commit B: olddir/{alpha_1, b}
+# Expected: newdir/{alpha_2, b}
+
+test_setup_12h () {
+ test_create_repo 12h &&
+ (
+ cd 12h &&
+
+ mkdir olddir &&
+ test_seq 3 8 >olddir/a &&
+ >olddir/b &&
+ git add olddir &&
+ git commit -m orig &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git switch A &&
+ test_seq 3 10 >olddir/a &&
+ git add olddir/a &&
+ git mv olddir newdir &&
+ git commit -m A &&
+
+ git switch B &&
+
+ git mv olddir/a olddir/alpha &&
+ git commit -m B
+ )
+}
+
+test_expect_failure '12h: renaming a file within a renamed directory' '
+ test_setup_12h &&
+ (
+ cd 12h &&
+
+ git checkout A^0 &&
+
+ test_might_fail git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+ git ls-files >tracked &&
+ test_line_count = 2 tracked &&
+
+ test_path_is_missing olddir/a &&
+ test_path_is_file newdir/alpha &&
+ test_path_is_file newdir/b &&
+
+ git rev-parse >actual \
+ HEAD:newdir/alpha HEAD:newdir/b &&
+ git rev-parse >expect \
+ A:newdir/a O:oldir/b &&
+ test_cmp expect actual
+ )
+'
+
###########################################################################
# SECTION 13: Checking informational and conflict messages
#
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh
new file mode 100755
index 0000000..035edc4
--- /dev/null
+++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -0,0 +1,700 @@
+#!/bin/sh
+
+test_description="remember regular & dir renames in sequence of merges"
+
+. ./test-lib.sh
+
+#
+# NOTE 1: this testfile tends to not only rename files, but modify on both
+# sides; without modifying on both sides, optimizations can kick in
+# which make rename detection irrelevant or trivial. We want to make
+# sure that we are triggering rename caching rather than rename
+# bypassing.
+#
+# NOTE 2: this testfile uses 'test-tool fast-rebase' instead of either
+# cherry-pick or rebase. sequencer.c is only superficially
+# integrated with merge-ort; it calls merge_switch_to_result()
+# after EACH merge, which updates the index and working copy AND
+# throws away the cached results (because merge_switch_to_result()
+# is only supposed to be called at the end of the sequence).
+# Integrating them more deeply is a big task, so for now the tests
+# use 'test-tool fast-rebase'.
+#
+
+
+#
+# In the following simple testcase:
+# Base: numbers_1, values_1
+# Upstream: numbers_2, values_2
+# Topic_1: sequence_3
+# Topic_2: scruples_3
+# or, in english, rename numbers -> sequence in the first commit, and rename
+# values -> scruples in the second commit.
+#
+# This shouldn't be a challenge, it's just verifying that cached renames isn't
+# preventing us from finding new renames.
+#
+test_expect_success 'caching renames does not preclude finding new ones' '
+ test_create_repo caching-renames-and-new-renames &&
+ (
+ cd caching-renames-and-new-renames &&
+
+ test_seq 2 10 >numbers &&
+ test_seq 2 10 >values &&
+ git add numbers values &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 1 10 >numbers &&
+ test_seq 1 10 >values &&
+ git add numbers values &&
+ git commit -m "Tweaked both files" &&
+
+ git switch topic &&
+
+ test_seq 2 12 >numbers &&
+ git add numbers &&
+ git mv numbers sequence &&
+ git commit -m A &&
+
+ test_seq 2 12 >values &&
+ git add values &&
+ git mv values scruples &&
+ git commit -m B &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream~1..topic
+
+ git ls-files >tracked-files &&
+ test_line_count = 2 tracked-files &&
+ test_seq 1 12 >expect &&
+ test_cmp expect sequence &&
+ test_cmp expect scruples
+ )
+'
+
+#
+# In the following testcase:
+# Base: numbers_1
+# Upstream: rename numbers_1 -> sequence_2
+# Topic_1: numbers_3
+# Topic_2: numbers_1
+# or, in english, the first commit on the topic branch modifies numbers by
+# shrinking it (dramatically) and the second commit on topic reverts its
+# parent.
+#
+# Can git apply both patches?
+#
+# Traditional cherry-pick/rebase will fail to apply the second commit, the
+# one that reverted its parent, because despite detecting the rename from
+# 'numbers' to 'sequence' for the first commit, it fails to detect that
+# rename when picking the second commit. That's "reasonable" given the
+# dramatic change in size of the file, but remembering the rename and
+# reusing it is reasonable too.
+#
+# We do test here that we expect rename detection to only be run once total
+# (the topic side of history doesn't need renames, and with caching we
+# should be able to only run rename detection on the upstream side one
+# time.)
+test_expect_success 'cherry-pick both a commit and its immediate revert' '
+ test_create_repo pick-commit-and-its-immediate-revert &&
+ (
+ cd pick-commit-and-its-immediate-revert &&
+
+ test_seq 11 30 >numbers &&
+ git add numbers &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 1 30 >numbers &&
+ git add numbers &&
+ git mv numbers sequence &&
+ git commit -m "Renamed (and modified) numbers -> sequence" &&
+
+ git switch topic &&
+
+ test_seq 11 13 >numbers &&
+ git add numbers &&
+ git commit -m A &&
+
+ git revert HEAD &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream~1..topic &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 1 calls
+ )
+'
+
+#
+# In the following testcase:
+# Base: sequence_1
+# Upstream: rename sequence_1 -> values_2
+# Topic_1: rename sequence_1 -> values_3
+# Topic_2: add unrelated sequence_4
+# or, in english, both sides rename sequence -> values, and then the second
+# commit on the topic branch adds an unrelated file called sequence.
+#
+# This testcase presents no problems for git traditionally, but having both
+# sides do the same rename in effect "uses it up" and if it remains cached,
+# could cause a spurious rename/add conflict.
+#
+test_expect_success 'rename same file identically, then reintroduce it' '
+ test_create_repo rename-rename-1to1-then-add-old-filename &&
+ (
+ cd rename-rename-1to1-then-add-old-filename &&
+
+ test_seq 3 8 >sequence &&
+ git add sequence &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 1 8 >sequence &&
+ git add sequence &&
+ git mv sequence values &&
+ git commit -m "Renamed (and modified) sequence -> values" &&
+
+ git switch topic &&
+
+ test_seq 3 10 >sequence &&
+ git add sequence &&
+ git mv sequence values &&
+ git commit -m A &&
+
+ test_write_lines A B C D E F G H I J >sequence &&
+ git add sequence &&
+ git commit -m B &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream~1..topic &&
+
+ git ls-files >tracked &&
+ test_line_count = 2 tracked &&
+ test_path_is_file values &&
+ test_path_is_file sequence &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 2 calls
+ )
+'
+
+#
+# In the following testcase:
+# Base: olddir/{valuesZ_1, valuesY_1, valuesX_1}
+# Upstream: rename olddir/valuesZ_1 -> dirA/valuesZ_2
+# rename olddir/valuesY_1 -> dirA/valuesY_2
+# rename olddir/valuesX_1 -> dirB/valuesX_2
+# Topic_1: rename olddir/valuesZ_1 -> dirA/valuesZ_3
+# rename olddir/valuesY_1 -> dirA/valuesY_3
+# Topic_2: add olddir/newfile
+# Expected Pick1: dirA/{valuesZ, valuesY}, dirB/valuesX
+# Expected Pick2: dirA/{valuesZ, valuesY}, dirB/{valuesX, newfile}
+#
+# This testcase presents no problems for git traditionally, but having both
+# sides do the same renames in effect "use it up" but if the renames remain
+# cached, the directory rename could put newfile in the wrong directory.
+#
+test_expect_success 'rename same file identically, then add file to old dir' '
+ test_create_repo rename-rename-1to1-then-add-file-to-old-dir &&
+ (
+ cd rename-rename-1to1-then-add-file-to-old-dir &&
+
+ mkdir olddir/ &&
+ test_seq 3 8 >olddir/valuesZ &&
+ test_seq 3 8 >olddir/valuesY &&
+ test_seq 3 8 >olddir/valuesX &&
+ git add olddir &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 1 8 >olddir/valuesZ &&
+ test_seq 1 8 >olddir/valuesY &&
+ test_seq 1 8 >olddir/valuesX &&
+ git add olddir &&
+ mkdir dirA &&
+ git mv olddir/valuesZ olddir/valuesY dirA &&
+ git mv olddir/ dirB/ &&
+ git commit -m "Renamed (and modified) values*" &&
+
+ git switch topic &&
+
+ test_seq 3 10 >olddir/valuesZ &&
+ test_seq 3 10 >olddir/valuesY &&
+ git add olddir &&
+ mkdir dirA &&
+ git mv olddir/valuesZ olddir/valuesY dirA &&
+ git commit -m A &&
+
+ >olddir/newfile &&
+ git add olddir/newfile &&
+ git commit -m B &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+ git config merge.directoryRenames true &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream~1..topic &&
+
+ git ls-files >tracked &&
+ test_line_count = 4 tracked &&
+ test_path_is_file dirA/valuesZ &&
+ test_path_is_file dirA/valuesY &&
+ test_path_is_file dirB/valuesX &&
+ test_path_is_file dirB/newfile &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 3 calls
+ )
+'
+
+#
+# In the following testcase, upstream renames a directory, and the topic branch
+# first adds a file to the directory, then later renames the directory
+# differently:
+# Base: olddir/a
+# olddir/b
+# Upstream: rename olddir/ -> newdir/
+# Topic_1: add olddir/newfile
+# Topic_2: rename olddir/ -> otherdir/
+#
+# Here we are just concerned that cached renames might prevent us from seeing
+# the rename conflict, and we want to ensure that we do get a conflict.
+#
+# While at it, though, we do test that we only try to detect renames 2
+# times and not three. (The first merge needs to detect renames on the
+# upstream side. Traditionally, the second merge would need to detect
+# renames on both sides of history, but our caching of upstream renames
+# should avoid the need to re-detect upstream renames.)
+#
+test_expect_success 'cached dir rename does not prevent noticing later conflict' '
+ test_create_repo dir-rename-cache-not-occluding-later-conflict &&
+ (
+ cd dir-rename-cache-not-occluding-later-conflict &&
+
+ mkdir olddir &&
+ test_seq 3 10 >olddir/a &&
+ test_seq 3 10 >olddir/b &&
+ git add olddir &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 3 10 >olddir/a &&
+ test_seq 3 10 >olddir/b &&
+ git add olddir &&
+ git mv olddir newdir &&
+ git commit -m "Dir renamed" &&
+
+ git switch topic &&
+
+ >olddir/newfile &&
+ git add olddir/newfile &&
+ git commit -m A &&
+
+ test_seq 1 8 >olddir/a &&
+ test_seq 1 8 >olddir/b &&
+ git add olddir &&
+ git mv olddir otherdir &&
+ git commit -m B &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+ git config merge.directoryRenames true &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&
+ #git cherry-pick upstream..topic &&
+
+ grep CONFLICT..rename/rename output &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 2 calls
+ )
+'
+
+# Helper for the next two tests
+test_setup_upstream_rename () {
+ test_create_repo $1 &&
+ (
+ cd $1 &&
+
+ test_seq 3 8 >somefile &&
+ test_seq 3 8 >relevant-rename &&
+ git add somefile relevant-rename &&
+ mkdir olddir &&
+ test_write_lines a b c d e f g >olddir/a &&
+ test_write_lines z y x w v u t >olddir/b &&
+ git add olddir &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 1 8 >somefile &&
+ test_seq 1 8 >relevant-rename &&
+ git add somefile relevant-rename &&
+ git mv relevant-rename renamed &&
+ echo h >>olddir/a &&
+ echo s >>olddir/b &&
+ git add olddir &&
+ git mv olddir newdir &&
+ git commit -m "Dir renamed"
+ )
+}
+
+#
+# In the following testcase, upstream renames a file in the toplevel directory
+# as well as its only directory:
+# Base: relevant-rename_1
+# somefile
+# olddir/a
+# olddir/b
+# Upstream: rename relevant-rename_1 -> renamed_2
+# rename olddir/ -> newdir/
+# Topic_1: relevant-rename_3
+# Topic_2: olddir/newfile_1
+# Topic_3: olddir/newfile_2
+#
+# In this testcase, since the first commit being picked only modifies a
+# file in the toplevel directory, the directory rename is irrelevant for
+# that first merge. However, we need to notice the directory rename for
+# the merge that picks the second commit, and we don't want the third
+# commit to mess up its location either. We want to make sure that
+# olddir/newfile doesn't exist in the result and that newdir/newfile does.
+#
+# We also test that we only do rename detection twice. We never need
+# rename detection on the topic side of history, but we do need it twice on
+# the upstream side of history. For the first topic commit, we only need
+# the
+# relevant-rename -> renamed
+# rename, because olddir is unmodified by Topic_1. For Topic_2, however,
+# the new file being added to olddir means files that were previously
+# irrelevant for rename detection are now relevant, forcing us to repeat
+# rename detection for the paths we don't already have cached. Topic_3 also
+# tweaks olddir/newfile, but the renames in olddir/ will have been cached
+# from the second rename detection run.
+#
+test_expect_success 'dir rename unneeded, then add new file to old dir' '
+ test_setup_upstream_rename dir-rename-unneeded-until-new-file &&
+ (
+ cd dir-rename-unneeded-until-new-file &&
+
+ git switch topic &&
+
+ test_seq 3 10 >relevant-rename &&
+ git add relevant-rename &&
+ git commit -m A &&
+
+ echo foo >olddir/newfile &&
+ git add olddir/newfile &&
+ git commit -m B &&
+
+ echo bar >>olddir/newfile &&
+ git add olddir/newfile &&
+ git commit -m C &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+ git config merge.directoryRenames true &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream..topic &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 2 calls &&
+
+ git ls-files >tracked &&
+ test_line_count = 5 tracked &&
+ test_path_is_missing olddir/newfile &&
+ test_path_is_file newdir/newfile
+ )
+'
+
+#
+# The following testcase is *very* similar to the last one, but instead of
+# adding a new olddir/newfile, it renames somefile -> olddir/newfile:
+# Base: relevant-rename_1
+# somefile_1
+# olddir/a
+# olddir/b
+# Upstream: rename relevant-rename_1 -> renamed_2
+# rename olddir/ -> newdir/
+# Topic_1: relevant-rename_3
+# Topic_2: rename somefile -> olddir/newfile_2
+# Topic_3: modify olddir/newfile_3
+#
+# In this testcase, since the first commit being picked only modifies a
+# file in the toplevel directory, the directory rename is irrelevant for
+# that first merge. However, we need to notice the directory rename for
+# the merge that picks the second commit, and we don't want the third
+# commit to mess up its location either. We want to make sure that
+# neither somefile or olddir/newfile exists in the result and that
+# newdir/newfile does.
+#
+# This testcase needs one more call to rename detection than the last
+# testcase, because of the somefile -> olddir/newfile rename in Topic_2.
+test_expect_success 'dir rename unneeded, then rename existing file into old dir' '
+ test_setup_upstream_rename dir-rename-unneeded-until-file-moved-inside &&
+ (
+ cd dir-rename-unneeded-until-file-moved-inside &&
+
+ git switch topic &&
+
+ test_seq 3 10 >relevant-rename &&
+ git add relevant-rename &&
+ git commit -m A &&
+
+ test_seq 1 10 >somefile &&
+ git add somefile &&
+ git mv somefile olddir/newfile &&
+ git commit -m B &&
+
+ test_seq 1 12 >olddir/newfile &&
+ git add olddir/newfile &&
+ git commit -m C &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+ git config merge.directoryRenames true &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream..topic &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 3 calls &&
+
+ test_path_is_missing somefile &&
+ test_path_is_missing olddir/newfile &&
+ test_path_is_file newdir/newfile &&
+ git ls-files >tracked &&
+ test_line_count = 4 tracked
+ )
+'
+
+# Helper for the next two tests
+test_setup_topic_rename () {
+ test_create_repo $1 &&
+ (
+ cd $1 &&
+
+ test_seq 3 8 >somefile &&
+ mkdir olddir &&
+ test_seq 3 8 >olddir/a &&
+ echo b >olddir/b &&
+ git add olddir somefile &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch topic &&
+ test_seq 1 8 >somefile &&
+ test_seq 1 8 >olddir/a &&
+ git add somefile olddir/a &&
+ git mv olddir newdir &&
+ git commit -m "Dir renamed" &&
+
+ test_seq 1 10 >somefile &&
+ git add somefile &&
+ mkdir olddir &&
+ >olddir/unrelated-file &&
+ git add olddir &&
+ git commit -m "Unrelated file in recreated old dir"
+ )
+}
+
+#
+# In the following testcase, the first commit on the topic branch renames
+# a directory, while the second recreates the old directory and places a
+# file into it:
+# Base: somefile
+# olddir/a
+# olddir/b
+# Upstream: olddir/newfile
+# Topic_1: somefile_2
+# rename olddir/ -> newdir/
+# Topic_2: olddir/unrelated-file
+#
+# Note that the first pick should merge:
+# Base: somefile
+# olddir/{a,b}
+# Upstream: olddir/newfile
+# Topic_1: rename olddir/ -> newdir/
+# For which the expected result (assuming merge.directoryRenames=true) is
+# clearly:
+# Result: somefile
+# newdir/{a, b, newfile}
+#
+# While the second pick does the following three-way merge:
+# Base (Topic_1): somefile
+# newdir/{a,b}
+# Upstream (Result from 1): same files as base, but adds newdir/newfile
+# Topic_2: same files as base, but adds olddir/unrelated-file
+#
+# The second merge is pretty trivial; upstream adds newdir/newfile, and
+# topic_2 adds olddir/unrelated-file. We're just testing that we don't
+# accidentally cache directory renames somehow and rename
+# olddir/unrelated-file to newdir/unrelated-file.
+#
+# This testcase should only need one call to diffcore_rename_extended().
+test_expect_success 'caching renames only on upstream side, part 1' '
+ test_setup_topic_rename cache-renames-only-upstream-add-file &&
+ (
+ cd cache-renames-only-upstream-add-file &&
+
+ git switch upstream &&
+
+ >olddir/newfile &&
+ git add olddir/newfile &&
+ git commit -m "Add newfile" &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+
+ git config merge.directoryRenames true &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream..topic &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 1 calls &&
+
+ git ls-files >tracked &&
+ test_line_count = 5 tracked &&
+ test_path_is_missing newdir/unrelated-file &&
+ test_path_is_file olddir/unrelated-file &&
+ test_path_is_file newdir/newfile &&
+ test_path_is_file newdir/b &&
+ test_path_is_file newdir/a &&
+ test_path_is_file somefile
+ )
+'
+
+#
+# The following testcase is *very* similar to the last one, but instead of
+# adding a new olddir/newfile, it renames somefile -> olddir/newfile:
+# Base: somefile
+# olddir/a
+# olddir/b
+# Upstream: somefile_1 -> olddir/newfile
+# Topic_1: rename olddir/ -> newdir/
+# somefile_2
+# Topic_2: olddir/unrelated-file
+# somefile_3
+#
+# Much like the previous test, this case is actually trivial and we are just
+# making sure there isn't some spurious directory rename caching going on
+# for the wrong side of history.
+#
+#
+# This testcase should only need two calls to diffcore_rename_extended(),
+# both for the first merge, one for each side of history.
+#
+test_expect_success 'caching renames only on upstream side, part 2' '
+ test_setup_topic_rename cache-renames-only-upstream-rename-file &&
+ (
+ cd cache-renames-only-upstream-rename-file &&
+
+ git switch upstream &&
+
+ git mv somefile olddir/newfile &&
+ git commit -m "Add newfile" &&
+
+ #
+ # Actual testing
+ #
+
+ git switch upstream &&
+
+ git config merge.directoryRenames true &&
+
+ GIT_TRACE2_PERF="$(pwd)/trace.output" &&
+ export GIT_TRACE2_PERF &&
+
+ test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ #git cherry-pick upstream..topic &&
+
+ grep region_enter.*diffcore_rename trace.output >calls &&
+ test_line_count = 2 calls &&
+
+ git ls-files >tracked &&
+ test_line_count = 4 tracked &&
+ test_path_is_missing newdir/unrelated-file &&
+ test_path_is_file olddir/unrelated-file &&
+ test_path_is_file newdir/newfile &&
+ test_path_is_file newdir/b &&
+ test_path_is_file newdir/a
+ )
+'
+
+test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 3b75400..30eff72 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -2167,6 +2167,37 @@
test_cmp expected-list actual-list
'
+test_expect_success $PREREQ 'test using command name with --sendmail-cmd' '
+ clean_fake_sendmail &&
+ PATH="$(pwd):$PATH" \
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --sendmail-cmd="fake.sendmail" \
+ HEAD^ &&
+ test_path_is_file commandline1
+'
+
+test_expect_success $PREREQ 'test using arguments with --sendmail-cmd' '
+ clean_fake_sendmail &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --sendmail-cmd='\''"$(pwd)/fake.sendmail" -f nobody@example.com'\'' \
+ HEAD^ &&
+ test_path_is_file commandline1
+'
+
+test_expect_success $PREREQ 'test shell expression with --sendmail-cmd' '
+ clean_fake_sendmail &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --sendmail-cmd='\''f() { "$(pwd)/fake.sendmail" "$@"; };f'\'' \
+ HEAD^ &&
+ test_path_is_file commandline1
+'
+
test_expect_success $PREREQ 'invoke hook' '
mkdir -p .git/hooks &&
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index 1d3fdcc..d5563ec 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -4,21 +4,13 @@
#
test_description='git svn basic tests'
-GIT_SVN_LC_ALL=${LC_ALL:-$LANG}
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./lib-git-svn.sh
-case "$GIT_SVN_LC_ALL" in
-*.UTF-8)
- test_set_prereq UTF8
- ;;
-*)
- say "# UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)"
- ;;
-esac
+prepare_utf8_locale
test_expect_success 'git svn --version works anywhere' '
nongit git svn --version
@@ -187,8 +179,8 @@
test ! -h "$SVN_TREE"/exec-2.sh &&
test_cmp help "$SVN_TREE"/exec-2.sh'
-name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
-LC_ALL="$GIT_SVN_LC_ALL"
+name="commit with UTF-8 message: locale: $GIT_TEST_UTF8_LOCALE"
+LC_ALL="$GIT_TEST_UTF8_LOCALE"
export LC_ALL
# This test relies on the previous test, hence requires POSIXPERM,SYMLINKS
test_expect_success UTF8,POSIXPERM,SYMLINKS "$name" "
diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh
index 9b44a44..743fbe1 100755
--- a/t/t9115-git-svn-dcommit-funky-renames.sh
+++ b/t/t9115-git-svn-dcommit-funky-renames.sh
@@ -93,9 +93,9 @@
# > ... All of the above characters, except for the backslash, are converted
# > to special UNICODE characters in the range 0xf000 to 0xf0ff (the
# > "Private use area") when creating or accessing files.
-prepare_a_utf8_locale
+prepare_utf8_locale
test_expect_success UTF8,!MINGW,!UTF8_NFD_TO_NFC 'svn.pathnameencoding=cp932 new file on dcommit' '
- LC_ALL=$a_utf8_locale &&
+ LC_ALL=$GIT_TEST_UTF8_LOCALE &&
export LC_ALL &&
neq=$(printf "\201\202") &&
git config svn.pathnameencoding cp932 &&
@@ -107,7 +107,7 @@
# See the comment on the above test for setting of LC_ALL.
test_expect_success !MINGW,!UTF8_NFD_TO_NFC 'svn.pathnameencoding=cp932 rename on dcommit' '
- LC_ALL=$a_utf8_locale &&
+ LC_ALL=$GIT_TEST_UTF8_LOCALE &&
export LC_ALL &&
inf=$(printf "\201\207") &&
git config svn.pathnameencoding cp932 &&
diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh
index 2c213ae..01e1e8a 100755
--- a/t/t9129-git-svn-i18n-commitencoding.sh
+++ b/t/t9129-git-svn-i18n-commitencoding.sh
@@ -14,12 +14,12 @@
test_cmp current "$1"
}
-prepare_a_utf8_locale
+prepare_utf8_locale
compare_svn_head_with () {
# extract just the log message and strip out committer info.
# don't use --limit here since svn 1.1.x doesn't have it,
- LC_ALL="$a_utf8_locale" svn log $(git svn info --url) | perl -w -e '
+ LC_ALL="$GIT_TEST_UTF8_LOCALE" svn log $(git svn info --url) | perl -w -e '
use bytes;
$/ = ("-"x72) . "\n";
my @x = <STDIN>;
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index b823c14..f0448da 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -172,12 +172,23 @@
# --notick
# Do not call test_tick before making a commit
# --append
-# Use "echo >>" instead of "echo >" when writing "<contents>" to
-# "<file>"
+# Use ">>" instead of ">" when writing "<contents>" to "<file>"
+# --printf
+# Use "printf" instead of "echo" when writing "<contents>" to
+# "<file>", use this to write escape sequences such as "\0", a
+# trailing "\n" won't be added automatically. This option
+# supports nothing but the FORMAT of printf(1), i.e. no custom
+# ARGUMENT(s).
# --signoff
# Invoke "git commit" with --signoff
# --author <author>
# Invoke "git commit" with --author <author>
+# --no-tag
+# Do not tag the resulting commit
+# --annotate
+# Create an annotated tag with "--annotate -m <message>". Calls
+# test_tick between making the commit and tag, unless --notick
+# is given.
#
# This will commit a file with the given contents and the given commit
# message, and tag the resulting commit with the given tag name.
@@ -186,17 +197,21 @@
test_commit () {
notick= &&
+ echo=echo &&
append= &&
author= &&
signoff= &&
indir= &&
- no_tag= &&
+ tag=light &&
while test $# != 0
do
case "$1" in
--notick)
notick=yes
;;
+ --printf)
+ echo=printf
+ ;;
--append)
append=yes
;;
@@ -218,7 +233,10 @@
shift
;;
--no-tag)
- no_tag=yes
+ tag=none
+ ;;
+ --annotate)
+ tag=annotate
;;
*)
break
@@ -230,9 +248,9 @@
file=${2:-"$1.t"} &&
if test -n "$append"
then
- echo "${3-$1}" >>"$indir$file"
+ $echo "${3-$1}" >>"$indir$file"
else
- echo "${3-$1}" >"$indir$file"
+ $echo "${3-$1}" >"$indir$file"
fi &&
git ${indir:+ -C "$indir"} add "$file" &&
if test -z "$notick"
@@ -242,10 +260,20 @@
git ${indir:+ -C "$indir"} commit \
${author:+ --author "$author"} \
$signoff -m "$1" &&
- if test -z "$no_tag"
- then
+ case "$tag" in
+ none)
+ ;;
+ light)
git ${indir:+ -C "$indir"} tag "${4:-$1}"
- fi
+ ;;
+ annotate)
+ if test -z "$notick"
+ then
+ test_tick
+ fi &&
+ git ${indir:+ -C "$indir"} tag -a -m "$1" "${4:-$1}"
+ ;;
+ esac
}
# Call test_merge with the arguments "<message> <commit>", where <commit>
@@ -1215,22 +1243,10 @@
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_atexit_cleanup"
}
-# Most tests can use the created repository, but some may need to create more.
+# Deprecated wrapper for "git init", use "git init" directly instead
# Usage: test_create_repo <directory>
test_create_repo () {
- test "$#" = 1 ||
- BUG "not 1 parameter to test-create-repo"
- repo="$1"
- mkdir -p "$repo"
- (
- cd "$repo" || error "Cannot setup test environment"
- "${GIT_TEST_INSTALLED:-$GIT_EXEC_PATH}/git$X" -c \
- init.defaultBranch="${GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME-master}" \
- init \
- "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
- error "cannot run git init -- have you built things yet?"
- mv .git/hooks .git/hooks-disabled
- ) || exit
+ git init "$@"
}
# This function helps on symlink challenged file systems when it is not
diff --git a/t/test-lib.sh b/t/test-lib.sh
index adaf035..54938c6 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -64,6 +64,11 @@
export GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS
fi
+# Explicitly set the default branch name for testing, to avoid the
+# transitory "git init" warning under --verbose.
+: ${GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME:=master}
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
################################################################
# It appears that people try to run tests without building...
"${GIT_TEST_INSTALLED:-$GIT_BUILD_DIR}/git$X" >/dev/null
@@ -1172,7 +1177,7 @@
esac
fi
- if test -z "$debug"
+ if test -z "$debug" && test -n "$remove_trash"
then
test -d "$TRASH_DIRECTORY" ||
error "Tests passed but trash directory already removed before test cleanup; aborting"
@@ -1337,28 +1342,8 @@
exit 1
fi
-# Test repository
-rm -fr "$TRASH_DIRECTORY" || {
- GIT_EXIT_OK=t
- echo >&5 "FATAL: Cannot prepare test area"
- exit 1
-}
-
-HOME="$TRASH_DIRECTORY"
-GNUPGHOME="$HOME/gnupg-home-not-used"
-export HOME GNUPGHOME
-
-if test -z "$TEST_NO_CREATE_REPO"
-then
- test_create_repo "$TRASH_DIRECTORY"
-else
- mkdir -p "$TRASH_DIRECTORY"
-fi
-
-# Use -P to resolve symlinks in our working directory so that the cwd
-# in subprocesses like git equals our $PWD (for pathname comparisons).
-cd -P "$TRASH_DIRECTORY" || exit 1
-
+# Are we running this test at all?
+remove_trash=
this_test=${0##*/}
this_test=${this_test%%-*}
if match_pattern_list "$this_test" $GIT_SKIP_TESTS
@@ -1368,6 +1353,31 @@
test_done
fi
+# Last-minute variable setup
+HOME="$TRASH_DIRECTORY"
+GNUPGHOME="$HOME/gnupg-home-not-used"
+export HOME GNUPGHOME
+
+# Test repository
+rm -fr "$TRASH_DIRECTORY" || {
+ GIT_EXIT_OK=t
+ echo >&5 "FATAL: Cannot prepare test area"
+ exit 1
+}
+
+remove_trash=t
+if test -z "$TEST_NO_CREATE_REPO"
+then
+ git init "$TRASH_DIRECTORY" >&3 2>&4 ||
+ error "cannot run git init"
+else
+ mkdir -p "$TRASH_DIRECTORY"
+fi
+
+# Use -P to resolve symlinks in our working directory so that the cwd
+# in subprocesses like git equals our $PWD (for pathname comparisons).
+cd -P "$TRASH_DIRECTORY" || exit 1
+
if test -n "$write_junit_xml"
then
junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out"
diff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c
index ae052a0..bda283e 100644
--- a/trace2/tr2_dst.c
+++ b/trace2/tr2_dst.c
@@ -204,15 +204,16 @@
fd = socket(AF_UNIX, sock_type, 0);
if (fd == -1)
- return errno;
+ return -1;
sa.sun_family = AF_UNIX;
strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
- int e = errno;
+ int saved_errno = errno;
close(fd);
- return e;
+ errno = saved_errno;
+ return -1;
}
*out_fd = fd;
@@ -227,7 +228,6 @@
{
unsigned int uds_try = 0;
int fd;
- int e;
const char *path = NULL;
/*
@@ -271,15 +271,13 @@
}
if (uds_try & TR2_DST_UDS_TRY_STREAM) {
- e = tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd);
- if (!e)
+ if (!tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd))
goto connected;
- if (e != EPROTOTYPE)
+ if (errno != EPROTOTYPE)
goto error;
}
if (uds_try & TR2_DST_UDS_TRY_DGRAM) {
- e = tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd);
- if (!e)
+ if (!tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd))
goto connected;
}
@@ -287,7 +285,7 @@
if (tr2_dst_want_warning())
warning("trace2: could not connect to socket '%s' for '%s' tracing: %s",
path, tr2_sysenv_display_name(dst->sysenv_var),
- strerror(e));
+ strerror(errno));
tr2_dst_trace_disable(dst);
return 0;
diff --git a/transport.c b/transport.c
index 6cf3da1..50f5830 100644
--- a/transport.c
+++ b/transport.c
@@ -427,7 +427,8 @@
cleanup:
close(data->fd[0]);
- close(data->fd[1]);
+ if (data->fd[1] >= 0)
+ close(data->fd[1]);
if (finish_connect(data->conn))
ret = -1;
data->conn = NULL;
@@ -869,7 +870,8 @@
if (data->got_remote_heads && !transport->stateless_rpc)
packet_flush(data->fd[1]);
close(data->fd[0]);
- close(data->fd[1]);
+ if (data->fd[1] >= 0)
+ close(data->fd[1]);
finish_connect(data->conn);
}