Imported Upstream version 2.15.2 upstream/2.15.2
authorDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:16:23 +0000 (15:16 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:16:23 +0000 (15:16 +0900)
42 files changed:
Documentation/.gitignore
Documentation/Makefile
Documentation/RelNotes/2.13.7.txt [new file with mode: 0644]
Documentation/RelNotes/2.14.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.15.2.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/git-merge-base.txt
GIT-VERSION-GEN
RelNotes
apply.c
builtin/notes.c
builtin/pull.c
builtin/submodule--helper.c
builtin/update-index.c
cache.h
config.c
contrib/completion/git-completion.bash
git-compat-util.h
git-rebase--am.sh
git-rebase.sh
git-submodule.sh
merge-recursive.c
path.c
read-cache.c
refs/files-backend.c
refs/packed-backend.c
refs/packed-backend.h
sequencer.c
submodule-config.c
submodule-config.h
t/helper/test-path-utils.c
t/lib-gpg.sh
t/t0060-path-utils.sh
t/t1409-avoid-packing-refs.sh [new file with mode: 0755]
t/t3310-notes-merge-manual-resolve.sh
t/t3320-notes-merge-worktrees.sh
t/t3400-rebase.sh
t/t3512-cherry-pick-submodule.sh
t/t4107-apply-ignore-whitespace.sh
t/t7415-submodule-names.sh [new file with mode: 0755]
utf8.c
utf8.h

index 2c8b2d6..c7096f1 100644 (file)
@@ -11,3 +11,4 @@ doc.dep
 cmds-*.txt
 mergetools-*.txt
 manpage-base-url.xsl
+SubmittingPatches.txt
index 471bb29..2ab6556 100644 (file)
@@ -67,6 +67,7 @@ SP_ARTICLES += howto/maintain-git
 API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
 SP_ARTICLES += $(API_DOCS)
 
+TECH_DOCS += SubmittingPatches
 TECH_DOCS += technical/hash-function-transition
 TECH_DOCS += technical/http-protocol
 TECH_DOCS += technical/index-format
@@ -181,6 +182,7 @@ ASCIIDOC = asciidoctor
 ASCIIDOC_CONF =
 ASCIIDOC_HTML = xhtml5
 ASCIIDOC_DOCBOOK = docbook45
+ASCIIDOC_EXTRA += -acompat-mode
 ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions
 ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
 DBLATEX_COMMON =
@@ -323,6 +325,7 @@ clean:
        $(RM) *.pdf
        $(RM) howto-index.txt howto/*.html doc.dep
        $(RM) technical/*.html technical/api-index.txt
+       $(RM) SubmittingPatches.txt
        $(RM) $(cmds_txt) $(mergetools_txt) *.made
        $(RM) manpage-base-url.xsl
 
@@ -361,6 +364,9 @@ technical/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
 $(patsubst %,%.html,$(API_DOCS) technical/api-index $(TECH_DOCS)): %.html : %.txt asciidoc.conf
        $(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt
 
+SubmittingPatches.txt: SubmittingPatches
+       $(QUIET_GEN) cp $< $@
+
 XSLT = docbook.xsl
 XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
 
diff --git a/Documentation/RelNotes/2.13.7.txt b/Documentation/RelNotes/2.13.7.txt
new file mode 100644 (file)
index 0000000..09fc014
--- /dev/null
@@ -0,0 +1,20 @@
+Git v2.13.7 Release Notes
+=========================
+
+Fixes since v2.13.6
+-------------------
+
+ * Submodule "names" come from the untrusted .gitmodules file, but we
+   blindly append them to $GIT_DIR/modules to create our on-disk repo
+   paths. This means you can do bad things by putting "../" into the
+   name. We now enforce some rules for submodule names which will cause
+   Git to ignore these malicious names (CVE-2018-11235).
+
+   Credit for finding this vulnerability and the proof of concept from
+   which the test script was adapted goes to Etienne Stalmans.
+
+ * It was possible to trick the code that sanity-checks paths on NTFS
+   into reading random piece of memory (CVE-2018-11233).
+
+Credit for fixing for these bugs goes to Jeff King, Johannes
+Schindelin and others.
diff --git a/Documentation/RelNotes/2.14.4.txt b/Documentation/RelNotes/2.14.4.txt
new file mode 100644 (file)
index 0000000..97755a8
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.14.4 Release Notes
+=========================
+
+This release is to forward-port the fixes made in the v2.13.7 version
+of Git.  See its release notes for details.
diff --git a/Documentation/RelNotes/2.15.2.txt b/Documentation/RelNotes/2.15.2.txt
new file mode 100644 (file)
index 0000000..b480e56
--- /dev/null
@@ -0,0 +1,50 @@
+Git v2.15.2 Release Notes
+=========================
+
+Fixes since v2.15.1
+-------------------
+
+ * Recent update to the refs infrastructure implementation started
+   rewriting packed-refs file more often than before; this has been
+   optimized again for most trivial cases.
+
+ * The SubmittingPatches document has been converted to produce an
+   HTML version via AsciiDoc/Asciidoctor.
+
+ * Contrary to the documentation, "git pull -4/-6 other-args" did not
+   ask the underlying "git fetch" to go over IPv4/IPv6, which has been
+   corrected.
+
+ * When "git rebase" prepared an mailbox of changes and fed it to "git
+   am" to replay them, it was confused when a stray "From " happened
+   to be in the log message of one of the replayed changes.  This has
+   been corrected.
+
+ * Command line completion (in contrib/) has been taught about the
+   "--copy" option of "git branch".
+
+ * "git apply --inaccurate-eof" when used with "--ignore-space-change"
+   triggered an internal sanity check, which has been fixed.
+
+ * The sequencer machinery (used by "git cherry-pick A..B", and "git
+   rebase -i", among other things) would have lost a commit if stopped
+   due to an unlockable index file, which has been fixed.
+
+ * The three-way merge performed by "git cherry-pick" was confused
+   when a new submodule was added in the meantime, which has been
+   fixed (or "papered over").
+
+ * "git notes" sent its error message to its standard output stream,
+   which was corrected.
+
+ * A few scripts (both in production and tests) incorrectly redirected
+   their error output.  These have been corrected.
+
+ * Clarify and enhance documentation for "merge-base --fork-point", as
+   it was clear what it computed but not why/what for.
+
+ * This release also contains the fixes made in the v2.13.7 version of
+   Git.  See its release notes for details.
+
+
+Also contains various documentation updates and code clean-ups.
index 89f2390..3ef3092 100644 (file)
@@ -1,40 +1,47 @@
+Submitting Patches
+==================
+
+== Guidelines
+
 Here are some guidelines for people who want to contribute their code
 to this software.
 
-(0) Decide what to base your work on.
+[[base-branch]]
+=== Decide what to base your work on.
 
 In general, always base your work on the oldest branch that your
 change is relevant to.
 
- - A bugfix should be based on 'maint' in general. If the bug is not
-   present in 'maint', base it on 'master'. For a bug that's not yet
-   in 'master', find the topic that introduces the regression, and
-   base your work on the tip of the topic.
+* A bugfix should be based on `maint` in general. If the bug is not
+  present in `maint`, base it on `master`. For a bug that's not yet
+  in `master`, find the topic that introduces the regression, and
+  base your work on the tip of the topic.
 
- - A new feature should be based on 'master' in general. If the new
-   feature depends on a topic that is in 'pu', but not in 'master',
-   base your work on the tip of that topic.
+* A new feature should be based on `master` in general. If the new
+  feature depends on a topic that is in `pu`, but not in `master`,
+  base your work on the tip of that topic.
 
- - Corrections and enhancements to a topic not yet in 'master' should
-   be based on the tip of that topic. If the topic has not been merged
-   to 'next', it's alright to add a note to squash minor corrections
-   into the series.
+* Corrections and enhancements to a topic not yet in `master` should
+  be based on the tip of that topic. If the topic has not been merged
+  to `next`, it's alright to add a note to squash minor corrections
+  into the series.
 
- - In the exceptional case that a new feature depends on several topics
-   not in 'master', start working on 'next' or 'pu' privately and send
-   out patches for discussion. Before the final merge, you may have to
-   wait until some of the dependent topics graduate to 'master', and
-   rebase your work.
+* In the exceptional case that a new feature depends on several topics
+  not in `master`, start working on `next` or `pu` privately and send
+  out patches for discussion. Before the final merge, you may have to
+  wait until some of the dependent topics graduate to `master`, and
+  rebase your work.
 
- - Some parts of the system have dedicated maintainers with their own
-   repositories (see the section "Subsystems" below).  Changes to
-   these parts should be based on their trees.
+* Some parts of the system have dedicated maintainers with their own
+  repositories (see the section "Subsystems" below).  Changes to
+  these parts should be based on their trees.
 
-To find the tip of a topic branch, run "git log --first-parent
-master..pu" and look for the merge commit. The second parent of this
+To find the tip of a topic branch, run `git log --first-parent
+master..pu` and look for the merge commit. The second parent of this
 commit is the tip of the topic branch.
 
-(1) Make separate commits for logically separate changes.
+[[separate-commits]]
+=== Make separate commits for logically separate changes.
 
 Unless your patch is really trivial, you should not be sending
 out a patch that was generated between your working tree and
@@ -58,8 +65,9 @@ differs substantially from the prior version, are all good things
 to have.
 
 Make sure that you have tests for the bug you are fixing.  See
-t/README for guidance.
+`t/README` for guidance.
 
+[[tests]]
 When adding a new feature, make sure that you have new tests to show
 the feature triggers the new behavior when it should, and to show the
 feature does not trigger when it shouldn't.  After any code change, make
@@ -84,41 +92,45 @@ turning en_UK spelling to en_US).  Obvious typographical fixes are much
 more welcomed ("teh -> "the"), preferably submitted as independent
 patches separate from other documentation changes.
 
+[[whitespace-check]]
 Oh, another thing.  We are picky about whitespaces.  Make sure your
 changes do not trigger errors with the sample pre-commit hook shipped
-in templates/hooks--pre-commit.  To help ensure this does not happen,
-run "git diff --check" on your changes before you commit.
+in `templates/hooks--pre-commit`.  To help ensure this does not happen,
+run `git diff --check` on your changes before you commit.
 
-
-(2) Describe your changes well.
+[[describe-changes]]
+=== Describe your changes well.
 
 The first line of the commit message should be a short description (50
-characters is the soft limit, see DISCUSSION in git-commit(1)), and
-should skip the full stop.  It is also conventional in most cases to
+characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]),
+and should skip the full stop.  It is also conventional in most cases to
 prefix the first line with "area: " where the area is a filename or
 identifier for the general area of the code being modified, e.g.
 
-  . doc: clarify distinction between sign-off and pgp-signing
-  . githooks.txt: improve the intro section
+* doc: clarify distinction between sign-off and pgp-signing
+* githooks.txt: improve the intro section
 
-If in doubt which identifier to use, run "git log --no-merges" on the
+If in doubt which identifier to use, run `git log --no-merges` on the
 files you are modifying to see the current conventions.
 
+[[summary-section]]
 It's customary to start the remainder of the first line after "area: "
 with a lower-case letter. E.g. "doc: clarify...", not "doc:
 Clarify...", or "githooks.txt: improve...", not "githooks.txt:
 Improve...".
 
+[[meaningful-message]]
 The body should provide a meaningful commit message, which:
 
-  . explains the problem the change tries to solve, i.e. what is wrong
-    with the current code without the change.
+. explains the problem the change tries to solve, i.e. what is wrong
+  with the current code without the change.
 
-  . justifies the way the change solves the problem, i.e. why the
-    result with the change is better.
+. justifies the way the change solves the problem, i.e. why the
+  result with the change is better.
 
-  . alternate solutions considered but discarded, if any.
+. alternate solutions considered but discarded, if any.
 
+[[imperative-mood]]
 Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
 instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
 to do frotz", as if you are giving orders to the codebase to change
@@ -126,36 +138,43 @@ its behavior.  Try to make sure your explanation can be understood
 without external resources. Instead of giving a URL to a mailing list
 archive, summarize the relevant points of the discussion.
 
+[[commit-reference]]
 If you want to reference a previous commit in the history of a stable
 branch, use the format "abbreviated sha1 (subject, date)",
 with the subject enclosed in a pair of double-quotes, like this:
 
-    Commit f86a374 ("pack-bitmap.c: fix a memleak", 2015-03-30)
-    noticed that ...
+....
+       Commit f86a374 ("pack-bitmap.c: fix a memleak", 2015-03-30)
+       noticed that ...
+....
 
 The "Copy commit summary" command of gitk can be used to obtain this
-format, or this invocation of "git show":
+format, or this invocation of `git show`:
 
-    git show -s --date=short --pretty='format:%h ("%s", %ad)' <commit>
+....
+       git show -s --date=short --pretty='format:%h ("%s", %ad)' <commit>
+....
 
-(3) Generate your patch using Git tools out of your commits.
+[[git-tools]]
+=== Generate your patch using Git tools out of your commits.
 
 Git based diff tools generate unidiff which is the preferred format.
 
-You do not have to be afraid to use -M option to "git diff" or
-"git format-patch", if your patch involves file renames.  The
+You do not have to be afraid to use `-M` option to `git diff` or
+`git format-patch`, if your patch involves file renames.  The
 receiving end can handle them just fine.
 
+[[review-patch]]
 Please make sure your patch does not add commented out debugging code,
 or include any extra files which do not relate to what your patch
 is trying to achieve. Make sure to review
 your patch after generating it, to ensure accuracy.  Before
-sending out, please make sure it cleanly applies to the "master"
+sending out, please make sure it cleanly applies to the `master`
 branch head.  If you are preparing a work based on "next" branch,
 that is fine, but please mark it as such.
 
-
-(4) Sending your patches.
+[[send-patches]]
+=== Sending your patches.
 
 Learn to use format-patch and send-email if possible.  These commands
 are optimized for the workflow of sending patches, avoiding many ways
@@ -192,7 +211,7 @@ discussion before being accepted, [PATCH v2], [PATCH v3] etc.
 are often seen when you are sending an update to what you have
 previously sent.
 
-The "git format-patch" command follows the best current practice to
+The `git format-patch` command follows the best current practice to
 format the body of an e-mail message.  At the beginning of the
 patch should come your commit message, ending with the
 Signed-off-by: lines, and a line that consists of three dashes,
@@ -213,6 +232,7 @@ an explanation of changes between each iteration can be kept in
 Git-notes and inserted automatically following the three-dash
 line via `git format-patch --notes`.
 
+[[attachment]]
 Do not attach the patch as a MIME attachment, compressed or not.
 Do not let your e-mail client send quoted-printable.  Do not let
 your e-mail client send format=flowed which would destroy
@@ -227,6 +247,7 @@ that it will be postponed.
 Exception:  If your mailer is mangling patches then someone may ask
 you to re-send them using MIME, that is OK.
 
+[[pgp-signature]]
 Do not PGP sign your patch. Most likely, your maintainer or other people on the
 list would not have your PGP key and would not bother obtaining it anyway.
 Your patch is not judged by who you are; a good patch from an unknown origin
@@ -235,28 +256,27 @@ origin that is done poorly or does incorrect things.
 
 If you really really really really want to do a PGP signed
 patch, format it as "multipart/signed", not a text/plain message
-that starts with '-----BEGIN PGP SIGNED MESSAGE-----'.  That is
+that starts with `-----BEGIN PGP SIGNED MESSAGE-----`.  That is
 not a text/plain, it's something else.
 
 Send your patch with "To:" set to the mailing list, with "cc:" listing
 people who are involved in the area you are touching (the output from
-"git blame $path" and "git shortlog --no-merges $path" would help to
++git blame _$path_+ and +git shortlog {litdd}no-merges _$path_+ would help to
 identify them), to solicit comments and reviews.
 
+:1: footnote:[The current maintainer: gitster@pobox.com]
+:2: footnote:[The mailing list: git@vger.kernel.org]
+
 After the list reached a consensus that it is a good idea to apply the
-patch, re-send it with "To:" set to the maintainer [*1*] and "cc:" the
-list [*2*] for inclusion.
+patch, re-send it with "To:" set to the maintainer{1} and "cc:" the
+list{2} for inclusion.
 
-Do not forget to add trailers such as "Acked-by:", "Reviewed-by:" and
-"Tested-by:" lines as necessary to credit people who helped your
+Do not forget to add trailers such as `Acked-by:`, `Reviewed-by:` and
+`Tested-by:` lines as necessary to credit people who helped your
 patch.
 
-    [Addresses]
-     *1* The current maintainer: gitster@pobox.com
-     *2* The mailing list: git@vger.kernel.org
-
-
-(5) Certify your work by adding your "Signed-off-by: " line
+[[sign-off]]
+=== Certify your work by adding your "Signed-off-by: " line
 
 To improve tracking of who did what, we've borrowed the
 "sign-off" procedure from the Linux kernel project on patches
@@ -268,35 +288,39 @@ the patch, which certifies that you wrote it or otherwise have
 the right to pass it on as a open-source patch.  The rules are
 pretty simple: if you can certify the below D-C-O:
 
-        Developer's Certificate of Origin 1.1
-
-        By making a contribution to this project, I certify that:
-
-        (a) The contribution was created in whole or in part by me and I
-            have the right to submit it under the open source license
-            indicated in the file; or
-
-        (b) The contribution is based upon previous work that, to the best
-            of my knowledge, is covered under an appropriate open source
-            license and I have the right under that license to submit that
-            work with modifications, whether created in whole or in part
-            by me, under the same open source license (unless I am
-            permitted to submit under a different license), as indicated
-            in the file; or
-
-        (c) The contribution was provided directly to me by some other
-            person who certified (a), (b) or (c) and I have not modified
-            it.
-
-        (d) I understand and agree that this project and the contribution
-            are public and that a record of the contribution (including all
-            personal information I submit with it, including my sign-off) is
-            maintained indefinitely and may be redistributed consistent with
-            this project or the open source license(s) involved.
+[[dco]]
+.Developer's Certificate of Origin 1.1
+____
+By making a contribution to this project, I certify that:
+
+a. The contribution was created in whole or in part by me and I
+   have the right to submit it under the open source license
+   indicated in the file; or
+
+b. The contribution is based upon previous work that, to the best
+   of my knowledge, is covered under an appropriate open source
+   license and I have the right under that license to submit that
+   work with modifications, whether created in whole or in part
+   by me, under the same open source license (unless I am
+   permitted to submit under a different license), as indicated
+   in the file; or
+
+c. The contribution was provided directly to me by some other
+   person who certified (a), (b) or (c) and I have not modified
+   it.
+
+d. I understand and agree that this project and the contribution
+   are public and that a record of the contribution (including all
+   personal information I submit with it, including my sign-off) is
+   maintained indefinitely and may be redistributed consistent with
+   this project or the open source license(s) involved.
+____
 
 then you just add a line saying
 
-        Signed-off-by: Random J Developer <random@developer.example.org>
+....
+       Signed-off-by: Random J Developer <random@developer.example.org>
+....
 
 This line can be automatically added by Git if you run the git-commit
 command with the -s option.
@@ -307,85 +331,86 @@ D-C-O.  Indeed you are encouraged to do so.  Do not forget to
 place an in-body "From: " line at the beginning to properly attribute
 the change to its true author (see (2) above).
 
+[[real-name]]
 Also notice that a real name is used in the Signed-off-by: line. Please
 don't hide your real name.
 
+[[commit-trailers]]
 If you like, you can put extra tags at the end:
 
-1. "Reported-by:" is used to credit someone who found the bug that
-   the patch attempts to fix.
-2. "Acked-by:" says that the person who is more familiar with the area
-   the patch attempts to modify liked the patch.
-3. "Reviewed-by:", unlike the other tags, can only be offered by the
-   reviewer and means that she is completely satisfied that the patch
-   is ready for application.  It is usually offered only after a
-   detailed review.
-4. "Tested-by:" is used to indicate that the person applied the patch
-   and found it to have the desired effect.
+. `Reported-by:` is used to credit someone who found the bug that
+  the patch attempts to fix.
+. `Acked-by:` says that the person who is more familiar with the area
+  the patch attempts to modify liked the patch.
+. `Reviewed-by:`, unlike the other tags, can only be offered by the
+  reviewer and means that she is completely satisfied that the patch
+  is ready for application.  It is usually offered only after a
+  detailed review.
+. `Tested-by:` is used to indicate that the person applied the patch
+  and found it to have the desired effect.
 
 You can also create your own tag or use one that's in common usage
 such as "Thanks-to:", "Based-on-patch-by:", or "Mentored-by:".
 
-------------------------------------------------
-Subsystems with dedicated maintainers
+== Subsystems with dedicated maintainers
 
 Some parts of the system have dedicated maintainers with their own
 repositories.
 
- - git-gui/ comes from git-gui project, maintained by Pat Thoyts:
+- 'git-gui/' comes from git-gui project, maintained by Pat Thoyts:
 
-        git://repo.or.cz/git-gui.git
+       git://repo.or.cz/git-gui.git
 
- - gitk-git/ comes from Paul Mackerras's gitk project:
+- 'gitk-git/' comes from Paul Mackerras's gitk project:
 
-        git://ozlabs.org/~paulus/gitk
+       git://ozlabs.org/~paulus/gitk
 
- - po/ comes from the localization coordinator, Jiang Xin:
+- 'po/' comes from the localization coordinator, Jiang Xin:
 
        https://github.com/git-l10n/git-po/
 
 Patches to these parts should be based on their trees.
 
-------------------------------------------------
-An ideal patch flow
+[[patch-flow]]
+== An ideal patch flow
 
 Here is an ideal patch flow for this project the current maintainer
 suggests to the contributors:
 
- (0) You come up with an itch.  You code it up.
+. You come up with an itch.  You code it up.
 
- (1) Send it to the list and cc people who may need to know about
-     the change.
+. Send it to the list and cc people who may need to know about
+  the change.
++
+The people who may need to know are the ones whose code you
+are butchering.  These people happen to be the ones who are
+most likely to be knowledgeable enough to help you, but
+they have no obligation to help you (i.e. you ask for help,
+don't demand).  +git log -p {litdd} _$area_you_are_modifying_+ would
+help you find out who they are.
 
-     The people who may need to know are the ones whose code you
-     are butchering.  These people happen to be the ones who are
-     most likely to be knowledgeable enough to help you, but
-     they have no obligation to help you (i.e. you ask for help,
-     don't demand).  "git log -p -- $area_you_are_modifying" would
-     help you find out who they are.
+. You get comments and suggestions for improvements.  You may
+  even get them in a "on top of your change" patch form.
 
- (2) You get comments and suggestions for improvements.  You may
-     even get them in a "on top of your change" patch form.
+. Polish, refine, and re-send to the list and the people who
+  spend their time to improve your patch.  Go back to step (2).
 
- (3) Polish, refine, and re-send to the list and the people who
-     spend their time to improve your patch.  Go back to step (2).
+. The list forms consensus that the last round of your patch is
+  good.  Send it to the maintainer and cc the list.
 
- (4) The list forms consensus that the last round of your patch is
-     good.  Send it to the maintainer and cc the list.
-
- (5) A topic branch is created with the patch and is merged to 'next',
-     and cooked further and eventually graduates to 'master'.
+. A topic branch is created with the patch and is merged to `next`,
+  and cooked further and eventually graduates to `master`.
 
 In any time between the (2)-(3) cycle, the maintainer may pick it up
-from the list and queue it to 'pu', in order to make it easier for
+from the list and queue it to `pu`, in order to make it easier for
 people play with it without having to pick up and apply the patch to
 their trees themselves.
 
-------------------------------------------------
-Know the status of your patch after submission
+[[patch-status]]
+== Know the status of your patch after submission
 
 * You can use Git itself to find out when your patch is merged in
-  master. 'git pull --rebase' will automatically skip already-applied
+  master. `git pull --rebase` will automatically skip already-applied
   patches, and will let you know. This works only if you rebase on top
   of the branch in which your patch has been merged (i.e. it will not
   tell you if your patch is merged in pu if you rebase on top of
@@ -395,8 +420,8 @@ Know the status of your patch after submission
   entitled "What's cooking in git.git" and "What's in git.git" giving
   the status of various proposed changes.
 
---------------------------------------------------
-GitHub-Travis CI hints
+[[travis]]
+== GitHub-Travis CI hints
 
 With an account at GitHub (you can get one for free to work on open
 source projects), you can use Travis CI to test your changes on Linux,
@@ -405,25 +430,25 @@ test build here: https://travis-ci.org/git/git/builds/120473209
 
 Follow these steps for the initial setup:
 
- (1) Fork https://github.com/git/git to your GitHub account.
-     You can find detailed instructions how to fork here:
-     https://help.github.com/articles/fork-a-repo/
+. Fork https://github.com/git/git to your GitHub account.
+  You can find detailed instructions how to fork here:
+  https://help.github.com/articles/fork-a-repo/
 
- (2) Open the Travis CI website: https://travis-ci.org
+. Open the Travis CI website: https://travis-ci.org
 
- (3) Press the "Sign in with GitHub" button.
+. Press the "Sign in with GitHub" button.
 
- (4) Grant Travis CI permissions to access your GitHub account.
-     You can find more information about the required permissions here:
-     https://docs.travis-ci.com/user/github-oauth-scopes
+. Grant Travis CI permissions to access your GitHub account.
+  You can find more information about the required permissions here:
+  https://docs.travis-ci.com/user/github-oauth-scopes
 
- (5) Open your Travis CI profile page: https://travis-ci.org/profile
+. Open your Travis CI profile page: https://travis-ci.org/profile
 
- (6) Enable Travis CI builds for your Git fork.
+. Enable Travis CI builds for your Git fork.
 
 After the initial setup, Travis CI will run whenever you push new changes
 to your fork of Git on GitHub.  You can monitor the test state of all your
-branches here: https://travis-ci.org/<Your GitHub handle>/git/branches
+branches here: https://travis-ci.org/__<Your GitHub handle>__/git/branches
 
 If a branch did not pass all test cases then it is marked with a red
 cross.  In that case you can click on the failing Travis CI job and
@@ -435,17 +460,16 @@ example: https://travis-ci.org/git/git/jobs/122676187
 Fix the problem and push your fix to your Git fork.  This will trigger
 a new Travis CI build to ensure all tests pass.
 
-
-------------------------------------------------
-MUA specific hints
+[[mua]]
+== MUA specific hints
 
 Some of patches I receive or pick up from the list share common
 patterns of breakage.  Please make sure your MUA is set up
 properly not to corrupt whitespaces.
 
-See the DISCUSSION section of git-format-patch(1) for hints on
+See the DISCUSSION section of linkgit:git-format-patch[1] for hints on
 checking your patch by mailing it to yourself and applying with
-git-am(1).
+linkgit:git-am[1].
 
 While you are at it, check the resulting commit log message from
 a trial run of applying the patch.  If what is in the resulting
@@ -457,23 +481,24 @@ should come after the three-dash line that signals the end of the
 commit message.
 
 
-Pine
-----
+=== Pine
 
 (Johannes Schindelin)
 
+....
 I don't know how many people still use pine, but for those poor
 souls it may be good to mention that the quell-flowed-text is
 needed for recent versions.
 
 ... the "no-strip-whitespace-before-send" option, too. AFAIK it
 was introduced in 4.60.
+....
 
 (Linus Torvalds)
 
+....
 And 4.58 needs at least this.
 
----
 diff-tree 8326dd8350be64ac7fc805f6563a1d61ad10d32c (from e886a61f76edf5410573e92e38ce22974f9c40f1)
 Author: Linus Torvalds <torvalds@g5.osdl.org>
 Date:   Mon Aug 15 17:23:51 2005 -0700
@@ -495,10 +520,11 @@ diff --git a/pico/pico.c b/pico/pico.c
 +#endif
                c |= COMP_EXIT;
                break;
-
+....
 
 (Daniel Barkalow)
 
+....
 > A patch to SubmittingPatches, MUA specific help section for
 > users of Pine 4.63 would be very much appreciated.
 
@@ -508,23 +534,21 @@ that or Gentoo did it.) So you need to set the
 "no-strip-whitespace-before-send" option, unless the option you have is
 "strip-whitespace-before-send", in which case you should avoid checking
 it.
+....
 
+=== Thunderbird, KMail, GMail
 
-Thunderbird, KMail, GMail
--------------------------
-
-See the MUA-SPECIFIC HINTS section of git-format-patch(1).
+See the MUA-SPECIFIC HINTS section of linkgit:git-format-patch[1].
 
-Gnus
-----
+=== Gnus
 
-'|' in the *Summary* buffer can be used to pipe the current
+"|" in the `*Summary*` buffer can be used to pipe the current
 message to an external program, and this is a handy way to drive
-"git am".  However, if the message is MIME encoded, what is
+`git am`.  However, if the message is MIME encoded, what is
 piped into the program is the representation you see in your
-*Article* buffer after unwrapping MIME.  This is often not what
+`*Article*` buffer after unwrapping MIME.  This is often not what
 you would want for two reasons.  It tends to screw up non ASCII
 characters (most notably in people's names), and also
-whitespaces (fatal in patches).  Running 'C-u g' to display the
-message in raw form before using '|' to run the pipe can work
+whitespaces (fatal in patches).  Running "C-u g" to display the
+message in raw form before using "|" to run the pipe can work
 this problem around.
index b968b64..502e00e 100644 (file)
@@ -154,23 +154,71 @@ topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
 
-                        o---B1
+                        o---B2
                        /
-       ---o---o---B2--o---o---o---B (origin/master)
+       ---o---o---B1--o---o---o---B (origin/master)
                \
-                B3
+                B0
                  \
-                  Derived (topic)
+                  D0---D1---D (topic)
 
-where `origin/master` used to point at commits B3, B2, B1 and now it
+where `origin/master` used to point at commits B0, B1, B2 and now it
 points at B, and your `topic` branch was started on top of it back
-when `origin/master` was at B3. This mode uses the reflog of
-`origin/master` to find B3 as the fork point, so that the `topic`
-can be rebased on top of the updated `origin/master` by:
+when `origin/master` was at B0, and you built three commits, D0, D1,
+and D, on top of it.  Imagine that you now want to rebase the work
+you did on the topic on top of the updated origin/master.
+
+In such a case, `git merge-base origin/master topic` would return the
+parent of B0 in the above picture, but B0^..D is *not* the range of
+commits you would want to replay on top of B (it includes B0, which
+is not what you wrote; it is a commit the other side discarded when
+it moved its tip from B0 to B1).
+
+`git merge-base --fork-point origin/master topic` is designed to
+help in such a case.  It takes not only B but also B0, B1, and B2
+(i.e. old tips of the remote-tracking branches your repository's
+reflog knows about) into account to see on which commit your topic
+branch was built and finds B0, allowing you to replay only the
+commits on your topic, excluding the commits the other side later
+discarded.
+
+Hence
 
     $ fork_point=$(git merge-base --fork-point origin/master topic)
+
+will find B0, and
+
     $ git rebase --onto origin/master $fork_point topic
 
+will replay D0, D1 and D on top of B to create a new history of this
+shape:
+
+                        o---B2
+                       /
+       ---o---o---B1--o---o---o---B (origin/master)
+               \                   \
+                B0                  D0'--D1'--D' (topic - updated)
+                 \
+                  D0---D1---D (topic - old)
+
+A caveat is that older reflog entries in your repository may be
+expired by `git gc`.  If B0 no longer appears in the reflog of the
+remote-tracking branch `origin/master`, the `--fork-point` mode
+obviously cannot find it and fails, avoiding to give a random and
+useless result (such as the parent of B0, like the same command
+without the `--fork-point` option gives).
+
+Also, the remote-tracking branch you use the `--fork-point` mode
+with must be the one your topic forked from its tip.  If you forked
+from an older commit than the tip, this mode would not find the fork
+point (imagine in the above sample history B0 did not exist,
+origin/master started at B1, moved to B2 and then B, and you forked
+your topic at origin/master^ when origin/master was B1; the shape of
+the history would be the same as above, without B0, and the parent
+of B1 is what `git merge-base origin/master topic` correctly finds,
+but the `--fork-point` mode will not, because it is not one of the
+commits that used to be at the tip of origin/master).
+
 
 See also
 --------
index 879178c..824649f 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.15.1
+DEF_VER=v2.15.2
 
 LF='
 '
index 0c1dd02..6162eb4 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.15.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.15.2.txt
\ No newline at end of file
diff --git a/apply.c b/apply.c
index d4c1dd7..97f3875 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -2958,6 +2958,8 @@ static int apply_one_fragment(struct apply_state *state,
            newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') {
                old--;
                strbuf_setlen(&newlines, newlines.len - 1);
+               preimage.line_allocated[preimage.nr - 1].len--;
+               postimage.line_allocated[postimage.nr - 1].len--;
        }
 
        leading = frag->leading;
@@ -3863,9 +3865,9 @@ static int check_unsafe_path(struct patch *patch)
        if (!patch->is_delete)
                new_name = patch->new_name;
 
-       if (old_name && !verify_path(old_name))
+       if (old_name && !verify_path(old_name, patch->old_mode))
                return error(_("invalid path '%s'"), old_name);
-       if (new_name && !verify_path(new_name))
+       if (new_name && !verify_path(new_name, patch->new_mode))
                return error(_("invalid path '%s'"), new_name);
        return 0;
 }
index 8e54f2d..e435286 100644 (file)
@@ -865,10 +865,10 @@ static int merge(int argc, const char **argv, const char *prefix)
                if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
                        die(_("failed to store link to current notes ref (%s)"),
                            default_notes_ref());
-               printf(_("Automatic notes merge failed. Fix conflicts in %s and "
-                        "commit the result with 'git notes merge --commit', or "
-                        "abort the merge with 'git notes merge --abort'.\n"),
-                      git_path(NOTES_MERGE_WORKTREE));
+               fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
+                                 "and commit the result with 'git notes merge --commit', "
+                                 "or abort the merge with 'git notes merge --abort'.\n"),
+                       git_path(NOTES_MERGE_WORKTREE));
        }
 
        free_notes(t);
index 4edab22..1b4d9cd 100644 (file)
@@ -112,6 +112,8 @@ static char *opt_depth;
 static char *opt_unshallow;
 static char *opt_update_shallow;
 static char *opt_refmap;
+static char *opt_ipv4;
+static char *opt_ipv6;
 
 static struct option pull_options[] = {
        /* Shared options */
@@ -214,6 +216,12 @@ static struct option pull_options[] = {
        OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
                N_("specify fetch refmap"),
                PARSE_OPT_NONEG),
+       OPT_PASSTHRU('4',  "ipv4", &opt_ipv4, NULL,
+               N_("use IPv4 addresses only"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU('6',  "ipv6", &opt_ipv6, NULL,
+               N_("use IPv6 addresses only"),
+               PARSE_OPT_NOARG),
 
        OPT_END()
 };
@@ -518,6 +526,10 @@ static int run_fetch(const char *repo, const char **refspecs)
                argv_array_push(&args, opt_update_shallow);
        if (opt_refmap)
                argv_array_push(&args, opt_refmap);
+       if (opt_ipv4)
+               argv_array_push(&args, opt_ipv4);
+       if (opt_ipv6)
+               argv_array_push(&args, opt_ipv6);
 
        if (repo) {
                argv_array_push(&args, repo);
index 06ed02f..7794049 100644 (file)
@@ -1242,6 +1242,29 @@ static int is_active(int argc, const char **argv, const char *prefix)
        return !is_submodule_active(the_repository, argv[1]);
 }
 
+/*
+ * Exit non-zero if any of the submodule names given on the command line is
+ * invalid. If no names are given, filter stdin to print only valid names
+ * (which is primarily intended for testing).
+ */
+static int check_name(int argc, const char **argv, const char *prefix)
+{
+       if (argc > 1) {
+               while (*++argv) {
+                       if (check_submodule_name(*argv) < 0)
+                               return 1;
+               }
+       } else {
+               struct strbuf buf = STRBUF_INIT;
+               while (strbuf_getline(&buf, stdin) != EOF) {
+                       if (!check_submodule_name(buf.buf))
+                               printf("%s\n", buf.buf);
+               }
+               strbuf_release(&buf);
+       }
+       return 0;
+}
+
 #define SUPPORT_SUPER_PREFIX (1<<0)
 
 struct cmd_struct {
@@ -1263,6 +1286,7 @@ static struct cmd_struct commands[] = {
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
        {"is-active", is_active, 0},
+       {"check-name", check_name, 0},
 };
 
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
index bf7420b..a9fde00 100644 (file)
@@ -361,10 +361,9 @@ static int process_directory(const char *path, int len, struct stat *st)
        return error("%s: is a directory - add files inside instead", path);
 }
 
-static int process_path(const char *path)
+static int process_path(const char *path, struct stat *st, int stat_errno)
 {
        int pos, len;
-       struct stat st;
        const struct cache_entry *ce;
 
        len = strlen(path);
@@ -388,13 +387,13 @@ static int process_path(const char *path)
         * First things first: get the stat information, to decide
         * what to do about the pathname!
         */
-       if (lstat(path, &st) < 0)
-               return process_lstat_error(path, errno);
+       if (stat_errno)
+               return process_lstat_error(path, stat_errno);
 
-       if (S_ISDIR(st.st_mode))
-               return process_directory(path, len, &st);
+       if (S_ISDIR(st->st_mode))
+               return process_directory(path, len, st);
 
-       return add_one_path(ce, path, len, &st);
+       return add_one_path(ce, path, len, st);
 }
 
 static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
@@ -403,7 +402,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
        int size, len, option;
        struct cache_entry *ce;
 
-       if (!verify_path(path))
+       if (!verify_path(path, mode))
                return error("Invalid path '%s'", path);
 
        len = strlen(path);
@@ -446,7 +445,17 @@ static void chmod_path(char flip, const char *path)
 
 static void update_one(const char *path)
 {
-       if (!verify_path(path)) {
+       int stat_errno = 0;
+       struct stat st;
+
+       if (mark_valid_only || mark_skip_worktree_only || force_remove)
+               st.st_mode = 0;
+       else if (lstat(path, &st) < 0) {
+               st.st_mode = 0;
+               stat_errno = errno;
+       } /* else stat is valid */
+
+       if (!verify_path(path, st.st_mode)) {
                fprintf(stderr, "Ignoring path %s\n", path);
                return;
        }
@@ -467,7 +476,7 @@ static void update_one(const char *path)
                report("remove '%s'", path);
                return;
        }
-       if (process_path(path))
+       if (process_path(path, &st, stat_errno))
                die("Unable to process path %s", path);
        report("add '%s'", path);
 }
@@ -537,7 +546,7 @@ static void read_index_info(int nul_term_line)
                        path_name = uq.buf;
                }
 
-               if (!verify_path(path_name)) {
+               if (!verify_path(path_name, mode)) {
                        fprintf(stderr, "Ignoring path %s\n", path_name);
                        continue;
                }
diff --git a/cache.h b/cache.h
index 6440e2b..803bd70 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -608,7 +608,7 @@ extern int write_locked_index(struct index_state *, struct lock_file *lock, unsi
 extern int discard_index(struct index_state *);
 extern void move_index_extensions(struct index_state *dst, struct index_state *src);
 extern int unmerged_index(const struct index_state *);
-extern int verify_path(const char *path);
+extern int verify_path(const char *path, unsigned mode);
 extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
 extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
 extern void adjust_dirname_case(struct index_state *istate, char *name);
@@ -1130,7 +1130,15 @@ int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, struct string_list *prefixes);
 char *strip_path_suffix(const char *path, const char *suffix);
 int daemon_avoid_alias(const char *path);
-extern int is_ntfs_dotgit(const char *name);
+
+/*
+ * These functions match their is_hfs_dotgit() counterparts; see utf8.h for
+ * details.
+ */
+int is_ntfs_dotgit(const char *name);
+int is_ntfs_dotgitmodules(const char *name);
+int is_ntfs_dotgitignore(const char *name);
+int is_ntfs_dotgitattributes(const char *name);
 
 /*
  * Returns true iff "str" could be confused as a command-line option when
index c38401a..41862d4 100644 (file)
--- a/config.c
+++ b/config.c
@@ -2811,7 +2811,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                         * multiple [branch "$name"] sections.
                         */
                        if (copystr.len > 0) {
-                               if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+                               if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) {
                                        ret = write_error(get_lock_file_path(lock));
                                        goto out;
                                }
@@ -2873,7 +2873,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
         * logic in the loop above.
         */
        if (copystr.len > 0) {
-               if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+               if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) {
                        ret = write_error(get_lock_file_path(lock));
                        goto out;
                }
index a54cc2c..17929b0 100644 (file)
@@ -1204,7 +1204,7 @@ _git_branch ()
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --no-contains --merged --no-merged
                        --set-upstream-to= --edit-description --list
-                       --unset-upstream --delete --move --remotes
+                       --unset-upstream --delete --move --copy --remotes
                        --column --no-column --sort= --points-at
                        "
                ;;
index cedad4d..0d270ff 100644 (file)
@@ -978,6 +978,23 @@ static inline int sane_iscase(int x, int is_lower)
                return (x & 0x20) == 0;
 }
 
+/*
+ * Like skip_prefix, but compare case-insensitively. Note that the comparison
+ * is done via tolower(), so it is strictly ASCII (no multi-byte characters or
+ * locale-specific conversions).
+ */
+static inline int skip_iprefix(const char *str, const char *prefix,
+                              const char **out)
+{
+       do {
+               if (!*prefix) {
+                       *out = str;
+                       return 1;
+               }
+       } while (tolower(*str++) == tolower(*prefix++));
+       return 0;
+}
+
 static inline int strtoul_ui(char const *s, int base, unsigned int *result)
 {
        unsigned long ul;
index 6e64d40..14c5078 100644 (file)
@@ -53,6 +53,7 @@ else
 
        git format-patch -k --stdout --full-index --cherry-pick --right-only \
                --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
+               --pretty=mboxrd \
                $git_format_patch_opt \
                "$revisions" ${restrict_revision+^$restrict_revision} \
                >"$GIT_DIR/rebased-patches"
@@ -83,6 +84,7 @@ else
        fi
 
        git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \
+               --patch-format=mboxrd \
                $allow_rerere_autoupdate \
                ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches"
        ret=$?
index 6344e8d..aabbf6b 100755 (executable)
@@ -166,7 +166,7 @@ apply_autostash () {
        if test -f "$state_dir/autostash"
        then
                stash_sha1=$(cat "$state_dir/autostash")
-               if git stash apply $stash_sha1 2>&1 >/dev/null
+               if git stash apply $stash_sha1 >/dev/null 2>&1
                then
                        echo "$(gettext 'Applied autostash.')" >&2
                else
index 66d1ae8..8f260fb 100755 (executable)
@@ -229,6 +229,11 @@ Use -f if you really want to add it." >&2
                sm_name="$sm_path"
        fi
 
+       if ! git submodule--helper check-name "$sm_name"
+       then
+               die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
+       fi
+
        # perhaps the path exists and is already a git repo, else clone it
        if test -e "$sm_path"
        then
index 1d3f8f0..9fb0b9f 100644 (file)
@@ -1901,8 +1901,9 @@ static int process_entry(struct merge_options *o,
                        oid = b_oid;
                        conf = _("directory/file");
                }
-               if (dir_in_way(path, !o->call_depth,
-                              S_ISGITLINK(a_mode))) {
+               if (dir_in_way(path,
+                              !o->call_depth && !S_ISGITLINK(a_mode),
+                              0)) {
                        char *new_path = unique_path(o, path, add_branch);
                        clean_merge = 0;
                        output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
diff --git a/path.c b/path.c
index da8b655..4c4a751 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1305,7 +1305,7 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
 
 int is_ntfs_dotgit(const char *name)
 {
-       int len;
+       size_t len;
 
        for (len = 0; ; len++)
                if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
@@ -1322,6 +1322,90 @@ int is_ntfs_dotgit(const char *name)
                }
 }
 
+static int is_ntfs_dot_generic(const char *name,
+                              const char *dotgit_name,
+                              size_t len,
+                              const char *dotgit_ntfs_shortname_prefix)
+{
+       int saw_tilde;
+       size_t i;
+
+       if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) {
+               i = len + 1;
+only_spaces_and_periods:
+               for (;;) {
+                       char c = name[i++];
+                       if (!c)
+                               return 1;
+                       if (c != ' ' && c != '.')
+                               return 0;
+               }
+       }
+
+       /*
+        * Is it a regular NTFS short name, i.e. shortened to 6 characters,
+        * followed by ~1, ... ~4?
+        */
+       if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
+           name[7] >= '1' && name[7] <= '4') {
+               i = 8;
+               goto only_spaces_and_periods;
+       }
+
+       /*
+        * Is it a fall-back NTFS short name (for details, see
+        * https://en.wikipedia.org/wiki/8.3_filename?
+        */
+       for (i = 0, saw_tilde = 0; i < 8; i++)
+               if (name[i] == '\0')
+                       return 0;
+               else if (saw_tilde) {
+                       if (name[i] < '0' || name[i] > '9')
+                               return 0;
+               } else if (name[i] == '~') {
+                       if (name[++i] < '1' || name[i] > '9')
+                               return 0;
+                       saw_tilde = 1;
+               } else if (i >= 6)
+                       return 0;
+               else if (name[i] < 0) {
+                       /*
+                        * We know our needles contain only ASCII, so we clamp
+                        * here to make the results of tolower() sane.
+                        */
+                       return 0;
+               } else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i])
+                       return 0;
+
+       goto only_spaces_and_periods;
+}
+
+/*
+ * Inline helper to make sure compiler resolves strlen() on literals at
+ * compile time.
+ */
+static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name,
+                                 const char *dotgit_ntfs_shortname_prefix)
+{
+       return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name),
+                                  dotgit_ntfs_shortname_prefix);
+}
+
+int is_ntfs_dotgitmodules(const char *name)
+{
+       return is_ntfs_dot_str(name, "gitmodules", "gi7eba");
+}
+
+int is_ntfs_dotgitignore(const char *name)
+{
+       return is_ntfs_dot_str(name, "gitignore", "gi250a");
+}
+
+int is_ntfs_dotgitattributes(const char *name)
+{
+       return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
+}
+
 int looks_like_command_line_option(const char *str)
 {
        return str && str[0] == '-';
index 65f4fe8..8cef7b6 100644 (file)
@@ -733,7 +733,7 @@ struct cache_entry *make_cache_entry(unsigned int mode,
        int size, len;
        struct cache_entry *ce, *ret;
 
-       if (!verify_path(path)) {
+       if (!verify_path(path, mode)) {
                error("Invalid path '%s'", path);
                return NULL;
        }
@@ -797,7 +797,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
  * Also, we don't want double slashes or slashes at the
  * end that can make pathnames ambiguous.
  */
-static int verify_dotfile(const char *rest)
+static int verify_dotfile(const char *rest, unsigned mode)
 {
        /*
         * The first character was '.', but that
@@ -811,8 +811,13 @@ static int verify_dotfile(const char *rest)
 
        switch (*rest) {
        /*
-        * ".git" followed by  NUL or slash is bad. This
-        * shares the path end test with the ".." case.
+        * ".git" followed by NUL or slash is bad. Note that we match
+        * case-insensitively here, even if ignore_case is not set.
+        * This outlaws ".GIT" everywhere out of an abundance of caution,
+        * since there's really no good reason to allow it.
+        *
+        * Once we've seen ".git", we can also find ".gitmodules", etc (also
+        * case-insensitively).
         */
        case 'g':
        case 'G':
@@ -820,8 +825,15 @@ static int verify_dotfile(const char *rest)
                        break;
                if (rest[2] != 't' && rest[2] != 'T')
                        break;
-               rest += 2;
-       /* fallthrough */
+               if (rest[3] == '\0' || is_dir_sep(rest[3]))
+                       return 0;
+               if (S_ISLNK(mode)) {
+                       rest += 3;
+                       if (skip_iprefix(rest, "modules", &rest) &&
+                           (*rest == '\0' || is_dir_sep(*rest)))
+                               return 0;
+               }
+               break;
        case '.':
                if (rest[1] == '\0' || is_dir_sep(rest[1]))
                        return 0;
@@ -829,7 +841,7 @@ static int verify_dotfile(const char *rest)
        return 1;
 }
 
-int verify_path(const char *path)
+int verify_path(const char *path, unsigned mode)
 {
        char c;
 
@@ -842,12 +854,25 @@ int verify_path(const char *path)
                        return 1;
                if (is_dir_sep(c)) {
 inside:
-                       if (protect_hfs && is_hfs_dotgit(path))
-                               return 0;
-                       if (protect_ntfs && is_ntfs_dotgit(path))
-                               return 0;
+                       if (protect_hfs) {
+                               if (is_hfs_dotgit(path))
+                                       return 0;
+                               if (S_ISLNK(mode)) {
+                                       if (is_hfs_dotgitmodules(path))
+                                               return 0;
+                               }
+                       }
+                       if (protect_ntfs) {
+                               if (is_ntfs_dotgit(path))
+                                       return 0;
+                               if (S_ISLNK(mode)) {
+                                       if (is_ntfs_dotgitmodules(path))
+                                               return 0;
+                               }
+                       }
+
                        c = *path++;
-                       if ((c == '.' && !verify_dotfile(path)) ||
+                       if ((c == '.' && !verify_dotfile(path, mode)) ||
                            is_dir_sep(c) || c == '\0')
                                return 0;
                }
@@ -1164,7 +1189,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
 
        if (!ok_to_add)
                return -1;
-       if (!verify_path(ce->name))
+       if (!verify_path(ce->name, ce->ce_mode))
                return error("Invalid path '%s'", ce->name);
 
        if (!skip_df_check &&
index 8cc1e07..a80d60a 100644 (file)
@@ -2605,7 +2605,23 @@ static int files_transaction_prepare(struct ref_store *ref_store,
                        goto cleanup;
                }
                backend_data->packed_refs_locked = 1;
-               ret = ref_transaction_prepare(packed_transaction, err);
+
+               if (is_packed_transaction_needed(refs->packed_ref_store,
+                                                packed_transaction)) {
+                       ret = ref_transaction_prepare(packed_transaction, err);
+               } else {
+                       /*
+                        * We can skip rewriting the `packed-refs`
+                        * file. But we do need to leave it locked, so
+                        * that somebody else doesn't pack a reference
+                        * that we are trying to delete.
+                        */
+                       if (ref_transaction_abort(packed_transaction, err)) {
+                               ret = TRANSACTION_GENERIC_ERROR;
+                               goto cleanup;
+                       }
+                       backend_data->packed_transaction = NULL;
+               }
        }
 
 cleanup:
index 3279d42..dab8a85 100644 (file)
@@ -1261,6 +1261,100 @@ error:
        return -1;
 }
 
+int is_packed_transaction_needed(struct ref_store *ref_store,
+                                struct ref_transaction *transaction)
+{
+       struct packed_ref_store *refs = packed_downcast(
+                       ref_store,
+                       REF_STORE_READ,
+                       "is_packed_transaction_needed");
+       struct strbuf referent = STRBUF_INIT;
+       size_t i;
+       int ret;
+
+       if (!is_lock_file_locked(&refs->lock))
+               BUG("is_packed_transaction_needed() called while unlocked");
+
+       /*
+        * We're only going to bother returning false for the common,
+        * trivial case that references are only being deleted, their
+        * old values are not being checked, and the old `packed-refs`
+        * file doesn't contain any of those reference(s). This gives
+        * false positives for some other cases that could
+        * theoretically be optimized away:
+        *
+        * 1. It could be that the old value is being verified without
+        *    setting a new value. In this case, we could verify the
+        *    old value here and skip the update if it agrees. If it
+        *    disagrees, we could either let the update go through
+        *    (the actual commit would re-detect and report the
+        *    problem), or come up with a way of reporting such an
+        *    error to *our* caller.
+        *
+        * 2. It could be that a new value is being set, but that it
+        *    is identical to the current packed value of the
+        *    reference.
+        *
+        * Neither of these cases will come up in the current code,
+        * because the only caller of this function passes to it a
+        * transaction that only includes `delete` updates with no
+        * `old_id`. Even if that ever changes, false positives only
+        * cause an optimization to be missed; they do not affect
+        * correctness.
+        */
+
+       /*
+        * Start with the cheap checks that don't require old
+        * reference values to be read:
+        */
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
+
+               if (update->flags & REF_HAVE_OLD)
+                       /* Have to check the old value -> needed. */
+                       return 1;
+
+               if ((update->flags & REF_HAVE_NEW) && !is_null_oid(&update->new_oid))
+                       /* Have to set a new value -> needed. */
+                       return 1;
+       }
+
+       /*
+        * The transaction isn't checking any old values nor is it
+        * setting any nonzero new values, so it still might be able
+        * to be skipped. Now do the more expensive check: the update
+        * is needed if any of the updates is a delete, and the old
+        * `packed-refs` file contains a value for that reference.
+        */
+       ret = 0;
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
+               unsigned int type;
+               struct object_id oid;
+
+               if (!(update->flags & REF_HAVE_NEW))
+                       /*
+                        * This reference isn't being deleted -> not
+                        * needed.
+                        */
+                       continue;
+
+               if (!refs_read_raw_ref(ref_store, update->refname,
+                                      oid.hash, &referent, &type) ||
+                   errno != ENOENT) {
+                       /*
+                        * We have to actually delete that reference
+                        * -> this transaction is needed.
+                        */
+                       ret = 1;
+                       break;
+               }
+       }
+
+       strbuf_release(&referent);
+       return ret;
+}
+
 struct packed_transaction_backend_data {
        /* True iff the transaction owns the packed-refs lock. */
        int own_lock;
index 61687e4..640245d 100644 (file)
@@ -23,4 +23,13 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
 void packed_refs_unlock(struct ref_store *ref_store);
 int packed_refs_is_locked(struct ref_store *ref_store);
 
+/*
+ * Return true if `transaction` really needs to be carried out against
+ * the specified packed_ref_store, or false if it can be skipped
+ * (i.e., because it is an obvious NOOP). `ref_store` must be locked
+ * before calling this function.
+ */
+int is_packed_transaction_needed(struct ref_store *ref_store,
+                                struct ref_transaction *transaction);
+
 #endif /* REFS_PACKED_BACKEND_H */
index 2882d53..5e09b5d 100644 (file)
@@ -438,7 +438,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        char **xopt;
        static struct lock_file index_lock;
 
-       hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
+       if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
+               return -1;
 
        read_cache();
 
index 2aa8a17..aa02641 100644 (file)
@@ -190,6 +190,31 @@ static struct submodule *cache_lookup_name(struct submodule_cache *cache,
        return NULL;
 }
 
+int check_submodule_name(const char *name)
+{
+       /* Disallow empty names */
+       if (!*name)
+               return -1;
+
+       /*
+        * Look for '..' as a path component. Check both '/' and '\\' as
+        * separators rather than is_dir_sep(), because we want the name rules
+        * to be consistent across platforms.
+        */
+       goto in_component; /* always start inside component */
+       while (*name) {
+               char c = *name++;
+               if (c == '/' || c == '\\') {
+in_component:
+                       if (name[0] == '.' && name[1] == '.' &&
+                           (!name[2] || name[2] == '/' || name[2] == '\\'))
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
 static int name_and_item_from_var(const char *var, struct strbuf *name,
                                  struct strbuf *item)
 {
@@ -201,6 +226,12 @@ static int name_and_item_from_var(const char *var, struct strbuf *name,
                return 0;
 
        strbuf_add(name, subsection, subsection_len);
+       if (check_submodule_name(name->buf) < 0) {
+               warning(_("ignoring suspicious submodule name: %s"), name->buf);
+               strbuf_release(name);
+               return 0;
+       }
+
        strbuf_addstr(item, key);
 
        return 1;
index e384583..93880cf 100644 (file)
@@ -45,4 +45,11 @@ extern const struct submodule *submodule_from_cache(struct repository *repo,
                                                    const char *key);
 extern void submodule_free(void);
 
+/*
+ * Returns 0 if the name is syntactically acceptable as a submodule "name"
+ * (e.g., that may be found in the subsection of a .gitmodules file) and -1
+ * otherwise.
+ */
+int check_submodule_name(const char *name);
+
 #endif /* SUBMODULE_CONFIG_H */
index 2b3c509..9484655 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "string-list.h"
+#include "utf8.h"
 
 /*
  * A "string_list_each_func_t" function that normalizes an entry from
@@ -170,6 +171,11 @@ static struct test_data dirname_data[] = {
        { NULL,              NULL     }
 };
 
+static int is_dotgitmodules(const char *path)
+{
+       return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
+}
+
 int cmd_main(int argc, const char **argv)
 {
        if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
@@ -270,6 +276,20 @@ int cmd_main(int argc, const char **argv)
        if (argc == 2 && !strcmp(argv[1], "dirname"))
                return test_function(dirname_data, posix_dirname, argv[1]);
 
+       if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) {
+               int res = 0, expect = 1, i;
+               for (i = 2; i < argc; i++)
+                       if (!strcmp("--not", argv[i]))
+                               expect = !expect;
+                       else if (expect != is_dotgitmodules(argv[i]))
+                               res = error("'%s' is %s.gitmodules", argv[i],
+                                           expect ? "not " : "");
+                       else
+                               fprintf(stderr, "ok: '%s' is %s.gitmodules\n",
+                                       argv[i], expect ? "" : "not ");
+               return !!res;
+       }
+
        fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
                argv[1] ? argv[1] : "(there was none)");
        return 1;
index 43679a4..a5d3b2c 100755 (executable)
@@ -31,7 +31,7 @@ then
                chmod 0700 ./gpghome &&
                GNUPGHOME="$(pwd)/gpghome" &&
                export GNUPGHOME &&
-               (gpgconf --kill gpg-agent 2>&1 >/dev/null || : ) &&
+               (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
                gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \
                        "$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
                gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \
index 7ea2bb5..3f3357e 100755 (executable)
@@ -349,4 +349,90 @@ test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh:
 test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo"
 test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo"
 
+test_expect_success 'match .gitmodules' '
+       test-path-utils is_dotgitmodules \
+               .gitmodules \
+               \
+               .git${u200c}modules \
+               \
+               .Gitmodules \
+               .gitmoduleS \
+               \
+               ".gitmodules " \
+               ".gitmodules." \
+               ".gitmodules  " \
+               ".gitmodules. " \
+               ".gitmodules ." \
+               ".gitmodules.." \
+               ".gitmodules   " \
+               ".gitmodules.  " \
+               ".gitmodules . " \
+               ".gitmodules  ." \
+               \
+               ".Gitmodules " \
+               ".Gitmodules." \
+               ".Gitmodules  " \
+               ".Gitmodules. " \
+               ".Gitmodules ." \
+               ".Gitmodules.." \
+               ".Gitmodules   " \
+               ".Gitmodules.  " \
+               ".Gitmodules . " \
+               ".Gitmodules  ." \
+               \
+               GITMOD~1 \
+               gitmod~1 \
+               GITMOD~2 \
+               gitmod~3 \
+               GITMOD~4 \
+               \
+               "GITMOD~1 " \
+               "gitmod~2." \
+               "GITMOD~3  " \
+               "gitmod~4. " \
+               "GITMOD~1 ." \
+               "gitmod~2   " \
+               "GITMOD~3.  " \
+               "gitmod~4 . " \
+               \
+               GI7EBA~1 \
+               gi7eba~9 \
+               \
+               GI7EB~10 \
+               GI7EB~11 \
+               GI7EB~99 \
+               GI7EB~10 \
+               GI7E~100 \
+               GI7E~101 \
+               GI7E~999 \
+               ~1000000 \
+               ~9999999 \
+               \
+               --not \
+               ".gitmodules x"  \
+               ".gitmodules .x" \
+               \
+               " .gitmodules" \
+               \
+               ..gitmodules \
+               \
+               gitmodules \
+               \
+               .gitmodule \
+               \
+               ".gitmodules x " \
+               ".gitmodules .x" \
+               \
+               GI7EBA~ \
+               GI7EBA~0 \
+               GI7EBA~~1 \
+               GI7EBA~X \
+               Gx7EBA~1 \
+               GI7EBX~1 \
+               \
+               GI7EB~1 \
+               GI7EB~01 \
+               GI7EB~1X
+'
+
 test_done
diff --git a/t/t1409-avoid-packing-refs.sh b/t/t1409-avoid-packing-refs.sh
new file mode 100755 (executable)
index 0000000..e5cb8a2
--- /dev/null
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+test_description='avoid rewriting packed-refs unnecessarily'
+
+. ./test-lib.sh
+
+# Add an identifying mark to the packed-refs file header line. This
+# shouldn't upset readers, and it should be omitted if the file is
+# ever rewritten.
+mark_packed_refs () {
+       sed -e "s/^\(#.*\)/\1 t1409 /" <.git/packed-refs >.git/packed-refs.new &&
+       mv .git/packed-refs.new .git/packed-refs
+}
+
+# Verify that the packed-refs file is still marked.
+check_packed_refs_marked () {
+       grep -q '^#.* t1409 ' .git/packed-refs
+}
+
+test_expect_success 'setup' '
+       git commit --allow-empty -m "Commit A" &&
+       A=$(git rev-parse HEAD) &&
+       git commit --allow-empty -m "Commit B" &&
+       B=$(git rev-parse HEAD) &&
+       git commit --allow-empty -m "Commit C" &&
+       C=$(git rev-parse HEAD)
+'
+
+test_expect_success 'do not create packed-refs file gratuitously' '
+       test_must_fail test -f .git/packed-refs &&
+       git update-ref refs/heads/foo $A &&
+       test_must_fail test -f .git/packed-refs &&
+       git update-ref refs/heads/foo $B &&
+       test_must_fail test -f .git/packed-refs &&
+       git update-ref refs/heads/foo $C $B &&
+       test_must_fail test -f .git/packed-refs &&
+       git update-ref -d refs/heads/foo &&
+       test_must_fail test -f .git/packed-refs
+'
+
+test_expect_success 'check that marking the packed-refs file works' '
+       git for-each-ref >expected &&
+       git pack-refs --all &&
+       mark_packed_refs &&
+       check_packed_refs_marked &&
+       git for-each-ref >actual &&
+       test_cmp expected actual &&
+       git pack-refs --all &&
+       test_must_fail check_packed_refs_marked &&
+       git for-each-ref >actual2 &&
+       test_cmp expected actual2
+'
+
+test_expect_success 'leave packed-refs untouched on update of packed' '
+       git update-ref refs/heads/packed-update $A &&
+       git pack-refs --all &&
+       mark_packed_refs &&
+       git update-ref refs/heads/packed-update $B &&
+       check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on checked update of packed' '
+       git update-ref refs/heads/packed-checked-update $A &&
+       git pack-refs --all &&
+       mark_packed_refs &&
+       git update-ref refs/heads/packed-checked-update $B $A &&
+       check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on verify of packed' '
+       git update-ref refs/heads/packed-verify $A &&
+       git pack-refs --all &&
+       mark_packed_refs &&
+       echo "verify refs/heads/packed-verify $A" | git update-ref --stdin &&
+       check_packed_refs_marked
+'
+
+test_expect_success 'touch packed-refs on delete of packed' '
+       git update-ref refs/heads/packed-delete $A &&
+       git pack-refs --all &&
+       mark_packed_refs &&
+       git update-ref -d refs/heads/packed-delete &&
+       test_must_fail check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on update of loose' '
+       git pack-refs --all &&
+       git update-ref refs/heads/loose-update $A &&
+       mark_packed_refs &&
+       git update-ref refs/heads/loose-update $B &&
+       check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on checked update of loose' '
+       git pack-refs --all &&
+       git update-ref refs/heads/loose-checked-update $A &&
+       mark_packed_refs &&
+       git update-ref refs/heads/loose-checked-update $B $A &&
+       check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on verify of loose' '
+       git pack-refs --all &&
+       git update-ref refs/heads/loose-verify $A &&
+       mark_packed_refs &&
+       echo "verify refs/heads/loose-verify $A" | git update-ref --stdin &&
+       check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on delete of loose' '
+       git pack-refs --all &&
+       git update-ref refs/heads/loose-delete $A &&
+       mark_packed_refs &&
+       git update-ref -d refs/heads/loose-delete &&
+       check_packed_refs_marked
+'
+
+test_done
index baef2d6..9c1bf6e 100755 (executable)
@@ -176,7 +176,7 @@ git rev-parse refs/notes/z > pre_merge_z
 test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
        git update-ref refs/notes/m refs/notes/y &&
        git config core.notesRef refs/notes/m &&
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
@@ -379,7 +379,7 @@ git rev-parse refs/notes/z > pre_merge_z
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
        git update-ref refs/notes/m refs/notes/y &&
        git config core.notesRef refs/notes/m &&
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
@@ -413,7 +413,7 @@ git rev-parse refs/notes/y > pre_merge_y
 git rev-parse refs/notes/z > pre_merge_z
 
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
@@ -494,7 +494,7 @@ cp expect_log_y expect_log_m
 
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
        git update-ref refs/notes/m refs/notes/y &&
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
index b9c3bc2..10bfc8b 100755 (executable)
@@ -61,7 +61,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
        (
                cd worktree2 &&
                git config core.notesRef refs/notes/x &&
-               test_must_fail git notes merge z 2>&1 >out &&
+               test_must_fail git notes merge z >out 2>&1 &&
                test_i18ngrep "Automatic notes merge failed" out &&
                grep -v "A notes merge into refs/notes/x is already in-progress in" out
        ) &&
index f5fd15e..8ac58d5 100755 (executable)
@@ -255,4 +255,26 @@ test_expect_success 'rebase commit with an ancient timestamp' '
        grep "author .* 34567 +0600$" actual
 '
 
+test_expect_success 'rebase with "From " line in commit message' '
+       git checkout -b preserve-from master~1 &&
+       cat >From_.msg <<EOF &&
+Somebody embedded an mbox in a commit message
+
+This is from so-and-so:
+
+From a@b Mon Sep 17 00:00:00 2001
+From: John Doe <nobody@example.com>
+Date: Sat, 11 Nov 2017 00:00:00 +0000
+Subject: not this message
+
+something
+EOF
+       >From_ &&
+       git add From_ &&
+       git commit -F From_.msg &&
+       git rebase master &&
+       git log -1 --pretty=format:%B >out &&
+       test_cmp From_.msg out
+'
+
 test_done
index 6863b7b..ce48c4f 100755 (executable)
@@ -10,4 +10,40 @@ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
 test_submodule_switch "git cherry-pick"
 
+test_expect_success 'unrelated submodule/file conflict is ignored' '
+       test_create_repo sub &&
+
+       touch sub/file &&
+       git -C sub add file &&
+       git -C sub commit -m "add a file in a submodule" &&
+
+       test_create_repo a_repo &&
+       (
+               cd a_repo &&
+               >a_file &&
+               git add a_file &&
+               git commit -m "add a file" &&
+
+               git branch test &&
+               git checkout test &&
+
+               mkdir sub &&
+               >sub/content &&
+               git add sub/content &&
+               git commit -m "add a regular folder with name sub" &&
+
+               echo "123" >a_file &&
+               git add a_file &&
+               git commit -m "modify a file" &&
+
+               git checkout master &&
+
+               git submodule add ../sub sub &&
+               git submodule update sub &&
+               git commit -m "add a submodule info folder with name sub" &&
+
+               git cherry-pick test
+       )
+'
+
 test_done
index 9e29b52..ac72eea 100755 (executable)
@@ -178,4 +178,18 @@ test_expect_success 'patch5 fails (--no-ignore-whitespace)' '
        test_must_fail git apply --no-ignore-whitespace patch5.patch
 '
 
+test_expect_success 'apply --ignore-space-change --inaccurate-eof' '
+       echo 1 >file &&
+       git apply --ignore-space-change --inaccurate-eof <<-\EOF &&
+       diff --git a/file b/file
+       --- a/file
+       +++ b/file
+       @@ -1 +1 @@
+       -1
+       +2
+       EOF
+       printf 2 >expect &&
+       test_cmp expect file
+'
+
 test_done
diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
new file mode 100755 (executable)
index 0000000..75fa071
--- /dev/null
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='check handling of .. in submodule names
+
+Exercise the name-checking function on a variety of names, and then give a
+real-world setup that confirms we catch this in practice.
+'
+. ./test-lib.sh
+
+test_expect_success 'check names' '
+       cat >expect <<-\EOF &&
+       valid
+       valid/with/paths
+       EOF
+
+       git submodule--helper check-name >actual <<-\EOF &&
+       valid
+       valid/with/paths
+
+       ../foo
+       /../foo
+       ..\foo
+       \..\foo
+       foo/..
+       foo/../
+       foo\..
+       foo\..\
+       foo/../bar
+       EOF
+
+       test_cmp expect actual
+'
+
+test_expect_success 'create innocent subrepo' '
+       git init innocent &&
+       git -C innocent commit --allow-empty -m foo
+'
+
+test_expect_success 'submodule add refuses invalid names' '
+       test_must_fail \
+               git submodule add --name ../../modules/evil "$PWD/innocent" evil
+'
+
+test_expect_success 'add evil submodule' '
+       git submodule add "$PWD/innocent" evil &&
+
+       mkdir modules &&
+       cp -r .git/modules/evil modules &&
+       write_script modules/evil/hooks/post-checkout <<-\EOF &&
+       echo >&2 "RUNNING POST CHECKOUT"
+       EOF
+
+       git config -f .gitmodules submodule.evil.update checkout &&
+       git config -f .gitmodules --rename-section \
+               submodule.evil submodule.../../modules/evil &&
+       git add modules &&
+       git commit -am evil
+'
+
+# This step seems like it shouldn't be necessary, since the payload is
+# contained entirely in the evil submodule. But due to the vagaries of the
+# submodule code, checking out the evil module will fail unless ".git/modules"
+# exists. Adding another submodule (with a name that sorts before "evil") is an
+# easy way to make sure this is the case in the victim clone.
+test_expect_success 'add other submodule' '
+       git submodule add "$PWD/innocent" another-module &&
+       git add another-module &&
+       git commit -am another
+'
+
+test_expect_success 'clone evil superproject' '
+       git clone --recurse-submodules . victim >output 2>&1 &&
+       ! grep "RUNNING POST CHECKOUT" output
+'
+
+test_done
diff --git a/utf8.c b/utf8.c
index 2c27ce0..f04c244 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -620,28 +620,33 @@ static ucs_char_t next_hfs_char(const char **in)
        }
 }
 
-int is_hfs_dotgit(const char *path)
+static int is_hfs_dot_generic(const char *path,
+                             const char *needle, size_t needle_len)
 {
        ucs_char_t c;
 
        c = next_hfs_char(&path);
        if (c != '.')
                return 0;
-       c = next_hfs_char(&path);
 
        /*
         * there's a great deal of other case-folding that occurs
-        * in HFS+, but this is enough to catch anything that will
-        * convert to ".git"
+        * in HFS+, but this is enough to catch our fairly vanilla
+        * hard-coded needles.
         */
-       if (c != 'g' && c != 'G')
-               return 0;
-       c = next_hfs_char(&path);
-       if (c != 'i' && c != 'I')
-               return 0;
-       c = next_hfs_char(&path);
-       if (c != 't' && c != 'T')
-               return 0;
+       for (; needle_len > 0; needle++, needle_len--) {
+               c = next_hfs_char(&path);
+
+               /*
+                * We know our needles contain only ASCII, so we clamp here to
+                * make the results of tolower() sane.
+                */
+               if (c > 127)
+                       return 0;
+               if (tolower(c) != *needle)
+                       return 0;
+       }
+
        c = next_hfs_char(&path);
        if (c && !is_dir_sep(c))
                return 0;
@@ -649,6 +654,35 @@ int is_hfs_dotgit(const char *path)
        return 1;
 }
 
+/*
+ * Inline wrapper to make sure the compiler resolves strlen() on literals at
+ * compile time.
+ */
+static inline int is_hfs_dot_str(const char *path, const char *needle)
+{
+       return is_hfs_dot_generic(path, needle, strlen(needle));
+}
+
+int is_hfs_dotgit(const char *path)
+{
+       return is_hfs_dot_str(path, "git");
+}
+
+int is_hfs_dotgitmodules(const char *path)
+{
+       return is_hfs_dot_str(path, "gitmodules");
+}
+
+int is_hfs_dotgitignore(const char *path)
+{
+       return is_hfs_dot_str(path, "gitignore");
+}
+
+int is_hfs_dotgitattributes(const char *path)
+{
+       return is_hfs_dot_str(path, "gitattributes");
+}
+
 const char utf8_bom[] = "\357\273\277";
 
 int skip_utf8_bom(char **text, size_t len)
diff --git a/utf8.h b/utf8.h
index 6bbcf31..da19b43 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -52,8 +52,13 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
  * The path should be NUL-terminated, but we will match variants of both ".git\0"
  * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
  * and verify_path().
+ *
+ * Likewise, the is_hfs_dotgitfoo() variants look for ".gitfoo".
  */
 int is_hfs_dotgit(const char *path);
+int is_hfs_dotgitmodules(const char *path);
+int is_hfs_dotgitignore(const char *path);
+int is_hfs_dotgitattributes(const char *path);
 
 typedef enum {
        ALIGN_LEFT,