Merge branch 'js/t6026-clean-up'
authorJunio C Hamano <gitster@pobox.com>
Mon, 12 Sep 2016 22:34:37 +0000 (15:34 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 12 Sep 2016 22:34:37 +0000 (15:34 -0700)
A test spawned a short-lived background process, which sometimes
prevented the test directory from getting removed at the end of the
script on some platforms.

* js/t6026-clean-up:
t6026-merge-attr: clean up background process at end of test case

78 files changed:
Documentation/RelNotes/2.11.0.txt [new file with mode: 0644]
Documentation/RelNotes/2.9.4.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-config.txt
Documentation/diff-options.txt
Documentation/git-clone.txt
Documentation/git-index-pack.txt
Documentation/git-receive-pack.txt
Documentation/git-status.txt
Documentation/git-unpack-objects.txt
Documentation/gitk.txt
Documentation/gitrevisions.txt
Documentation/pretty-formats.txt
Documentation/rev-list-options.txt
Documentation/revisions.txt
GIT-VERSION-GEN
Makefile
RelNotes
builtin/am.c
builtin/clone.c
builtin/commit.c
builtin/help.c
builtin/index-pack.c
builtin/log.c
builtin/receive-pack.c
builtin/rev-list.c
builtin/submodule--helper.c
builtin/symbolic-ref.c
builtin/unpack-objects.c
cache.h
color.c
compat/nedmalloc/nedmalloc.c
compat/strdup.c [new file with mode: 0644]
contrib/diff-highlight/Makefile [new file with mode: 0644]
contrib/diff-highlight/diff-highlight
contrib/diff-highlight/t/.gitignore [new file with mode: 0644]
contrib/diff-highlight/t/Makefile [new file with mode: 0644]
contrib/diff-highlight/t/t9400-diff-highlight.sh [new file with mode: 0755]
diff.c
diff.h
git-compat-util.h
git-submodule.sh
git.c
graph.c
graph.h
hex.c
log-tree.c
path.c
pkt-line.c
pretty.c
ref-filter.c
refs/files-backend.c
sha1_file.c
submodule.c
submodule.h
t/perf/p0003-delta-base-cache.sh [new file with mode: 0755]
t/t0012-help.sh [new file with mode: 0755]
t/t1401-symbolic-ref.sh
t/t4013-diff-various.sh
t/t4013/diff.diff_--line-prefix=abc_master_master^_side [new file with mode: 0644]
t/t4013/diff.diff_--line-prefix_--cached_--_file0 [new file with mode: 0644]
t/t4021-format-patch-numbered.sh
t/t4059-diff-submodule-not-initialized.sh [new file with mode: 0755]
t/t4060-diff-submodule-option-diff-format.sh [new file with mode: 0755]
t/t4202-log.sh
t/t5526-fetch-submodules.sh
t/t5546-receive-limits.sh [new file with mode: 0755]
t/t7060-wtstatus.sh
t/t7064-wtstatus-pv2.sh [new file with mode: 0755]
t/t7408-submodule-reference.sh
t/t9903-bash-prompt.sh
t/test-lib-functions.sh
t/test-lib.sh
url.c
usage.c
wt-status.c
wt-status.h
diff --git a/Documentation/RelNotes/2.11.0.txt b/Documentation/RelNotes/2.11.0.txt
new file mode 100644 (file)
index 0000000..6e8a5d5
--- /dev/null
@@ -0,0 +1,56 @@
+Git 2.11 Release Notes
+======================
+
+Updates since v2.10
+-------------------
+
+UI, Workflows & Features
+
+ * "git format-patch --cover-letter HEAD^" to format a single patch
+   with a separate cover letter now numbers the output as [PATCH 0/1]
+   and [PATCH 1/1] by default.
+
+ * An incoming "git push" that attempts to push too many bytes can now
+   be rejected by setting a new configuration variable at the receiving
+   end.
+
+ * "git nosuchcommand --help" said "No manual entry for gitnosuchcommand",
+   which was not intuitive, given that "git nosuchcommand" said "git:
+   'nosuchcommand' is not a git command".
+
+ * "git clone --resurse-submodules --reference $path $URL" is a way to
+   reduce network transfer cost by borrowing objects in an existing
+   $path repository when cloning the superproject from $URL; it
+   learned to also peek into $path for presense of corresponding
+   repositories of submodules and borrow objects from there when able.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The delta-base-cache mechanism has been a key to the performance in
+   a repository with a tightly packed packfile, but it did not scale
+   well even with a larger value of core.deltaBaseCacheLimit.
+
+ * Enhance "git status --porcelain" output by collecting more data on
+   the state of the index and the working tree files, which may
+   further be used to teach git-prompt (in contrib/) to make fewer
+   calls to git.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.10
+-----------------
+
+Unless otherwise noted, all the fixes since v2.9 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * Clarify various ways to specify the "revision ranges" in the
+   documentation.
+   (merge a117be4 po/range-doc later to maint).
+
+ * "diff-highlight" script (in contrib/) learned to work better with
+   "git log -p --graph" output.
+   (merge 3dbfe2b bh/diff-highlight-graph later to maint).
diff --git a/Documentation/RelNotes/2.9.4.txt b/Documentation/RelNotes/2.9.4.txt
new file mode 100644 (file)
index 0000000..01e8642
--- /dev/null
@@ -0,0 +1,83 @@
+Git v2.9.4 Release Notes
+========================
+
+Fixes since v2.9.3
+------------------
+
+ * There are certain house-keeping tasks that need to be performed at
+   the very beginning of any Git program, and programs that are not
+   built-in commands had to do them exactly the same way as "git"
+   potty does.  It was easy to make mistakes in one-off standalone
+   programs (like test helpers).  A common "main()" function that
+   calls cmd_main() of individual program has been introduced to
+   make it harder to make mistakes.
+
+ * "git merge" with renormalization did not work well with
+   merge-recursive, due to "safer crlf" conversion kicking in when it
+   shouldn't.
+
+ * The reflog output format is documented better, and a new format
+   --date=unix to report the seconds-since-epoch (without timezone)
+   has been added.
+
+ * "git push --force-with-lease" already had enough logic to allow
+   ensuring that such a push results in creation of a ref (i.e. the
+   receiving end did not have another push from sideways that would be
+   discarded by our force-pushing), but didn't expose this possibility
+   to the users.  It does so now.
+
+ * "import-tars" fast-import script (in contrib/) used to ignore a
+   hardlink target and replaced it with an empty file, which has been
+   corrected to record the same blob as the other file the hardlink is
+   shared with.
+
+ * "git mv dir non-existing-dir/" did not work in some environments
+   the same way as existing mainstream platforms.  The code now moves
+   "dir" to "non-existing-dir", without relying on rename("A", "B/")
+   that strips the trailing slash of '/'.
+
+ * The "t/" hierarchy is prone to get an unusual pathname; "make test"
+   has been taught to make sure they do not contain paths that cannot
+   be checked out on Windows (and the mechanism can be reusable to
+   catch pathnames that are not portable to other platforms as need
+   arises).
+
+ * When "git merge-recursive" works on history with many criss-cross
+   merges in "verbose" mode, the names the command assigns to the
+   virtual merge bases could have overwritten each other by unintended
+   reuse of the same piece of memory.
+
+ * "git checkout --detach <branch>" used to give the same advice
+   message as that is issued when "git checkout <tag>" (or anything
+   that is not a branch name) is given, but asking with "--detach" is
+   an explicit enough sign that the user knows what is going on.  The
+   advice message has been squelched in this case.
+
+ * "git difftool" by default ignores the error exit from the backend
+   commands it spawns, because often they signal that they found
+   differences by exiting with a non-zero status code just like "diff"
+   does; the exit status codes 126 and above however are special in
+   that they are used to signal that the command is not executable,
+   does not exist, or killed by a signal.  "git difftool" has been
+   taught to notice these exit status codes.
+
+ * On Windows, help.browser configuration variable used to be ignored,
+   which has been corrected.
+
+ * The "git -c var[=val] cmd" facility to append a configuration
+   variable definition at the end of the search order was described in
+   git(1) manual page, but not in git-config(1), which was more likely
+   place for people to look for when they ask "can I make a one-shot
+   override, and if so how?"
+
+ * The tempfile (hence its user lockfile) API lets the caller to open
+   a file descriptor to a temporary file, write into it and then
+   finalize it by first closing the filehandle and then either
+   removing or renaming the temporary file.  When the process spawns a
+   subprocess after obtaining the file descriptor, and if the
+   subprocess has not exited when the attempt to remove or rename is
+   made, the last step fails on Windows, because the subprocess has
+   the file descriptor still open.  Open tempfile with O_CLOEXEC flag
+   to avoid this (on Windows, this is mapped to O_NOINHERIT).
+
+Also contains minor documentation updates and code clean-ups.
index 500230c054be697d507cfa8e800e657803e6ab0d..08352deaae4763791b70bd172682c1fe5380f7b4 100644 (file)
@@ -122,9 +122,14 @@ without external resources. Instead of giving a URL to a mailing list
 archive, summarize the relevant points of the discussion.
 
 If you want to reference a previous commit in the history of a stable
-branch use the format "abbreviated sha1 (subject, date)". So for example
-like this: "Commit f86a374 (pack-bitmap.c: fix a memleak, 2015-03-30)
-noticed [...]".
+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 ...
+
+The "Copy commit summary" command of gitk can be used to obtain this
+format.
 
 
 (3) Generate your patch using Git tools out of your commits.
index 0bcb6790d6efbb2a812bc90b6f64e323cb3ec393..32f065ca6a9382ba7b496d9c6606a72941dc0c1f 100644 (file)
@@ -2517,6 +2517,12 @@ receive.unpackLimit::
        especially on slow filesystems.  If not set, the value of
        `transfer.unpackLimit` is used instead.
 
+receive.maxInputSize::
+       If the size of the incoming pack stream is larger than this
+       limit, then git-receive-pack will error out, instead of
+       accepting the pack file. If not set or set to 0, then the size
+       is unlimited.
+
 receive.denyDeletes::
        If set to true, git-receive-pack will deny a ref update that deletes
        the ref. Use this to prevent such a ref deletion via a push.
@@ -2847,6 +2853,18 @@ submodule.fetchJobs::
        in parallel. A value of 0 will give some reasonable default.
        If unset, it defaults to 1.
 
+submodule.alternateLocation::
+       Specifies how the submodules obtain alternates when submodules are
+       cloned. Possible values are `no`, `superproject`.
+       By default `no` is assumed, which doesn't add references. When the
+       value is set to `superproject` the submodule to be cloned computes
+       its alternates location relative to the superprojects alternate.
+
+submodule.alternateErrorStrategy
+       Specifies how to treat errors with the alternates for a submodule
+       as computed via `submodule.alternateLocation`. Possible values are
+       `ignore`, `info`, `die`. Default is `die`.
+
 tag.forceSignAnnotated::
        A boolean to specify whether annotated tags created should be GPG signed.
        If `--annotate` is specified on the command line, it takes
index d5a5b17d508839bc379821ee1a318400a3557fe9..0eded24034b57a511425f60c92b5d9119e8950fa 100644 (file)
@@ -122,10 +122,11 @@ diff.suppressBlankEmpty::
 
 diff.submodule::
        Specify the format in which differences in submodules are
-       shown.  The "log" format lists the commits in the range like
-       linkgit:git-submodule[1] `summary` does.  The "short" format
-       format just shows the names of the commits at the beginning
-       and end of the range.  Defaults to short.
+       shown.  The "short" format just shows the names of the commits
+       at the beginning and end of the range. The "log" format lists
+       the commits in the range like linkgit:git-submodule[1] `summary`
+       does. The "diff" format shows an inline diff of the changed
+       contents of the submodule. Defaults to "short".
 
 diff.wordRegex::
        A POSIX Extended Regular Expression used to determine what is a "word"
index 705a8739420067d433817c0f855c5cb4ee99559b..7805a0ccadf27e4fea46dcd95796c570e893e6b3 100644 (file)
@@ -210,13 +210,16 @@ any of those replacements occurred.
        of the `--diff-filter` option on what the status letters mean.
 
 --submodule[=<format>]::
-       Specify how differences in submodules are shown.  When `--submodule`
-       or `--submodule=log` is given, the 'log' format is used.  This format lists
-       the commits in the range like linkgit:git-submodule[1] `summary` does.
-       Omitting the `--submodule` option or specifying `--submodule=short`,
-       uses the 'short' format. This format just shows the names of the commits
-       at the beginning and end of the range.  Can be tweaked via the
-       `diff.submodule` configuration variable.
+       Specify how differences in submodules are shown.  When specifying
+       `--submodule=short` the 'short' format is used.  This format just
+       shows the names of the commits at the beginning and end of the range.
+       When `--submodule` or `--submodule=log` is specified, the 'log'
+       format is used.  This format lists the commits in the range like
+       linkgit:git-submodule[1] `summary` does.  When `--submodule=diff`
+       is specified, the 'diff' format is used.  This format shows an
+       inline diff of the changes in the submodule contents between the
+       commit range.  Defaults to `diff.submodule` or the 'short' format
+       if the config option is unset.
 
 --color[=<when>]::
        Show colored diff.
@@ -569,5 +572,8 @@ endif::git-format-patch[]
 --no-prefix::
        Do not show any source or destination prefix.
 
+--line-prefix=<prefix>::
+       Prepend an additional prefix to every line of output.
+
 For more detailed explanation on these common options, see also
 linkgit:gitdiffcore[7].
index ec41d3d698a1bcffac3b3a21489b61cc74f9d8d0..e316c4bd51a64fb9df87ce003f2edffad38b63ad 100644 (file)
@@ -90,13 +90,16 @@ If you want to break the dependency of a repository cloned with `-s` on
 its source repository, you can simply run `git repack -a` to copy all
 objects from the source repository into a pack in the cloned repository.
 
---reference <repository>::
+--reference[-if-able] <repository>::
        If the reference repository is on the local machine,
        automatically setup `.git/objects/info/alternates` to
        obtain objects from the reference repository.  Using
        an already existing repository as an alternate will
        require fewer objects to be copied from the repository
        being cloned, reducing network and local storage costs.
+       When using the `--reference-if-able`, a non existing
+       directory is skipped with a warning instead of aborting
+       the clone.
 +
 *NOTE*: see the NOTE for the `--shared` option, and also the
 `--dissociate` option.
index 7a4e0555205be5d8bb26e5a183f1552cc057637a..1b4b65d6657b003079e0407d1da8d8c239933267 100644 (file)
@@ -87,6 +87,8 @@ OPTIONS
        Specifying 0 will cause Git to auto-detect the number of CPU's
        and use maximum 3 threads.
 
+--max-input-size=<size>::
+       Die, if the pack is larger than <size>.
 
 Note
 ----
index 000ee8dba2ab3069e0459defe3df9d7140541a59..0ccd5fbc781deb3adcca4d28ec8a4ed0d9db9977 100644 (file)
@@ -33,6 +33,9 @@ post-update hooks found in the Documentation/howto directory.
 option, which tells it if updates to a ref should be denied if they
 are not fast-forwards.
 
+A number of other receive.* config options are available to tweak
+its behavior, see linkgit:git-config[1].
+
 OPTIONS
 -------
 <directory>::
index e1e8f57cdd217b43b9b04bc54381e9b155d9cbde..725065ef2d7b6b3c7c6c29a434a8996a7e1ae559 100644 (file)
@@ -32,11 +32,14 @@ OPTIONS
 --branch::
        Show the branch and tracking info even in short-format.
 
---porcelain::
+--porcelain[=<version>]::
        Give the output in an easy-to-parse format for scripts.
        This is similar to the short output, but will remain stable
        across Git versions and regardless of user configuration. See
        below for details.
++
+The version parameter is used to specify the format version.
+This is optional and defaults to the original version 'v1' format.
 
 --long::
        Give the output in the long-format. This is the default.
@@ -96,7 +99,7 @@ configuration variable documented in linkgit:git-config[1].
 
 -z::
        Terminate entries with NUL, instead of LF.  This implies
-       the `--porcelain` output format if no other format is given.
+       the `--porcelain=v1` output format if no other format is given.
 
 --column[=<options>]::
 --no-column::
@@ -180,12 +183,12 @@ in which case `XY` are `!!`.
 
 If -b is used the short-format status is preceded by a line
 
-## branchname tracking info
+    ## branchname tracking info
 
-Porcelain Format
-~~~~~~~~~~~~~~~~
+Porcelain Format Version 1
+~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The porcelain format is similar to the short format, but is guaranteed
+Version 1 porcelain format is similar to the short format, but is guaranteed
 not to change in a backwards-incompatible way between Git versions or
 based on user configuration. This makes it ideal for parsing by scripts.
 The description of the short format above also describes the porcelain
@@ -207,6 +210,124 @@ field from the first filename).  Third, filenames containing special
 characters are not specially formatted; no quoting or
 backslash-escaping is performed.
 
+Porcelain Format Version 2
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Version 2 format adds more detailed information about the state of
+the worktree and changed items.  Version 2 also defines an extensible
+set of easy to parse optional headers.
+
+Header lines start with "#" and are added in response to specific
+command line arguments.  Parsers should ignore headers they
+don't recognize.
+
+### Branch Headers
+
+If `--branch` is given, a series of header lines are printed with
+information about the current branch.
+
+    Line                                     Notes
+    ------------------------------------------------------------
+    # branch.oid <commit> | (initial)        Current commit.
+    # branch.head <branch> | (detached)      Current branch.
+    # branch.upstream <upstream_branch>      If upstream is set.
+    # branch.ab +<ahead> -<behind>           If upstream is set and
+                                            the commit is present.
+    ------------------------------------------------------------
+
+### Changed Tracked Entries
+
+Following the headers, a series of lines are printed for tracked
+entries.  One of three different line formats may be used to describe
+an entry depending on the type of change.  Tracked entries are printed
+in an undefined order; parsers should allow for a mixture of the 3
+line types in any order.
+
+Ordinary changed entries have the following format:
+
+    1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
+
+Renamed or copied entries have the following format:
+
+    2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
+
+    Field       Meaning
+    --------------------------------------------------------
+    <XY>        A 2 character field containing the staged and
+               unstaged XY values described in the short format,
+               with unchanged indicated by a "." rather than
+               a space.
+    <sub>       A 4 character field describing the submodule state.
+               "N..." when the entry is not a submodule.
+               "S<c><m><u>" when the entry is a submodule.
+               <c> is "C" if the commit changed; otherwise ".".
+               <m> is "M" if it has tracked changes; otherwise ".".
+               <u> is "U" if there are untracked changes; otherwise ".".
+    <mH>        The octal file mode in HEAD.
+    <mI>        The octal file mode in the index.
+    <mW>        The octal file mode in the worktree.
+    <hH>        The object name in HEAD.
+    <hI>        The object name in the index.
+    <X><score>  The rename or copy score (denoting the percentage
+               of similarity between the source and target of the
+               move or copy). For example "R100" or "C75".
+    <path>      The pathname.  In a renamed/copied entry, this
+               is the path in the index and in the working tree.
+    <sep>       When the `-z` option is used, the 2 pathnames are separated
+               with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
+               byte separates them.
+    <origPath>  The pathname in the commit at HEAD.  This is only
+               present in a renamed/copied entry, and tells
+               where the renamed/copied contents came from.
+    --------------------------------------------------------
+
+Unmerged entries have the following format; the first character is
+a "u" to distinguish from ordinary changed entries.
+
+    u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
+
+    Field       Meaning
+    --------------------------------------------------------
+    <XY>        A 2 character field describing the conflict type
+               as described in the short format.
+    <sub>       A 4 character field describing the submodule state
+               as described above.
+    <m1>        The octal file mode in stage 1.
+    <m2>        The octal file mode in stage 2.
+    <m3>        The octal file mode in stage 3.
+    <mW>        The octal file mode in the worktree.
+    <h1>        The object name in stage 1.
+    <h2>        The object name in stage 2.
+    <h3>        The object name in stage 3.
+    <path>      The pathname.
+    --------------------------------------------------------
+
+### Other Items
+
+Following the tracked entries (and if requested), a series of
+lines will be printed for untracked and then ignored items
+found in the worktree.
+
+Untracked items have the following format:
+
+    ? <path>
+
+Ignored items have the following format:
+
+    ! <path>
+
+### Pathname Format Notes and -z
+
+When the `-z` option is given, pathnames are printed as is and
+without any quoting and lines are terminated with a NUL (ASCII 0x00)
+byte.
+
+Otherwise, all pathnames will be "C-quoted" if they contain any tab,
+linefeed, double quote, or backslash characters. In C-quoting, these
+characters will be replaced with the corresponding C-style escape
+sequences and the resulting pathname will be double quoted.
+
+
 CONFIGURATION
 -------------
 
index 3e887d16109c48f4f906d8a7c5c0c860bbd6123d..b3de50d7106819f4dee92b3508c7713d170832ce 100644 (file)
@@ -44,6 +44,9 @@ OPTIONS
 --strict::
        Don't write objects with broken content or links.
 
+--max-input-size=<size>::
+       Die, if the pack is larger than <size>.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index a68d860fa3e9b63bf8ae76b93011bd9b49ecda25..e382dd96dfded769a27c2295ebc096c15214713b 100644 (file)
@@ -70,7 +70,7 @@ linkgit:git-rev-list[1] for a complete list.
 
 --left-right::
 
-       Mark which side of a symmetric diff a commit is reachable
+       Mark which side of a symmetric difference a commit is reachable
        from.  Commits from the left side are prefixed with a `<`
        symbol and those from the right with a `>` symbol.
 
index e903eb786049b4742e9d7c766b8448a503cbb6a3..27dec5b91d08bbdb114400dd3c86a8cf515e8774 100644 (file)
@@ -15,9 +15,9 @@ DESCRIPTION
 
 Many Git commands take revision parameters as arguments. Depending on
 the command, they denote a specific commit or, for commands which
-walk the revision graph (such as linkgit:git-log[1]), all commits which can
-be reached from that commit. In the latter case one can also specify a
-range of revisions explicitly.
+walk the revision graph (such as linkgit:git-log[1]), all commits which are
+reachable from that commit. For commands that walk the revision graph one can
+also specify a range of revisions explicitly.
 
 In addition, some Git commands (such as linkgit:git-show[1]) also take
 revision parameters which denote other objects than commits, e.g. blobs
index b95d67ec01f130417af9ff0d7bd0d2377008146c..a942d57f73a8502903b888d65d09a677816a5f0f 100644 (file)
@@ -172,7 +172,7 @@ endif::git-rev-list[]
   respecting the `auto` settings of the former if we are going to a
   terminal). `auto` alone (i.e. `%C(auto)`) will turn on auto coloring
   on the next placeholders until the color is switched again.
-- '%m': left, right or boundary mark
+- '%m': left (`<`), right (`>`) or boundary (`-`) mark
 - '%n': newline
 - '%%': a raw '%'
 - '%x00': print a byte from a hex code
index a779c9dfec0ed962e7274577b1c53daa0ecff1d9..7e462d3841daa1e7c11af224a98c6d7cf0b801a4 100644 (file)
@@ -225,7 +225,7 @@ excluded from the output.
 
 --left-only::
 --right-only::
-       List only commits on the respective side of a symmetric range,
+       List only commits on the respective side of a symmetric difference,
        i.e. only those which would be marked `<` resp. `>` by
        `--left-right`.
 +
@@ -796,7 +796,7 @@ ifdef::git-rev-list[]
 endif::git-rev-list[]
 
 --left-right::
-       Mark which side of a symmetric diff a commit is reachable from.
+       Mark which side of a symmetric difference a commit is reachable from.
        Commits from the left side are prefixed with `<` and those from
        the right with `>`.  If combined with `--boundary`, those
        commits are prefixed with `-`.
index abae36398351491db4bab57db6363e3597ade79d..4bed5b1ab741d9c86f5a9443f980080a56b84629 100644 (file)
@@ -237,48 +237,74 @@ SPECIFYING RANGES
 -----------------
 
 History traversing commands such as `git log` operate on a set
-of commits, not just a single commit.  To these commands,
-specifying a single revision with the notation described in the
-previous section means the set of commits reachable from that
-commit, following the commit ancestry chain.
-
-To exclude commits reachable from a commit, a prefix '{caret}'
-notation is used.  E.g. '{caret}r1 r2' means commits reachable
-from 'r2' but exclude the ones reachable from 'r1'.
-
-This set operation appears so often that there is a shorthand
-for it.  When you have two commits 'r1' and 'r2' (named according
-to the syntax explained in SPECIFYING REVISIONS above), you can ask
-for commits that are reachable from r2 excluding those that are reachable
-from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'.
-
-A similar notation 'r1\...r2' is called symmetric difference
-of 'r1' and 'r2' and is defined as
-'r1 r2 --not $(git merge-base --all r1 r2)'.
-It is the set of commits that are reachable from either one of
-'r1' or 'r2' but not from both.
-
-In these two shorthands, you can omit one end and let it default to HEAD.
+of commits, not just a single commit.
+
+For these commands,
+specifying a single revision, using the notation described in the
+previous section, means the set of commits `reachable` from the given
+commit.
+
+A commit's reachable set is the commit itself and the commits in
+its ancestry chain.
+
+
+Commit Exclusions
+~~~~~~~~~~~~~~~~~
+
+'{caret}<rev>' (caret) Notation::
+ To exclude commits reachable from a commit, a prefix '{caret}'
+ notation is used.  E.g. '{caret}r1 r2' means commits reachable
+ from 'r2' but exclude the ones reachable from 'r1' (i.e. 'r1' and
+ its ancestors).
+
+Dotted Range Notations
+~~~~~~~~~~~~~~~~~~~~~~
+
+The '..' (two-dot) Range Notation::
+ The '{caret}r1 r2' set operation appears so often that there is a shorthand
+ for it.  When you have two commits 'r1' and 'r2' (named according
+ to the syntax explained in SPECIFYING REVISIONS above), you can ask
+ for commits that are reachable from r2 excluding those that are reachable
+ from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'.
+
+The '...' (three dot) Symmetric Difference Notation::
+ A similar notation 'r1\...r2' is called symmetric difference
+ of 'r1' and 'r2' and is defined as
+ 'r1 r2 --not $(git merge-base --all r1 r2)'.
+ It is the set of commits that are reachable from either one of
+ 'r1' (left side) or 'r2' (right side) but not from both.
+
+In these two shorthand notations, you can omit one end and let it default to HEAD.
 For example, 'origin..' is a shorthand for 'origin..HEAD' and asks "What
 did I do since I forked from the origin branch?"  Similarly, '..origin'
 is a shorthand for 'HEAD..origin' and asks "What did the origin do since
 I forked from them?"  Note that '..' would mean 'HEAD..HEAD' which is an
 empty range that is both reachable and unreachable from HEAD.
 
-Two other shorthands for naming a set that is formed by a commit
-and its parent commits exist.  The 'r1{caret}@' notation means all
-parents of 'r1'.  'r1{caret}!' includes commit 'r1' but excludes
-all of its parents.
+Other <rev>{caret} Parent Shorthand Notations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Two other shorthands exist, particularly useful for merge commits,
+for naming a set that is formed by a commit and its parent commits.
+
+The 'r1{caret}@' notation means all parents of 'r1'.
+
+The 'r1{caret}!' notation includes commit 'r1' but excludes all of its parents.
+By itself, this notation denotes the single commit 'r1'.
+
+While '<rev>{caret}<n>' was about specifying a single commit parent, these
+two notations consider all its parents. For example you can say
+'HEAD{caret}2{caret}@', however you cannot say 'HEAD{caret}@{caret}2'.
 
-To summarize:
+Revision Range Summary
+----------------------
 
 '<rev>'::
-       Include commits that are reachable from (i.e. ancestors of)
-       <rev>.
+       Include commits that are reachable from <rev> (i.e. <rev> and its
+       ancestors).
 
 '{caret}<rev>'::
-       Exclude commits that are reachable from (i.e. ancestors of)
-       <rev>.
+       Exclude commits that are reachable from <rev> (i.e. <rev> and its
+       ancestors).
 
 '<rev1>..<rev2>'::
        Include commits that are reachable from <rev2> but exclude
@@ -300,16 +326,27 @@ To summarize:
   as giving commit '<rev>' and then all its parents prefixed with
   '{caret}' to exclude them (and their ancestors).
 
-Here are a handful of examples:
-
-   D                G H D
-   D F              G H I J D F
-   ^G D             H D
-   ^D B             E I J F B
-   B..C             C
-   B...C            G H D E B C
-   ^D B C           E I J F B C
-   C                I J F C
-   C^@              I J F
-   C^!              C
-   F^! D            G H D F
+Here are a handful of examples using the Loeliger illustration above,
+with each step in the notation's expansion and selection carefully
+spelt out:
+
+   Args   Expanded arguments    Selected commits
+   D                            G H D
+   D F                          G H I J D F
+   ^G D                         H D
+   ^D B                         E I J F B
+   ^D B C                       E I J F B C
+   C                            I J F C
+   B..C   = ^B C                C
+   B...C  = B ^F C              G H D E B C
+   C^@    = C^1
+         = F                   I J F
+   B^@    = B^1 B^2 B^3
+         = D E F               D G H E F I J
+   C^!    = C ^C^@
+         = C ^C^1
+         = C ^F                C
+   B^!    = B ^B^@
+         = B ^B^1 ^B^2 ^B^3
+         = B ^D ^E ^F          B
+   F^! D  = F ^I ^J D           G H D F
index 6754ab076e649f0d728230869afcc45bcf6a4b3d..55e88b02d43e6972fc813ac840cfec39ba555362 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.10.0
+DEF_VER=v2.10.0.GIT
 
 LF='
 '
index d96ecb7141a12f1fe47246805c5d4e4e562a5c86..7f184923ddc6fa5089b64ced3cdca405758468e1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -296,6 +296,11 @@ all::
 # Define USE_NED_ALLOCATOR if you want to replace the platforms default
 # memory allocators with the nedmalloc allocator written by Niall Douglas.
 #
+# Define OVERRIDE_STRDUP to override the libc version of strdup(3).
+# This is necessary when using a custom allocator in order to avoid
+# crashes due to allocation and free working on different 'heaps'.
+# It's defined automatically if USE_NED_ALLOCATOR is set.
+#
 # Define NO_REGEX if you have no or inferior regex support in your C library.
 #
 # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
@@ -1456,8 +1461,14 @@ ifdef NATIVE_CRLF
 endif
 
 ifdef USE_NED_ALLOCATOR
-       COMPAT_CFLAGS += -Icompat/nedmalloc
-       COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
+       COMPAT_CFLAGS += -Icompat/nedmalloc
+       COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
+       OVERRIDE_STRDUP = YesPlease
+endif
+
+ifdef OVERRIDE_STRDUP
+       COMPAT_CFLAGS += -DOVERRIDE_STRDUP
+       COMPAT_OBJS += compat/strdup.o
 endif
 
 ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
@@ -2029,7 +2040,7 @@ endif
 
 ifdef USE_NED_ALLOCATOR
 compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
-       -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
+       -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR
 compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null
 endif
 
index 62615ffa4e97803da96aefbc798ab50f949a8db7..b54330f7cdb37630dc47d6bdb71e847b8a947c17 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.10.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.11.0.txt
\ No newline at end of file
index 739b34dcf25a1355246bed436ec7fdb4723d7ca7..b36d1f047d8124e3aeab5fabb24dd2bb2a003fdf 100644 (file)
@@ -28,6 +28,7 @@
 #include "rerere.h"
 #include "prompt.h"
 #include "mailinfo.h"
+#include "string-list.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -258,38 +259,29 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state,
 }
 
 /**
- * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE
- * as a newly-allocated string. VALUE must be a quoted string, and the KEY must
- * match `key`. Returns NULL on failure.
- *
- * This is used by read_author_script() to read the GIT_AUTHOR_* variables from
- * the author-script.
+ * Take a series of KEY='VALUE' lines where VALUE part is
+ * sq-quoted, and append <KEY, VALUE> at the end of the string list
  */
-static char *read_shell_var(FILE *fp, const char *key)
+static int parse_key_value_squoted(char *buf, struct string_list *list)
 {
-       struct strbuf sb = STRBUF_INIT;
-       const char *str;
-
-       if (strbuf_getline_lf(&sb, fp))
-               goto fail;
-
-       if (!skip_prefix(sb.buf, key, &str))
-               goto fail;
-
-       if (!skip_prefix(str, "=", &str))
-               goto fail;
-
-       strbuf_remove(&sb, 0, str - sb.buf);
-
-       str = sq_dequote(sb.buf);
-       if (!str)
-               goto fail;
-
-       return strbuf_detach(&sb, NULL);
-
-fail:
-       strbuf_release(&sb);
-       return NULL;
+       while (*buf) {
+               struct string_list_item *item;
+               char *np;
+               char *cp = strchr(buf, '=');
+               if (!cp)
+                       return -1;
+               np = strchrnul(cp, '\n');
+               *cp++ = '\0';
+               item = string_list_append(list, buf);
+
+               buf = np + (*np == '\n');
+               *np = '\0';
+               cp = sq_dequote(cp);
+               if (!cp)
+                       return -1;
+               item->util = xstrdup(cp);
+       }
+       return 0;
 }
 
 /**
@@ -311,44 +303,39 @@ static char *read_shell_var(FILE *fp, const char *key)
 static int read_author_script(struct am_state *state)
 {
        const char *filename = am_path(state, "author-script");
-       FILE *fp;
+       struct strbuf buf = STRBUF_INIT;
+       struct string_list kv = STRING_LIST_INIT_DUP;
+       int retval = -1; /* assume failure */
+       int fd;
 
        assert(!state->author_name);
        assert(!state->author_email);
        assert(!state->author_date);
 
-       fp = fopen(filename, "r");
-       if (!fp) {
+       fd = open(filename, O_RDONLY);
+       if (fd < 0) {
                if (errno == ENOENT)
                        return 0;
                die_errno(_("could not open '%s' for reading"), filename);
        }
+       strbuf_read(&buf, fd, 0);
+       close(fd);
+       if (parse_key_value_squoted(buf.buf, &kv))
+               goto finish;
 
-       state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME");
-       if (!state->author_name) {
-               fclose(fp);
-               return -1;
-       }
-
-       state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL");
-       if (!state->author_email) {
-               fclose(fp);
-               return -1;
-       }
-
-       state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE");
-       if (!state->author_date) {
-               fclose(fp);
-               return -1;
-       }
-
-       if (fgetc(fp) != EOF) {
-               fclose(fp);
-               return -1;
-       }
-
-       fclose(fp);
-       return 0;
+       if (kv.nr != 3 ||
+           strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") ||
+           strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") ||
+           strcmp(kv.items[2].string, "GIT_AUTHOR_DATE"))
+               goto finish;
+       state->author_name = kv.items[0].util;
+       state->author_email = kv.items[1].util;
+       state->author_date = kv.items[2].util;
+       retval = 0;
+finish:
+       string_list_clear(&kv, !!retval);
+       strbuf_release(&buf);
+       return retval;
 }
 
 /**
index f044a8c27f542c94c0b7f2458de1590e0d02fae2..404c5e80226c81a8f051e63fa8e40286fea95a18 100644 (file)
@@ -50,7 +50,8 @@ static int option_verbosity;
 static int option_progress = -1;
 static enum transport_family family;
 static struct string_list option_config = STRING_LIST_INIT_NODUP;
-static struct string_list option_reference = STRING_LIST_INIT_NODUP;
+static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
+static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
 static int option_dissociate;
 static int max_jobs = -1;
 
@@ -79,8 +80,10 @@ static struct option builtin_clone_options[] = {
                    N_("number of submodules cloned in parallel")),
        OPT_STRING(0, "template", &option_template, N_("template-directory"),
                   N_("directory from which templates will be used")),
-       OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"),
+       OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"),
                        N_("reference repository")),
+       OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference,
+                       N_("repo"), N_("reference repository")),
        OPT_BOOL(0, "dissociate", &option_dissociate,
                 N_("use --reference only while cloning")),
        OPT_STRING('o', "origin", &option_origin, N_("name"),
@@ -282,50 +285,37 @@ static void strip_trailing_slashes(char *dir)
 
 static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
-       char *ref_git;
-       const char *repo;
-       struct strbuf alternate = STRBUF_INIT;
-
-       /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
-       ref_git = xstrdup(real_path(item->string));
-
-       repo = read_gitfile(ref_git);
-       if (!repo)
-               repo = read_gitfile(mkpath("%s/.git", ref_git));
-       if (repo) {
-               free(ref_git);
-               ref_git = xstrdup(repo);
-       }
+       struct strbuf err = STRBUF_INIT;
+       int *required = cb_data;
+       char *ref_git = compute_alternate_path(item->string, &err);
 
-       if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
-               char *ref_git_git = mkpathdup("%s/.git", ref_git);
-               free(ref_git);
-               ref_git = ref_git_git;
-       } else if (!is_directory(mkpath("%s/objects", ref_git))) {
+       if (!ref_git) {
+               if (*required)
+                       die("%s", err.buf);
+               else
+                       fprintf(stderr,
+                               _("info: Could not add alternate for '%s': %s\n"),
+                               item->string, err.buf);
+       } else {
                struct strbuf sb = STRBUF_INIT;
-               if (get_common_dir(&sb, ref_git))
-                       die(_("reference repository '%s' as a linked checkout is not supported yet."),
-                           item->string);
-               die(_("reference repository '%s' is not a local repository."),
-                   item->string);
+               strbuf_addf(&sb, "%s/objects", ref_git);
+               add_to_alternates_file(sb.buf);
+               strbuf_release(&sb);
        }
 
-       if (!access(mkpath("%s/shallow", ref_git), F_OK))
-               die(_("reference repository '%s' is shallow"), item->string);
-
-       if (!access(mkpath("%s/info/grafts", ref_git), F_OK))
-               die(_("reference repository '%s' is grafted"), item->string);
-
-       strbuf_addf(&alternate, "%s/objects", ref_git);
-       add_to_alternates_file(alternate.buf);
-       strbuf_release(&alternate);
+       strbuf_release(&err);
        free(ref_git);
        return 0;
 }
 
 static void setup_reference(void)
 {
-       for_each_string_list(&option_reference, add_one_reference, NULL);
+       int required = 1;
+       for_each_string_list(&option_required_reference,
+                            add_one_reference, &required);
+       required = 0;
+       for_each_string_list(&option_optional_reference,
+                            add_one_reference, &required);
 }
 
 static void copy_alternates(struct strbuf *src, struct strbuf *dst,
@@ -957,6 +947,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                else
                        fprintf(stderr, _("Cloning into '%s'...\n"), dir);
        }
+
+       if (option_recursive) {
+               if (option_required_reference.nr &&
+                   option_optional_reference.nr)
+                       die(_("clone --recursive is not compatible with "
+                             "both --reference and --reference-if-able"));
+               else if (option_required_reference.nr) {
+                       string_list_append(&option_config,
+                               "submodule.alternateLocation=superproject");
+                       string_list_append(&option_config,
+                               "submodule.alternateErrorStrategy=die");
+               } else if (option_optional_reference.nr) {
+                       string_list_append(&option_config,
+                               "submodule.alternateLocation=superproject");
+                       string_list_append(&option_config,
+                               "submodule.alternateErrorStrategy=info");
+               }
+       }
+
        init_db(option_template, INIT_DB_QUIET);
        write_config(&option_config);
 
@@ -977,7 +986,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
 
-       if (option_reference.nr)
+       if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
 
        fetch_pattern = value.buf;
index 77e3dc849419e697abe8f2f4c7d753cce17634a8..bb9f79b6ef4b5cd01cafac551bb6665a433334aa 100644 (file)
@@ -142,14 +142,24 @@ static int show_ignored_in_status, have_option_m;
 static const char *only_include_assumed;
 static struct strbuf message = STRBUF_INIT;
 
-static enum status_format {
-       STATUS_FORMAT_NONE = 0,
-       STATUS_FORMAT_LONG,
-       STATUS_FORMAT_SHORT,
-       STATUS_FORMAT_PORCELAIN,
+static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
-       STATUS_FORMAT_UNSPECIFIED
-} status_format = STATUS_FORMAT_UNSPECIFIED;
+static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
+{
+       enum wt_status_format *value = (enum wt_status_format *)opt->value;
+       if (unset)
+               *value = STATUS_FORMAT_NONE;
+       else if (!arg)
+               *value = STATUS_FORMAT_PORCELAIN;
+       else if (!strcmp(arg, "v1") || !strcmp(arg, "1"))
+               *value = STATUS_FORMAT_PORCELAIN;
+       else if (!strcmp(arg, "v2") || !strcmp(arg, "2"))
+               *value = STATUS_FORMAT_PORCELAIN_V2;
+       else
+               die("unsupported porcelain version '%s'", arg);
+
+       return 0;
+}
 
 static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 {
@@ -500,24 +510,13 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        s->fp = fp;
        s->nowarn = nowarn;
        s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+       if (!s->is_initial)
+               hashcpy(s->sha1_commit, sha1);
+       s->status_format = status_format;
+       s->ignore_submodule_arg = ignore_submodule_arg;
 
        wt_status_collect(s);
-
-       switch (status_format) {
-       case STATUS_FORMAT_SHORT:
-               wt_shortstatus_print(s);
-               break;
-       case STATUS_FORMAT_PORCELAIN:
-               wt_porcelain_print(s);
-               break;
-       case STATUS_FORMAT_UNSPECIFIED:
-               die("BUG: finalize_deferred_config() should have been called");
-               break;
-       case STATUS_FORMAT_NONE:
-       case STATUS_FORMAT_LONG:
-               wt_status_print(s);
-               break;
-       }
+       wt_status_print(s);
 
        return s->commitable;
 }
@@ -1099,7 +1098,7 @@ static const char *read_commit_message(const char *name)
  * is not in effect here.
  */
 static struct status_deferred_config {
-       enum status_format status_format;
+       enum wt_status_format status_format;
        int show_branch;
 } status_deferred_config = {
        STATUS_FORMAT_UNSPECIFIED,
@@ -1109,6 +1108,7 @@ static struct status_deferred_config {
 static void finalize_deferred_config(struct wt_status *s)
 {
        int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
+                                  status_format != STATUS_FORMAT_PORCELAIN_V2 &&
                                   !s->null_termination);
 
        if (s->null_termination) {
@@ -1336,9 +1336,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                            N_("show status concisely"), STATUS_FORMAT_SHORT),
                OPT_BOOL('b', "branch", &s.show_branch,
                         N_("show branch information")),
-               OPT_SET_INT(0, "porcelain", &status_format,
-                           N_("machine-readable output"),
-                           STATUS_FORMAT_PORCELAIN),
+               { OPTION_CALLBACK, 0, "porcelain", &status_format,
+                 N_("version"), N_("machine-readable output"),
+                 PARSE_OPT_OPTARG, opt_parse_porcelain },
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
@@ -1380,7 +1380,13 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        fd = hold_locked_index(&index_lock, 0);
 
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+       if (!s.is_initial)
+               hashcpy(s.sha1_commit, sha1);
+
        s.ignore_submodule_arg = ignore_submodule_arg;
+       s.status_format = status_format;
+       s.verbose = verbose;
+
        wt_status_collect(&s);
 
        if (0 <= fd)
@@ -1389,23 +1395,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        if (s.relative_paths)
                s.prefix = prefix;
 
-       switch (status_format) {
-       case STATUS_FORMAT_SHORT:
-               wt_shortstatus_print(&s);
-               break;
-       case STATUS_FORMAT_PORCELAIN:
-               wt_porcelain_print(&s);
-               break;
-       case STATUS_FORMAT_UNSPECIFIED:
-               die("BUG: finalize_deferred_config() should have been called");
-               break;
-       case STATUS_FORMAT_NONE:
-       case STATUS_FORMAT_LONG:
-               s.verbose = verbose;
-               s.ignore_submodule_arg = ignore_submodule_arg;
-               wt_status_print(&s);
-               break;
-       }
+       wt_status_print(&s);
        return 0;
 }
 
index e8f79d7af54bce18e8d503dea182146da0fbd456..49f7a07f85db1e36d959749adf2eb1962940fb19 100644 (file)
@@ -37,8 +37,10 @@ static int show_all = 0;
 static int show_guides = 0;
 static unsigned int colopts;
 static enum help_format help_format = HELP_FORMAT_NONE;
+static int exclude_guides;
 static struct option builtin_help_options[] = {
        OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
+       OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
        OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
        OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
        OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
@@ -426,10 +428,29 @@ static void list_common_guides_help(void)
        putchar('\n');
 }
 
+static const char *check_git_cmd(const char* cmd)
+{
+       char *alias;
+
+       if (is_git_command(cmd))
+               return cmd;
+
+       alias = alias_lookup(cmd);
+       if (alias) {
+               printf_ln(_("`git %s' is aliased to `%s'"), cmd, alias);
+               free(alias);
+               exit(0);
+       }
+
+       if (exclude_guides)
+               return help_unknown_cmd(cmd);
+
+       return cmd;
+}
+
 int cmd_help(int argc, const char **argv, const char *prefix)
 {
        int nongit;
-       char *alias;
        enum help_format parsed_help_format;
 
        argc = parse_options(argc, argv, prefix, builtin_help_options,
@@ -469,12 +490,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        if (help_format == HELP_FORMAT_NONE)
                help_format = parse_help_format(DEFAULT_HELP_FORMAT);
 
-       alias = alias_lookup(argv[0]);
-       if (alias && !is_git_command(argv[0])) {
-               printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
-               free(alias);
-               return 0;
-       }
+       argv[0] = check_git_cmd(argv[0]);
 
        switch (help_format) {
        case HELP_FORMAT_NONE:
index 1d2ea583a45507935ec2007d6c44f5e968a1aed5..4a8b4aebbac7d3992082b4440957f11aeffc39f5 100644 (file)
@@ -87,6 +87,7 @@ static struct progress *progress;
 static unsigned char input_buffer[4096];
 static unsigned int input_offset, input_len;
 static off_t consumed_bytes;
+static off_t max_input_size;
 static unsigned deepest_delta;
 static git_SHA_CTX input_ctx;
 static uint32_t input_crc32;
@@ -297,6 +298,8 @@ static void use(int bytes)
        if (signed_add_overflows(consumed_bytes, bytes))
                die(_("pack too large for current definition of off_t"));
        consumed_bytes += bytes;
+       if (max_input_size && consumed_bytes > max_input_size)
+               die(_("pack exceeds maximum allowed size"));
 }
 
 static const char *open_pack_file(const char *pack_name)
@@ -1714,6 +1717,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                        opts.off32_limit = strtoul(c+1, &c, 0);
                                if (*c || opts.off32_limit & 0x80000000)
                                        die(_("bad %s"), arg);
+                       } else if (skip_prefix(arg, "--max-input-size=", &arg)) {
+                               max_input_size = strtoumax(arg, NULL, 10);
                        } else
                                usage(index_pack_usage);
                        continue;
index 92dc34dcb0cc54df27b99c91d08786b6c0d5b62a..49aa534f4a018aaaa650a9dc27718f8ed2f00c6e 100644 (file)
@@ -1676,16 +1676,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                /* nothing to do */
                return 0;
        total = nr;
-       if (!keep_subject && auto_number && total > 1)
-               numbered = 1;
-       if (numbered)
-               rev.total = total + start_number - 1;
        if (cover_letter == -1) {
                if (config_cover_letter == COVER_AUTO)
                        cover_letter = (total > 1);
                else
                        cover_letter = (config_cover_letter == COVER_ON);
        }
+       if (!keep_subject && auto_number && (total > 1 || cover_letter))
+               numbered = 1;
+       if (numbered)
+               rev.total = total + start_number - 1;
 
        if (!signature) {
                ; /* --no-signature inhibits all signatures */
index 011db00d31709408a21b97abfb5742b97b54238d..f1ce05ce282361d8c49b1a447db90c13d5186674 100644 (file)
@@ -46,6 +46,7 @@ static int transfer_unpack_limit = -1;
 static int advertise_atomic_push = 1;
 static int advertise_push_options;
 static int unpack_limit = 100;
+static off_t max_input_size;
 static int report_status;
 static int use_sideband;
 static int use_atomic;
@@ -212,6 +213,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "receive.maxinputsize") == 0) {
+               max_input_size = git_config_int64(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -1648,6 +1654,9 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                if (fsck_objects)
                        argv_array_pushf(&child.args, "--strict%s",
                                fsck_msg_types.buf);
+               if (max_input_size)
+                       argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
+                               (uintmax_t)max_input_size);
                child.no_stdout = 1;
                child.err = err_fd;
                child.git_cmd = 1;
@@ -1676,6 +1685,9 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                                fsck_msg_types.buf);
                if (!reject_thin)
                        argv_array_push(&child.args, "--fix-thin");
+               if (max_input_size)
+                       argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
+                               (uintmax_t)max_input_size);
                child.out = -1;
                child.err = err_fd;
                child.git_cmd = 1;
index 0ba82b1635b6380d9a7d9fd9a31471b8c8ac9f20..8479f6ed28aa75d01350fc17eafbded0b52dca84 100644 (file)
@@ -122,48 +122,40 @@ static void show_commit(struct commit *commit, void *data)
                ctx.fmt = revs->commit_format;
                ctx.output_encoding = get_log_output_encoding();
                pretty_print_commit(&ctx, commit, &buf);
-               if (revs->graph) {
-                       if (buf.len) {
-                               if (revs->commit_format != CMIT_FMT_ONELINE)
-                                       graph_show_oneline(revs->graph);
-
-                               graph_show_commit_msg(revs->graph, &buf);
-
-                               /*
-                                * Add a newline after the commit message.
-                                *
-                                * Usually, this newline produces a blank
-                                * padding line between entries, in which case
-                                * we need to add graph padding on this line.
-                                *
-                                * However, the commit message may not end in a
-                                * newline.  In this case the newline simply
-                                * ends the last line of the commit message,
-                                * and we don't need any graph output.  (This
-                                * always happens with CMIT_FMT_ONELINE, and it
-                                * happens with CMIT_FMT_USERFORMAT when the
-                                * format doesn't explicitly end in a newline.)
-                                */
-                               if (buf.len && buf.buf[buf.len - 1] == '\n')
-                                       graph_show_padding(revs->graph);
-                               putchar('\n');
-                       } else {
-                               /*
-                                * If the message buffer is empty, just show
-                                * the rest of the graph output for this
-                                * commit.
-                                */
-                               if (graph_show_remainder(revs->graph))
-                                       putchar('\n');
-                               if (revs->commit_format == CMIT_FMT_ONELINE)
-                                       putchar('\n');
-                       }
+               if (buf.len) {
+                       if (revs->commit_format != CMIT_FMT_ONELINE)
+                               graph_show_oneline(revs->graph);
+
+                       graph_show_commit_msg(revs->graph, stdout, &buf);
+
+                       /*
+                        * Add a newline after the commit message.
+                        *
+                        * Usually, this newline produces a blank
+                        * padding line between entries, in which case
+                        * we need to add graph padding on this line.
+                        *
+                        * However, the commit message may not end in a
+                        * newline.  In this case the newline simply
+                        * ends the last line of the commit message,
+                        * and we don't need any graph output.  (This
+                        * always happens with CMIT_FMT_ONELINE, and it
+                        * happens with CMIT_FMT_USERFORMAT when the
+                        * format doesn't explicitly end in a newline.)
+                        */
+                       if (buf.len && buf.buf[buf.len - 1] == '\n')
+                               graph_show_padding(revs->graph);
+                       putchar('\n');
                } else {
-                       if (revs->commit_format != CMIT_FMT_USERFORMAT ||
-                           buf.len) {
-                               fwrite(buf.buf, 1, buf.len, stdout);
-                               putchar(info->hdr_termination);
-                       }
+                       /*
+                        * If the message buffer is empty, just show
+                        * the rest of the graph output for this
+                        * commit.
+                        */
+                       if (graph_show_remainder(revs->graph))
+                               putchar('\n');
+                       if (revs->commit_format == CMIT_FMT_ONELINE)
+                               putchar('\n');
                }
                strbuf_release(&buf);
        } else {
index e79790f0bdc9f19cb9d9773d1a9bdb10044bbb8d..9d79f1994a43efed3883ccb80898292a687c4d76 100644 (file)
@@ -442,7 +442,7 @@ static int module_name(int argc, const char **argv, const char *prefix)
 }
 
 static int clone_submodule(const char *path, const char *gitdir, const char *url,
-                          const char *depth, const char *reference, int quiet)
+                          const char *depth, struct string_list *reference, int quiet)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
 
@@ -452,8 +452,12 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
                argv_array_push(&cp.args, "--quiet");
        if (depth && *depth)
                argv_array_pushl(&cp.args, "--depth", depth, NULL);
-       if (reference && *reference)
-               argv_array_pushl(&cp.args, "--reference", reference, NULL);
+       if (reference->nr) {
+               struct string_list_item *item;
+               for_each_string_list_item(item, reference)
+                       argv_array_pushl(&cp.args, "--reference",
+                                        item->string, NULL);
+       }
        if (gitdir && *gitdir)
                argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
 
@@ -467,15 +471,114 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
        return run_command(&cp);
 }
 
+struct submodule_alternate_setup {
+       const char *submodule_name;
+       enum SUBMODULE_ALTERNATE_ERROR_MODE {
+               SUBMODULE_ALTERNATE_ERROR_DIE,
+               SUBMODULE_ALTERNATE_ERROR_INFO,
+               SUBMODULE_ALTERNATE_ERROR_IGNORE
+       } error_mode;
+       struct string_list *reference;
+};
+#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
+       SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
+
+static int add_possible_reference_from_superproject(
+               struct alternate_object_database *alt, void *sas_cb)
+{
+       struct submodule_alternate_setup *sas = sas_cb;
+
+       /* directory name, minus trailing slash */
+       size_t namelen = alt->name - alt->base - 1;
+       struct strbuf name = STRBUF_INIT;
+       strbuf_add(&name, alt->base, namelen);
+
+       /*
+        * If the alternate object store is another repository, try the
+        * standard layout with .git/modules/<name>/objects
+        */
+       if (ends_with(name.buf, ".git/objects")) {
+               char *sm_alternate;
+               struct strbuf sb = STRBUF_INIT;
+               struct strbuf err = STRBUF_INIT;
+               strbuf_add(&sb, name.buf, name.len - strlen("objects"));
+               /*
+                * We need to end the new path with '/' to mark it as a dir,
+                * otherwise a submodule name containing '/' will be broken
+                * as the last part of a missing submodule reference would
+                * be taken as a file name.
+                */
+               strbuf_addf(&sb, "modules/%s/", sas->submodule_name);
+
+               sm_alternate = compute_alternate_path(sb.buf, &err);
+               if (sm_alternate) {
+                       string_list_append(sas->reference, xstrdup(sb.buf));
+                       free(sm_alternate);
+               } else {
+                       switch (sas->error_mode) {
+                       case SUBMODULE_ALTERNATE_ERROR_DIE:
+                               die(_("submodule '%s' cannot add alternate: %s"),
+                                   sas->submodule_name, err.buf);
+                       case SUBMODULE_ALTERNATE_ERROR_INFO:
+                               fprintf(stderr, _("submodule '%s' cannot add alternate: %s"),
+                                       sas->submodule_name, err.buf);
+                       case SUBMODULE_ALTERNATE_ERROR_IGNORE:
+                               ; /* nothing */
+                       }
+               }
+               strbuf_release(&sb);
+       }
+
+       strbuf_release(&name);
+       return 0;
+}
+
+static void prepare_possible_alternates(const char *sm_name,
+               struct string_list *reference)
+{
+       char *sm_alternate = NULL, *error_strategy = NULL;
+       struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT;
+
+       git_config_get_string("submodule.alternateLocation", &sm_alternate);
+       if (!sm_alternate)
+               return;
+
+       git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
+
+       if (!error_strategy)
+               error_strategy = xstrdup("die");
+
+       sas.submodule_name = sm_name;
+       sas.reference = reference;
+       if (!strcmp(error_strategy, "die"))
+               sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE;
+       else if (!strcmp(error_strategy, "info"))
+               sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO;
+       else if (!strcmp(error_strategy, "ignore"))
+               sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE;
+       else
+               die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy);
+
+       if (!strcmp(sm_alternate, "superproject"))
+               foreach_alt_odb(add_possible_reference_from_superproject, &sas);
+       else if (!strcmp(sm_alternate, "no"))
+               ; /* do nothing */
+       else
+               die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate);
+
+       free(sm_alternate);
+       free(error_strategy);
+}
+
 static int module_clone(int argc, const char **argv, const char *prefix)
 {
-       const char *name = NULL, *url = NULL;
-       const char *reference = NULL, *depth = NULL;
+       const char *name = NULL, *url = NULL, *depth = NULL;
        int quiet = 0;
        FILE *submodule_dot_git;
        char *p, *path = NULL, *sm_gitdir;
        struct strbuf rel_path = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
+       struct string_list reference = STRING_LIST_INIT_NODUP;
 
        struct option module_clone_options[] = {
                OPT_STRING(0, "prefix", &prefix,
@@ -490,8 +593,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                OPT_STRING(0, "url", &url,
                           N_("string"),
                           N_("url where to clone the submodule from")),
-               OPT_STRING(0, "reference", &reference,
-                          N_("string"),
+               OPT_STRING_LIST(0, "reference", &reference,
+                          N_("repo"),
                           N_("reference repository")),
                OPT_STRING(0, "depth", &depth,
                           N_("string"),
@@ -527,7 +630,10 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        if (!file_exists(sm_gitdir)) {
                if (safe_create_leading_directories_const(sm_gitdir) < 0)
                        die(_("could not create directory '%s'"), sm_gitdir);
-               if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet))
+
+               prepare_possible_alternates(name, &reference);
+
+               if (clone_submodule(path, sm_gitdir, url, depth, &reference, quiet))
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
        } else {
@@ -579,7 +685,7 @@ struct submodule_update_clone {
        /* configuration parameters which are passed on to the children */
        int quiet;
        int recommend_shallow;
-       const char *reference;
+       struct string_list references;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
@@ -595,7 +701,8 @@ struct submodule_update_clone {
        int failed_clones_nr, failed_clones_alloc;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
-       SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, NULL, NULL, NULL, NULL, \
+       SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, STRING_LIST_INIT_DUP, \
+       NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
 
 
@@ -705,8 +812,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
        argv_array_pushl(&child->args, "--path", sub->path, NULL);
        argv_array_pushl(&child->args, "--name", sub->name, NULL);
        argv_array_pushl(&child->args, "--url", url, NULL);
-       if (suc->reference)
-               argv_array_push(&child->args, suc->reference);
+       if (suc->references.nr) {
+               struct string_list_item *item;
+               for_each_string_list_item(item, &suc->references)
+                       argv_array_pushl(&child->args, "--reference", item->string, NULL);
+       }
        if (suc->depth)
                argv_array_push(&child->args, suc->depth);
 
@@ -829,7 +939,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
                OPT_STRING(0, "update", &update,
                           N_("string"),
                           N_("rebase, merge, checkout or none")),
-               OPT_STRING(0, "reference", &suc.reference, N_("repo"),
+               OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
                           N_("reference repository")),
                OPT_STRING(0, "depth", &suc.depth, "<depth>",
                           N_("Create a shallow clone truncated to the "
index 9c29a64e4331e1e45b4467829ba950006b673818..96eed944683a45c33fa5d4a05414ae56406fafca 100644 (file)
@@ -56,6 +56,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                ret = check_symref(argv[0], 1, 0, 0);
                if (ret)
                        die("Cannot delete %s, not a symbolic ref", argv[0]);
+               if (!strcmp(argv[0], "HEAD"))
+                       die("deleting '%s' is not allowed", argv[0]);
                return delete_ref(argv[0], NULL, REF_NODEREF);
        }
 
index 172470bf241e4da731c5741567555d7e01691c74..4532aa083154ae7e9be07014d3761821fa6c4ddc 100644 (file)
@@ -19,6 +19,7 @@ static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict]
 static unsigned char buffer[4096];
 static unsigned int offset, len;
 static off_t consumed_bytes;
+static off_t max_input_size;
 static git_SHA_CTX ctx;
 static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
 
@@ -87,6 +88,8 @@ static void use(int bytes)
        if (signed_add_overflows(consumed_bytes, bytes))
                die("pack too large for current definition of off_t");
        consumed_bytes += bytes;
+       if (max_input_size && consumed_bytes > max_input_size)
+               die(_("pack exceeds maximum allowed size"));
 }
 
 static void *get_data(unsigned long size)
@@ -550,6 +553,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
                                len = sizeof(*hdr);
                                continue;
                        }
+                       if (skip_prefix(arg, "--max-input-size=", &arg)) {
+                               max_input_size = strtoumax(arg, NULL, 10);
+                               continue;
+                       }
                        usage(unpack_usage);
                }
 
diff --git a/cache.h b/cache.h
index b780a91a567143ca2cd1283dd441dcc0e00673d4..35563266bf54c49cb9e2ae1efbed7a26fbca3519 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -819,8 +819,8 @@ extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
-extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
-                                     const char *fmt, ...)
+extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path,
+                                    const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
@@ -953,22 +953,39 @@ static inline void oidclr(struct object_id *oid)
 #define EMPTY_TREE_SHA1_BIN_LITERAL \
         "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
         "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
-#define EMPTY_TREE_SHA1_BIN \
-        ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
+extern const struct object_id empty_tree_oid;
+#define EMPTY_TREE_SHA1_BIN (empty_tree_oid.hash)
 
 #define EMPTY_BLOB_SHA1_HEX \
        "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
 #define EMPTY_BLOB_SHA1_BIN_LITERAL \
        "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
        "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
-#define EMPTY_BLOB_SHA1_BIN \
-       ((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
+extern const struct object_id empty_blob_oid;
+#define EMPTY_BLOB_SHA1_BIN (empty_blob_oid.hash)
+
 
 static inline int is_empty_blob_sha1(const unsigned char *sha1)
 {
        return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
 }
 
+static inline int is_empty_blob_oid(const struct object_id *oid)
+{
+       return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN);
+}
+
+static inline int is_empty_tree_sha1(const unsigned char *sha1)
+{
+       return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN);
+}
+
+static inline int is_empty_tree_oid(const struct object_id *oid)
+{
+       return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
+}
+
+
 int git_mkstemp(char *path, size_t n, const char *template);
 
 /* set default permissions by passing mode arguments to open(2) */
@@ -1139,6 +1156,16 @@ static inline unsigned int hexval(unsigned char c)
        return hexval_table[c];
 }
 
+/*
+ * Convert two consecutive hexadecimal digits into a char.  Return a
+ * negative value on error.  Don't run over the end of short strings.
+ */
+static inline int hex2chr(const char *s)
+{
+       int val = hexval(s[0]);
+       return (val < 0) ? val : (val << 4) | hexval(s[1]);
+}
+
 /* Convert to/from hex/sha1 representation */
 #define MINIMUM_ABBREV minimum_abbrev
 #define DEFAULT_ABBREV default_abbrev
@@ -1344,6 +1371,7 @@ extern struct alternate_object_database {
 } *alt_odb_list;
 extern void prepare_alt_odb(void);
 extern void read_info_alternates(const char * relative_base, int depth);
+extern char *compute_alternate_path(const char *path, struct strbuf *err);
 extern void add_to_alternates_file(const char *reference);
 typedef int alt_odb_fn(struct alternate_object_database *, void *);
 extern int foreach_alt_odb(alt_odb_fn, void*);
diff --git a/color.c b/color.c
index 81c26767239f9e028057c1843f57b77640db6c26..1b95e6b2a7bb1601fa5862de8989775e197c7b41 100644 (file)
--- a/color.c
+++ b/color.c
@@ -215,7 +215,7 @@ int color_parse_mem(const char *value, int value_len, char *dst)
        /* [fg [bg]] [attr]... */
        while (len > 0) {
                const char *word = ptr;
-               struct color c;
+               struct color c = { COLOR_UNSPECIFIED };
                int val, wordlen = 0;
 
                while (len > 0 && !isspace(word[wordlen])) {
index 2d4ef59013823d3c7caac8b705fbd4916cfe8fd6..1cc31c350223b84cd97939afccf1bcc1caa070b4 100644 (file)
@@ -948,22 +948,6 @@ void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **
        return ret;
 }
 
-#ifdef OVERRIDE_STRDUP
-/*
- * This implementation is purely there to override the libc version, to
- * avoid a crash due to allocation and free on different 'heaps'.
- */
-char *strdup(const char *s1)
-{
-       size_t len = strlen(s1) + 1;
-       char *s2 = malloc(len);
-
-       if (s2)
-               memcpy(s2, s1, len);
-       return s2;
-}
-#endif
-
 #if defined(__cplusplus)
 }
 #endif
diff --git a/compat/strdup.c b/compat/strdup.c
new file mode 100644 (file)
index 0000000..f3fb978
--- /dev/null
@@ -0,0 +1,11 @@
+#include "../git-compat-util.h"
+
+char *gitstrdup(const char *s1)
+{
+       size_t len = strlen(s1) + 1;
+       char *s2 = malloc(len);
+
+       if (s2)
+               memcpy(s2, s1, len);
+       return s2;
+}
diff --git a/contrib/diff-highlight/Makefile b/contrib/diff-highlight/Makefile
new file mode 100644 (file)
index 0000000..9018724
--- /dev/null
@@ -0,0 +1,5 @@
+# nothing to build
+all:
+
+test:
+       $(MAKE) -C t
index ffefc31a98a26dfd65d188bbb547cc114231c13d..81bd8040e3cbd34a8b247f23a94eee891fc44296 100755 (executable)
@@ -21,6 +21,10 @@ my $RESET = "\x1b[m";
 my $COLOR = qr/\x1b\[[0-9;]*m/;
 my $BORING = qr/$COLOR|\s/;
 
+# The patch portion of git log -p --graph should only ever have preceding | and
+# not / or \ as merge history only shows up on the commit line.
+my $GRAPH = qr/$COLOR?\|$COLOR?\s+/;
+
 my @removed;
 my @added;
 my $in_hunk;
@@ -32,12 +36,12 @@ $SIG{PIPE} = 'DEFAULT';
 while (<>) {
        if (!$in_hunk) {
                print;
-               $in_hunk = /^$COLOR*\@/;
+               $in_hunk = /^$GRAPH*$COLOR*\@\@ /;
        }
-       elsif (/^$COLOR*-/) {
+       elsif (/^$GRAPH*$COLOR*-/) {
                push @removed, $_;
        }
-       elsif (/^$COLOR*\+/) {
+       elsif (/^$GRAPH*$COLOR*\+/) {
                push @added, $_;
        }
        else {
@@ -46,7 +50,7 @@ while (<>) {
                @added = ();
 
                print;
-               $in_hunk = /^$COLOR*[\@ ]/;
+               $in_hunk = /^$GRAPH*$COLOR*[\@ ]/;
        }
 
        # Most of the time there is enough output to keep things streaming,
@@ -163,6 +167,9 @@ sub highlight_pair {
        }
 }
 
+# we split either by $COLOR or by character. This has the side effect of
+# leaving in graph cruft. It works because the graph cruft does not contain "-"
+# or "+"
 sub split_line {
        local $_ = shift;
        return utf8::decode($_) ?
@@ -211,8 +218,8 @@ sub is_pair_interesting {
        my $suffix_a = join('', @$a[($sa+1)..$#$a]);
        my $suffix_b = join('', @$b[($sb+1)..$#$b]);
 
-       return $prefix_a !~ /^$COLOR*-$BORING*$/ ||
-              $prefix_b !~ /^$COLOR*\+$BORING*$/ ||
+       return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ ||
+              $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ ||
               $suffix_a !~ /^$BORING*$/ ||
               $suffix_b !~ /^$BORING*$/;
 }
diff --git a/contrib/diff-highlight/t/.gitignore b/contrib/diff-highlight/t/.gitignore
new file mode 100644 (file)
index 0000000..7dcbb23
--- /dev/null
@@ -0,0 +1,2 @@
+/trash directory*
+/test-results
diff --git a/contrib/diff-highlight/t/Makefile b/contrib/diff-highlight/t/Makefile
new file mode 100644 (file)
index 0000000..5ff5275
--- /dev/null
@@ -0,0 +1,22 @@
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+# copied from ../../t/Makefile
+SHELL_PATH ?= $(SHELL)
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: test
+test: $(T)
+
+.PHONY: help clean all test $(T)
+
+help:
+       @echo 'Run "$(MAKE) test" to launch test scripts'
+       @echo 'Run "$(MAKE) clean" to remove trash folders'
+
+$(T):
+       @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+
+clean:
+       $(RM) -r 'trash directory'.*
diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh
new file mode 100755 (executable)
index 0000000..3b43dbe
--- /dev/null
@@ -0,0 +1,296 @@
+#!/bin/sh
+
+test_description='Test diff-highlight'
+
+CURR_DIR=$(pwd)
+TEST_OUTPUT_DIRECTORY=$(pwd)
+TEST_DIRECTORY="$CURR_DIR"/../../../t
+DIFF_HIGHLIGHT="$CURR_DIR"/../diff-highlight
+
+CW="$(printf "\033[7m")"       # white
+CR="$(printf "\033[27m")"      # reset
+
+. "$TEST_DIRECTORY"/test-lib.sh
+
+if ! test_have_prereq PERL
+then
+       skip_all='skipping diff-highlight tests; perl not available'
+       test_done
+fi
+
+# dh_test is a test helper function which takes 3 file names as parameters. The
+# first 2 files are used to generate diff and commit output, which is then
+# piped through diff-highlight. The 3rd file should contain the expected output
+# of diff-highlight (minus the diff/commit header, ie. everything after and
+# including the first @@ line).
+dh_test () {
+       a="$1" b="$2" &&
+
+       cat >patch.exp &&
+
+       {
+               cat "$a" >file &&
+               git add file &&
+               git commit -m "Add a file" &&
+
+               cat "$b" >file &&
+               git diff file >diff.raw &&
+               git commit -a -m "Update a file" &&
+               git show >commit.raw
+       } >/dev/null &&
+
+       "$DIFF_HIGHLIGHT" <diff.raw | test_strip_patch_header >diff.act &&
+       "$DIFF_HIGHLIGHT" <commit.raw | test_strip_patch_header >commit.act &&
+       test_cmp patch.exp diff.act &&
+       test_cmp patch.exp commit.act
+}
+
+test_strip_patch_header () {
+       sed -n '/^@@/,$p' $*
+}
+
+# dh_test_setup_history generates a contrived graph such that we have at least
+# 1 nesting (E) and 2 nestings (F).
+#
+#            A branch
+#           /
+#      D---E---F master
+#
+#      git log --all --graph
+#      * commit
+#      |    A
+#      | * commit
+#      | |    F
+#      | * commit
+#      |/
+#      |    E
+#      * commit
+#           D
+#
+dh_test_setup_history () {
+       echo "file1" >file1 &&
+       echo "file2" >file2 &&
+       echo "file3" >file3 &&
+
+       cat file1 >file &&
+       git add file &&
+       git commit -m "D" &&
+
+       git checkout -b branch &&
+       cat file2 >file &&
+       git commit -a -m "A" &&
+
+       git checkout master &&
+       cat file2 >file &&
+       git commit -a -m "E" &&
+
+       cat file3 >file &&
+       git commit -a -m "F"
+}
+
+left_trim () {
+       "$PERL_PATH" -pe 's/^\s+//'
+}
+
+trim_graph () {
+       # graphs start with * or |
+       # followed by a space or / or \
+       "$PERL_PATH" -pe 's@^((\*|\|)( |/|\\))+@@'
+}
+
+test_expect_success 'diff-highlight highlights the beginning of a line' '
+       cat >a <<-\EOF &&
+               aaa
+               bbb
+               ccc
+       EOF
+
+       cat >b <<-\EOF &&
+               aaa
+               0bb
+               ccc
+       EOF
+
+       dh_test a b <<-EOF
+               @@ -1,3 +1,3 @@
+                aaa
+               -${CW}b${CR}bb
+               +${CW}0${CR}bb
+                ccc
+       EOF
+'
+
+test_expect_success 'diff-highlight highlights the end of a line' '
+       cat >a <<-\EOF &&
+               aaa
+               bbb
+               ccc
+       EOF
+
+       cat >b <<-\EOF &&
+               aaa
+               bb0
+               ccc
+       EOF
+
+       dh_test a b <<-EOF
+               @@ -1,3 +1,3 @@
+                aaa
+               -bb${CW}b${CR}
+               +bb${CW}0${CR}
+                ccc
+       EOF
+'
+
+test_expect_success 'diff-highlight highlights the middle of a line' '
+       cat >a <<-\EOF &&
+               aaa
+               bbb
+               ccc
+       EOF
+
+       cat >b <<-\EOF &&
+               aaa
+               b0b
+               ccc
+       EOF
+
+       dh_test a b <<-EOF
+               @@ -1,3 +1,3 @@
+                aaa
+               -b${CW}b${CR}b
+               +b${CW}0${CR}b
+                ccc
+       EOF
+'
+
+test_expect_success 'diff-highlight does not highlight whole line' '
+       cat >a <<-\EOF &&
+               aaa
+               bbb
+               ccc
+       EOF
+
+       cat >b <<-\EOF &&
+               aaa
+               000
+               ccc
+       EOF
+
+       dh_test a b <<-EOF
+               @@ -1,3 +1,3 @@
+                aaa
+               -bbb
+               +000
+                ccc
+       EOF
+'
+
+test_expect_failure 'diff-highlight highlights mismatched hunk size' '
+       cat >a <<-\EOF &&
+               aaa
+               bbb
+       EOF
+
+       cat >b <<-\EOF &&
+               aaa
+               b0b
+               ccc
+       EOF
+
+       dh_test a b <<-EOF
+               @@ -1,3 +1,3 @@
+                aaa
+               -b${CW}b${CR}b
+               +b${CW}0${CR}b
+               +ccc
+       EOF
+'
+
+# These two code points share the same leading byte in UTF-8 representation;
+# a naive byte-wise diff would highlight only the second byte.
+#
+#   - U+00f3 ("o" with acute)
+o_accent=$(printf '\303\263')
+#   - U+00f8 ("o" with stroke)
+o_stroke=$(printf '\303\270')
+
+test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' '
+       echo "unic${o_accent}de" >a &&
+       echo "unic${o_stroke}de" >b &&
+       dh_test a b <<-EOF
+               @@ -1 +1 @@
+               -unic${CW}${o_accent}${CR}de
+               +unic${CW}${o_stroke}${CR}de
+       EOF
+'
+
+# Unlike the UTF-8 above, these are combining code points which are meant
+# to modify the character preceding them:
+#
+#   - U+0301 (combining acute accent)
+combine_accent=$(printf '\314\201')
+#   - U+0302 (combining circumflex)
+combine_circum=$(printf '\314\202')
+
+test_expect_failure 'diff-highlight treats combining code points as a unit' '
+       echo "unico${combine_accent}de" >a &&
+       echo "unico${combine_circum}de" >b &&
+       dh_test a b <<-EOF
+               @@ -1 +1 @@
+               -unic${CW}o${combine_accent}${CR}de
+               +unic${CW}o${combine_circum}${CR}de
+       EOF
+'
+
+test_expect_success 'diff-highlight works with the --graph option' '
+       dh_test_setup_history &&
+
+       # topo-order so that the order of the commits is the same as with --graph
+       # trim graph elements so we can do a diff
+       # trim leading space because our trim_graph is not perfect
+       git log --branches -p --topo-order |
+               "$DIFF_HIGHLIGHT" | left_trim >graph.exp &&
+       git log --branches -p --graph |
+               "$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph.act &&
+       test_cmp graph.exp graph.act
+'
+
+# Most combined diffs won't meet diff-highlight's line-number filter. So we
+# create one here where one side drops a line and the other modifies it. That
+# should result in a diff like:
+#
+#    - modified content
+#    ++resolved content
+#
+# which naively looks like one side added "+resolved".
+test_expect_success 'diff-highlight ignores combined diffs' '
+       echo "content" >file &&
+       git add file &&
+       git commit -m base &&
+
+       >file &&
+       git commit -am master &&
+
+       git checkout -b other HEAD^ &&
+       echo "modified content" >file &&
+       git commit -am other &&
+
+       test_must_fail git merge master &&
+       echo "resolved content" >file &&
+       git commit -am resolved &&
+
+       cat >expect <<-\EOF &&
+       --- a/file
+       +++ b/file
+       @@@ -1,1 -1,0 +1,1 @@@
+       - modified content
+       ++resolved content
+       EOF
+
+       git show -c | "$DIFF_HIGHLIGHT" >actual.raw &&
+       sed -n "/^---/,\$p" <actual.raw >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/diff.c b/diff.c
index 534c12e28ea8b077c9fa305a634f7a73cf30589d..b38d95eb249c8dabc906cf426a7fd33abd37c4f7 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -18,6 +18,7 @@
 #include "ll-merge.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "graph.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -131,9 +132,11 @@ static int parse_dirstat_params(struct diff_options *options, const char *params
 static int parse_submodule_params(struct diff_options *options, const char *value)
 {
        if (!strcmp(value, "log"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (!strcmp(value, "short"))
-               DIFF_OPT_CLR(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_SHORT;
+       else if (!strcmp(value, "diff"))
+               options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
        else
                return -1;
        return 0;
@@ -1625,7 +1628,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
         */
 
        if (options->stat_width == -1)
-               width = term_columns() - options->output_prefix_length;
+               width = term_columns() - strlen(line_prefix);
        else
                width = options->stat_width ? options->stat_width : 80;
        number_width = decimal_width(max_change) > number_width ?
@@ -2299,17 +2302,37 @@ static void builtin_diff(const char *name_a,
        struct strbuf header = STRBUF_INIT;
        const char *line_prefix = diff_line_prefix(o);
 
-       if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
-                       (!one->mode || S_ISGITLINK(one->mode)) &&
-                       (!two->mode || S_ISGITLINK(two->mode))) {
+       diff_set_mnemonic_prefix(o, "a/", "b/");
+       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+               a_prefix = o->b_prefix;
+               b_prefix = o->a_prefix;
+       } else {
+               a_prefix = o->a_prefix;
+               b_prefix = o->b_prefix;
+       }
+
+       if (o->submodule_format == DIFF_SUBMODULE_LOG &&
+           (!one->mode || S_ISGITLINK(one->mode)) &&
+           (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one->path ? one->path : two->path,
                                line_prefix,
-                               one->oid.hash, two->oid.hash,
+                               &one->oid, &two->oid,
                                two->dirty_submodule,
                                meta, del, add, reset);
                return;
+       } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
+                  (!one->mode || S_ISGITLINK(one->mode)) &&
+                  (!two->mode || S_ISGITLINK(two->mode))) {
+               const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
+               const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
+               show_submodule_inline_diff(o->file, one->path ? one->path : two->path,
+                               line_prefix,
+                               &one->oid, &two->oid,
+                               two->dirty_submodule,
+                               meta, del, add, reset, o);
+               return;
        }
 
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
@@ -2317,15 +2340,6 @@ static void builtin_diff(const char *name_a,
                textconv_two = get_textconv(two);
        }
 
-       diff_set_mnemonic_prefix(o, "a/", "b/");
-       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
-               a_prefix = o->b_prefix;
-               b_prefix = o->a_prefix;
-       } else {
-               a_prefix = o->a_prefix;
-               b_prefix = o->b_prefix;
-       }
-
        /* Never use a non-valid filename anywhere if at all possible */
        name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
        name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
@@ -3915,7 +3929,7 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (skip_prefix(arg, "--submodule=", &arg))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
@@ -3966,6 +3980,12 @@ int diff_opt_parse(struct diff_options *options,
                options->a_prefix = optarg;
                return argcount;
        }
+       else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
+               options->line_prefix = optarg;
+               options->line_prefix_length = strlen(options->line_prefix);
+               graph_setup_line_prefix(options);
+               return argcount;
+       }
        else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
                options->b_prefix = optarg;
                return argcount;
diff --git a/diff.h b/diff.h
index 7883729edf10ae2888ebc672f62f6015dee21550..ec76a90522ea88354882666a5a6d336dfa8963fd 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -83,7 +83,6 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
 #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
 #define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
-#define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
 #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
 #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
 #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
@@ -110,11 +109,19 @@ enum diff_words_type {
        DIFF_WORDS_COLOR
 };
 
+enum diff_submodule_format {
+       DIFF_SUBMODULE_SHORT = 0,
+       DIFF_SUBMODULE_LOG,
+       DIFF_SUBMODULE_INLINE_DIFF
+};
+
 struct diff_options {
        const char *orderfile;
        const char *pickaxe;
        const char *single_follow;
        const char *a_prefix, *b_prefix;
+       const char *line_prefix;
+       size_t line_prefix_length;
        unsigned flags;
        unsigned touched_flags;
 
@@ -155,6 +162,7 @@ struct diff_options {
        int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;
+       enum diff_submodule_format submodule_format;
 
        /* this is set by diffcore for DIFF_FORMAT_PATCH */
        int found_changes;
@@ -174,7 +182,6 @@ struct diff_options {
        diff_format_fn_t format_callback;
        void *format_callback_data;
        diff_prefix_fn_t output_prefix;
-       int output_prefix_length;
        void *output_prefix_data;
 
        int diff_path_counter;
index db89ba774891a9608531e52252de19d552cbf535..68615b1a6a4db0f4ba7b4ce543e00e31e3b1705e 100644 (file)
@@ -436,6 +436,7 @@ static inline int const_error(void)
        return -1;
 }
 #define error(...) (error(__VA_ARGS__), const_error())
+#define error_errno(...) (error_errno(__VA_ARGS__), const_error())
 #endif
 
 extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
@@ -663,6 +664,14 @@ void *gitmemmem(const void *haystack, size_t haystacklen,
                 const void *needle, size_t needlelen);
 #endif
 
+#ifdef OVERRIDE_STRDUP
+#ifdef strdup
+#undef strdup
+#endif
+#define strdup gitstrdup
+char *gitstrdup(const char *s);
+#endif
+
 #ifdef NO_GETPAGESIZE
 #define getpagesize() sysconf(_SC_PAGESIZE)
 #endif
index b57f87de658b627c48ec672faaab3dc6aac3b505..a1cc71b521b76c346509207f4ef9a24d48ead229 100755 (executable)
@@ -576,7 +576,7 @@ cmd_update()
                ${wt_prefix:+--prefix "$wt_prefix"} \
                ${prefix:+--recursive-prefix "$prefix"} \
                ${update:+--update "$update"} \
-               ${reference:+--reference "$reference"} \
+               ${reference:+"$reference"} \
                ${depth:+--depth "$depth"} \
                ${recommend_shallow:+"$recommend_shallow"} \
                ${jobs:+$jobs} \
diff --git a/git.c b/git.c
index 0f1937fd0c23da7c316540b8f9a6b05746011506..1c61151758531889e2a2e60e66799283240dc89a 100644 (file)
--- a/git.c
+++ b/git.c
@@ -522,21 +522,34 @@ static void strip_extension(const char **argv)
 
 static void handle_builtin(int argc, const char **argv)
 {
+       struct argv_array args = ARGV_ARRAY_INIT;
        const char *cmd;
        struct cmd_struct *builtin;
 
        strip_extension(argv);
        cmd = argv[0];
 
-       /* Turn "git cmd --help" into "git help cmd" */
+       /* Turn "git cmd --help" into "git help --exclude-guides cmd" */
        if (argc > 1 && !strcmp(argv[1], "--help")) {
+               int i;
+
                argv[1] = argv[0];
                argv[0] = cmd = "help";
+
+               for (i = 0; i < argc; i++) {
+                       argv_array_push(&args, argv[i]);
+                       if (!i)
+                               argv_array_push(&args, "--exclude-guides");
+               }
+
+               argc++;
+               argv = args.argv;
        }
 
        builtin = get_builtin(cmd);
        if (builtin)
                exit(run_builtin(builtin, argc, argv));
+       argv_array_clear(&args);
 }
 
 static void execv_dashed_external(const char **argv)
diff --git a/graph.c b/graph.c
index dd1720148dc51740c96da0f4b17b59b97994abac..06f1139f2e201e1319bece3865ae95060ae2522c 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -2,7 +2,6 @@
 #include "commit.h"
 #include "color.h"
 #include "graph.h"
-#include "diff.h"
 #include "revision.h"
 
 /* Internal API */
@@ -28,8 +27,15 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
  * responsible for printing this line's graph (perhaps via
  * graph_show_commit() or graph_show_oneline()) before calling
  * graph_show_strbuf().
+ *
+ * Note that unlike some other graph display functions, you must pass the file
+ * handle directly. It is assumed that this is the same file handle as the
+ * file specified by the graph diff options. This is necessary so that
+ * graph_show_strbuf can be called even with a NULL graph.
  */
-static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
+static void graph_show_strbuf(struct git_graph *graph,
+                             FILE *file,
+                             struct strbuf const *sb);
 
 /*
  * TODO:
@@ -59,6 +65,17 @@ enum graph_state {
        GRAPH_COLLAPSING
 };
 
+static void graph_show_line_prefix(const struct diff_options *diffopt)
+{
+       if (!diffopt || !diffopt->line_prefix)
+               return;
+
+       fwrite(diffopt->line_prefix,
+              sizeof(char),
+              diffopt->line_prefix_length,
+              diffopt->file);
+}
+
 static const char **column_colors;
 static unsigned short column_colors_max;
 
@@ -195,14 +212,28 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void
        static struct strbuf msgbuf = STRBUF_INIT;
 
        assert(opt);
-       assert(graph);
 
-       opt->output_prefix_length = graph->width;
        strbuf_reset(&msgbuf);
-       graph_padding_line(graph, &msgbuf);
+       if (opt->line_prefix)
+               strbuf_add(&msgbuf, opt->line_prefix,
+                          opt->line_prefix_length);
+       if (graph)
+               graph_padding_line(graph, &msgbuf);
        return &msgbuf;
 }
 
+static const struct diff_options *default_diffopt;
+
+void graph_setup_line_prefix(struct diff_options *diffopt)
+{
+       default_diffopt = diffopt;
+
+       /* setup an output prefix callback if necessary */
+       if (diffopt && !diffopt->output_prefix)
+               diffopt->output_prefix = diff_output_prefix_callback;
+}
+
+
 struct git_graph *graph_init(struct rev_info *opt)
 {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
@@ -245,7 +276,6 @@ struct git_graph *graph_init(struct rev_info *opt)
         */
        opt->diffopt.output_prefix = diff_output_prefix_callback;
        opt->diffopt.output_prefix_data = graph;
-       opt->diffopt.output_prefix_length = 0;
 
        return graph;
 }
@@ -1185,6 +1215,8 @@ void graph_show_commit(struct git_graph *graph)
        struct strbuf msgbuf = STRBUF_INIT;
        int shown_commit_line = 0;
 
+       graph_show_line_prefix(default_diffopt);
+
        if (!graph)
                return;
 
@@ -1202,8 +1234,10 @@ void graph_show_commit(struct git_graph *graph)
                shown_commit_line = graph_next_line(graph, &msgbuf);
                fwrite(msgbuf.buf, sizeof(char), msgbuf.len,
                        graph->revs->diffopt.file);
-               if (!shown_commit_line)
+               if (!shown_commit_line) {
                        putc('\n', graph->revs->diffopt.file);
+                       graph_show_line_prefix(&graph->revs->diffopt);
+               }
                strbuf_setlen(&msgbuf, 0);
        }
 
@@ -1214,6 +1248,8 @@ void graph_show_oneline(struct git_graph *graph)
 {
        struct strbuf msgbuf = STRBUF_INIT;
 
+       graph_show_line_prefix(default_diffopt);
+
        if (!graph)
                return;
 
@@ -1226,6 +1262,8 @@ void graph_show_padding(struct git_graph *graph)
 {
        struct strbuf msgbuf = STRBUF_INIT;
 
+       graph_show_line_prefix(default_diffopt);
+
        if (!graph)
                return;
 
@@ -1239,6 +1277,8 @@ int graph_show_remainder(struct git_graph *graph)
        struct strbuf msgbuf = STRBUF_INIT;
        int shown = 0;
 
+       graph_show_line_prefix(default_diffopt);
+
        if (!graph)
                return 0;
 
@@ -1252,27 +1292,24 @@ int graph_show_remainder(struct git_graph *graph)
                strbuf_setlen(&msgbuf, 0);
                shown = 1;
 
-               if (!graph_is_commit_finished(graph))
+               if (!graph_is_commit_finished(graph)) {
                        putc('\n', graph->revs->diffopt.file);
-               else
+                       graph_show_line_prefix(&graph->revs->diffopt);
+               } else {
                        break;
+               }
        }
        strbuf_release(&msgbuf);
 
        return shown;
 }
 
-
-static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
+static void graph_show_strbuf(struct git_graph *graph,
+                             FILE *file,
+                             struct strbuf const *sb)
 {
        char *p;
 
-       if (!graph) {
-               fwrite(sb->buf, sizeof(char), sb->len,
-                       graph->revs->diffopt.file);
-               return;
-       }
-
        /*
         * Print the strbuf line by line,
         * and display the graph info before each line but the first.
@@ -1287,7 +1324,7 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
                } else {
                        len = (sb->buf + sb->len) - p;
                }
-               fwrite(p, sizeof(char), len, graph->revs->diffopt.file);
+               fwrite(p, sizeof(char), len, file);
                if (next_p && *next_p != '\0')
                        graph_show_oneline(graph);
                p = next_p;
@@ -1295,29 +1332,20 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
 }
 
 void graph_show_commit_msg(struct git_graph *graph,
+                          FILE *file,
                           struct strbuf const *sb)
 {
        int newline_terminated;
 
-       if (!graph) {
-               /*
-                * If there's no graph, just print the message buffer.
-                *
-                * The message buffer for CMIT_FMT_ONELINE and
-                * CMIT_FMT_USERFORMAT are already missing a terminating
-                * newline.  All of the other formats should have it.
-                */
-               fwrite(sb->buf, sizeof(char), sb->len,
-                       graph->revs->diffopt.file);
-               return;
-       }
-
-       newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
-
        /*
         * Show the commit message
         */
-       graph_show_strbuf(graph, sb);
+       graph_show_strbuf(graph, file, sb);
+
+       if (!graph)
+               return;
+
+       newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
 
        /*
         * If there is more output needed for this commit, show it now
@@ -1329,7 +1357,7 @@ void graph_show_commit_msg(struct git_graph *graph,
                 * new line.
                 */
                if (!newline_terminated)
-                       putc('\n', graph->revs->diffopt.file);
+                       putc('\n', file);
 
                graph_show_remainder(graph);
 
@@ -1337,6 +1365,6 @@ void graph_show_commit_msg(struct git_graph *graph,
                 * If sb ends with a newline, our output should too.
                 */
                if (newline_terminated)
-                       putc('\n', graph->revs->diffopt.file);
+                       putc('\n', file);
        }
 }
diff --git a/graph.h b/graph.h
index 3f48c19b6208712233f80c1afae3783951e967a9..af623390b6058d16677bb81223b2038148d07a6d 100644 (file)
--- a/graph.h
+++ b/graph.h
@@ -1,9 +1,22 @@
 #ifndef GRAPH_H
 #define GRAPH_H
+#include "diff.h"
 
 /* A graph is a pointer to this opaque structure */
 struct git_graph;
 
+/*
+ * Called to setup global display of line_prefix diff option.
+ *
+ * Passed a diff_options structure which indicates the line_prefix and the
+ * file to output the prefix to. This is sort of a hack used so that the
+ * line_prefix will be honored by all flows which also honor "--graph"
+ * regardless of whether a graph has actually been setup. The normal graph
+ * flow will honor the exact diff_options passed, but a NULL graph will cause
+ * display of a line_prefix to stdout.
+ */
+void graph_setup_line_prefix(struct diff_options *diffopt);
+
 /*
  * Set up a custom scheme for column colors.
  *
@@ -113,7 +126,14 @@ int graph_show_remainder(struct git_graph *graph);
  * missing a terminating newline (including if it is empty), the output
  * printed by graph_show_commit_msg() will also be missing a terminating
  * newline.
+ *
+ * Note that unlike some other graph display functions, you must pass the file
+ * handle directly. It is assumed that this is the same file handle as the
+ * file specified by the graph diff options. This is necessary so that
+ * graph_show_commit_msg can be called even with a NULL graph.
  */
-void graph_show_commit_msg(struct git_graph *graph, struct strbuf const *sb);
+void graph_show_commit_msg(struct git_graph *graph,
+                          FILE *file,
+                          struct strbuf const *sb);
 
 #endif /* GRAPH_H */
diff --git a/hex.c b/hex.c
index 9619b67af0b79c4ebae4c5e6a5f5748405bd4dea..ab2610e498615b197787a8c22cd00f979e488fc8 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -39,16 +39,8 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
 {
        int i;
        for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
-               unsigned int val;
-               /*
-                * hex[1]=='\0' is caught when val is checked below,
-                * but if hex[0] is NUL we have to avoid reading
-                * past the end of the string:
-                */
-               if (!hex[0])
-                       return -1;
-               val = (hexval(hex[0]) << 4) | hexval(hex[1]);
-               if (val & ~0xff)
+               int val = hex2chr(hex);
+               if (val < 0)
                        return -1;
                *sha1++ = val;
                hex += 2;
index bfb735c84556f0c6f4d3f17dc4ed18774b8db817..8c2415747a2606aefb29fd6e0092c86da3c72589 100644 (file)
@@ -715,10 +715,7 @@ void show_log(struct rev_info *opt)
        else
                opt->missing_newline = 0;
 
-       if (opt->graph)
-               graph_show_commit_msg(opt->graph, &msgbuf);
-       else
-               fwrite(msgbuf.buf, sizeof(char), msgbuf.len, opt->diffopt.file);
+       graph_show_commit_msg(opt->graph, opt->diffopt.file, &msgbuf);
        if (opt->use_terminator && !commit_format_is_empty(opt->commit_format)) {
                if (!opt->missing_newline)
                        graph_show_padding(opt->graph);
diff --git a/path.c b/path.c
index fe3c4d96c6d82b2c8f4e1553f1a52e410c8c02d3..a8e72955f6f28f6e27e7faeee144f4f3ed492c83 100644 (file)
--- a/path.c
+++ b/path.c
@@ -6,6 +6,7 @@
 #include "string-list.h"
 #include "dir.h"
 #include "worktree.h"
+#include "submodule-config.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -468,12 +469,16 @@ const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
        return pathname->buf;
 }
 
-static void do_submodule_path(struct strbuf *buf, const char *path,
-                             const char *fmt, va_list args)
+/* Returns 0 on success, negative on failure. */
+#define SUBMODULE_PATH_ERR_NOT_CONFIGURED -1
+static int do_submodule_path(struct strbuf *buf, const char *path,
+                            const char *fmt, va_list args)
 {
        const char *git_dir;
        struct strbuf git_submodule_common_dir = STRBUF_INIT;
        struct strbuf git_submodule_dir = STRBUF_INIT;
+       const struct submodule *sub;
+       int err = 0;
 
        strbuf_addstr(buf, path);
        strbuf_complete(buf, '/');
@@ -484,6 +489,17 @@ static void do_submodule_path(struct strbuf *buf, const char *path,
                strbuf_reset(buf);
                strbuf_addstr(buf, git_dir);
        }
+       if (!is_git_directory(buf->buf)) {
+               gitmodules_config();
+               sub = submodule_from_path(null_sha1, path);
+               if (!sub) {
+                       err = SUBMODULE_PATH_ERR_NOT_CONFIGURED;
+                       goto cleanup;
+               }
+               strbuf_reset(buf);
+               strbuf_git_path(buf, "%s/%s", "modules", sub->name);
+       }
+
        strbuf_addch(buf, '/');
        strbuf_addbuf(&git_submodule_dir, buf);
 
@@ -494,27 +510,38 @@ static void do_submodule_path(struct strbuf *buf, const char *path,
 
        strbuf_cleanup_path(buf);
 
+cleanup:
        strbuf_release(&git_submodule_dir);
        strbuf_release(&git_submodule_common_dir);
+
+       return err;
 }
 
 char *git_pathdup_submodule(const char *path, const char *fmt, ...)
 {
+       int err;
        va_list args;
        struct strbuf buf = STRBUF_INIT;
        va_start(args, fmt);
-       do_submodule_path(&buf, path, fmt, args);
+       err = do_submodule_path(&buf, path, fmt, args);
        va_end(args);
+       if (err) {
+               strbuf_release(&buf);
+               return NULL;
+       }
        return strbuf_detach(&buf, NULL);
 }
 
-void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
-                              const char *fmt, ...)
+int strbuf_git_path_submodule(struct strbuf *buf, const char *path,
+                             const char *fmt, ...)
 {
+       int err;
        va_list args;
        va_start(args, fmt);
-       do_submodule_path(buf, path, fmt, args);
+       err = do_submodule_path(buf, path, fmt, args);
        va_end(args);
+
+       return err;
 }
 
 static void do_git_common_path(struct strbuf *buf,
index 62fdb37079fa31698846044448b5cfb398e45528..30489c60b1d3f8acc002fad31aa68b528757c8da 100644 (file)
@@ -172,27 +172,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
 
 static int packet_length(const char *linelen)
 {
-       int n;
-       int len = 0;
-
-       for (n = 0; n < 4; n++) {
-               unsigned char c = linelen[n];
-               len <<= 4;
-               if (c >= '0' && c <= '9') {
-                       len += c - '0';
-                       continue;
-               }
-               if (c >= 'a' && c <= 'f') {
-                       len += c - 'a' + 10;
-                       continue;
-               }
-               if (c >= 'A' && c <= 'F') {
-                       len += c - 'A' + 10;
-                       continue;
-               }
-               return -1;
-       }
-       return len;
+       int val = hex2chr(linelen);
+       return (val < 0) ? val : (val << 8) | hex2chr(linelen + 2);
 }
 
 int packet_read(int fd, char **src_buf, size_t *src_len,
index 9609afb510e2486ae857501a160a9234be87661f..9788bd8f3f0ece03a6b87778a2d43bfaf3c14a9d 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1065,7 +1065,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
        const struct commit *commit = c->commit;
        const char *msg = c->message;
        struct commit_list *p;
-       int h1, h2;
+       int ch;
 
        /* these are independent of the commit */
        switch (placeholder[0]) {
@@ -1089,14 +1089,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                return 1;
        case 'x':
                /* %x00 == NUL, %x0a == LF, etc. */
-               if (0 <= (h1 = hexval_table[0xff & placeholder[1]]) &&
-                   h1 <= 16 &&
-                   0 <= (h2 = hexval_table[0xff & placeholder[2]]) &&
-                   h2 <= 16) {
-                       strbuf_addch(sb, (h1<<4)|h2);
-                       return 3;
-               } else
+               ch = hex2chr(placeholder + 1);
+               if (ch < 0)
                        return 0;
+               strbuf_addch(sb, ch);
+               return 3;
        case 'w':
                if (placeholder[1] == '(') {
                        unsigned long width = 0, indent1 = 0, indent2 = 0;
index bc551a752c460cd4751034b3d69d7e9b5dfc775e..9adbb8af3eaa138ccf161a91ce5b00e366fae7fa 100644 (file)
@@ -1576,24 +1576,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
        qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
 }
 
-static int hex1(char ch)
-{
-       if ('0' <= ch && ch <= '9')
-               return ch - '0';
-       else if ('a' <= ch && ch <= 'f')
-               return ch - 'a' + 10;
-       else if ('A' <= ch && ch <= 'F')
-               return ch - 'A' + 10;
-       return -1;
-}
-static int hex2(const char *cp)
-{
-       if (cp[0] && cp[1])
-               return (hex1(cp[0]) << 4) | hex1(cp[1]);
-       else
-               return -1;
-}
-
 static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
 {
        struct strbuf *s = &state->stack->output;
@@ -1603,7 +1585,7 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
                        if (cp[1] == '%')
                                cp++;
                        else {
-                               int ch = hex2(cp + 1);
+                               int ch = hex2chr(cp + 1);
                                if (0 <= ch) {
                                        strbuf_addch(s, ch);
                                        cp += 3;
index 12290d249643b5aaab18961ca91637910d0d7261..1f34b444af8dd5fbbacc67f0a13510005225c6cc 100644 (file)
@@ -1225,13 +1225,19 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
        struct strbuf refname;
        struct strbuf path = STRBUF_INIT;
        size_t path_baselen;
+       int err = 0;
 
        if (*refs->name)
-               strbuf_git_path_submodule(&path, refs->name, "%s", dirname);
+               err = strbuf_git_path_submodule(&path, refs->name, "%s", dirname);
        else
                strbuf_git_path(&path, "%s", dirname);
        path_baselen = path.len;
 
+       if (err) {
+               strbuf_release(&path);
+               return;
+       }
+
        d = opendir(path.buf);
        if (!d) {
                strbuf_release(&path);
index 3045aeabda1654e095fed3451ea4d5e5efc5c1dc..472ccb2ff96d4bcf57cba4832dc2ca7631b309eb 100644 (file)
@@ -24,6 +24,7 @@
 #include "streaming.h"
 #include "dir.h"
 #include "mru.h"
+#include "list.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -38,6 +39,12 @@ static inline uintmax_t sz_fmt(size_t s) { return s; }
 
 const unsigned char null_sha1[20];
 const struct object_id null_oid;
+const struct object_id empty_tree_oid = {
+       EMPTY_TREE_SHA1_BIN_LITERAL
+};
+const struct object_id empty_blob_oid = {
+       EMPTY_BLOB_SHA1_BIN_LITERAL
+};
 
 /*
  * This is meant to hold a *small* number of objects that you would
@@ -418,6 +425,82 @@ void add_to_alternates_file(const char *reference)
        free(alts);
 }
 
+/*
+ * Compute the exact path an alternate is at and returns it. In case of
+ * error NULL is returned and the human readable error is added to `err`
+ * `path` may be relative and should point to $GITDIR.
+ * `err` must not be null.
+ */
+char *compute_alternate_path(const char *path, struct strbuf *err)
+{
+       char *ref_git = NULL;
+       const char *repo, *ref_git_s;
+       int seen_error = 0;
+
+       ref_git_s = real_path_if_valid(path);
+       if (!ref_git_s) {
+               seen_error = 1;
+               strbuf_addf(err, _("path '%s' does not exist"), path);
+               goto out;
+       } else
+               /*
+                * Beware: read_gitfile(), real_path() and mkpath()
+                * return static buffer
+                */
+               ref_git = xstrdup(ref_git_s);
+
+       repo = read_gitfile(ref_git);
+       if (!repo)
+               repo = read_gitfile(mkpath("%s/.git", ref_git));
+       if (repo) {
+               free(ref_git);
+               ref_git = xstrdup(repo);
+       }
+
+       if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
+               char *ref_git_git = mkpathdup("%s/.git", ref_git);
+               free(ref_git);
+               ref_git = ref_git_git;
+       } else if (!is_directory(mkpath("%s/objects", ref_git))) {
+               struct strbuf sb = STRBUF_INIT;
+               seen_error = 1;
+               if (get_common_dir(&sb, ref_git)) {
+                       strbuf_addf(err,
+                                   _("reference repository '%s' as a linked "
+                                     "checkout is not supported yet."),
+                                   path);
+                       goto out;
+               }
+
+               strbuf_addf(err, _("reference repository '%s' is not a "
+                                       "local repository."), path);
+               goto out;
+       }
+
+       if (!access(mkpath("%s/shallow", ref_git), F_OK)) {
+               strbuf_addf(err, _("reference repository '%s' is shallow"),
+                           path);
+               seen_error = 1;
+               goto out;
+       }
+
+       if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) {
+               strbuf_addf(err,
+                           _("reference repository '%s' is grafted"),
+                           path);
+               seen_error = 1;
+               goto out;
+       }
+
+out:
+       if (seen_error) {
+               free(ref_git);
+               ref_git = NULL;
+       }
+
+       return ref_git;
+}
+
 int foreach_alt_odb(alt_odb_fn fn, void *cb)
 {
        struct alternate_object_database *ent;
@@ -2073,136 +2156,142 @@ static void *unpack_compressed_entry(struct packed_git *p,
        return buffer;
 }
 
-#define MAX_DELTA_CACHE (256)
-
+static struct hashmap delta_base_cache;
 static size_t delta_base_cached;
 
-static struct delta_base_cache_lru_list {
-       struct delta_base_cache_lru_list *prev;
-       struct delta_base_cache_lru_list *next;
-} delta_base_cache_lru = { &delta_base_cache_lru, &delta_base_cache_lru };
+static LIST_HEAD(delta_base_cache_lru);
 
-static struct delta_base_cache_entry {
-       struct delta_base_cache_lru_list lru;
-       void *data;
+struct delta_base_cache_key {
        struct packed_git *p;
        off_t base_offset;
+};
+
+struct delta_base_cache_entry {
+       struct hashmap hash;
+       struct delta_base_cache_key key;
+       struct list_head lru;
+       void *data;
        unsigned long size;
        enum object_type type;
-} delta_base_cache[MAX_DELTA_CACHE];
+};
 
-static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
+static unsigned int pack_entry_hash(struct packed_git *p, off_t base_offset)
 {
-       unsigned long hash;
+       unsigned int hash;
 
-       hash = (unsigned long)(intptr_t)p + (unsigned long)base_offset;
+       hash = (unsigned int)(intptr_t)p + (unsigned int)base_offset;
        hash += (hash >> 8) + (hash >> 16);
-       return hash % MAX_DELTA_CACHE;
+       return hash;
 }
 
 static struct delta_base_cache_entry *
 get_delta_base_cache_entry(struct packed_git *p, off_t base_offset)
 {
-       unsigned long hash = pack_entry_hash(p, base_offset);
-       return delta_base_cache + hash;
+       struct hashmap_entry entry;
+       struct delta_base_cache_key key;
+
+       if (!delta_base_cache.cmpfn)
+               return NULL;
+
+       hashmap_entry_init(&entry, pack_entry_hash(p, base_offset));
+       key.p = p;
+       key.base_offset = base_offset;
+       return hashmap_get(&delta_base_cache, &entry, &key);
+}
+
+static int delta_base_cache_key_eq(const struct delta_base_cache_key *a,
+                                  const struct delta_base_cache_key *b)
+{
+       return a->p == b->p && a->base_offset == b->base_offset;
 }
 
-static int eq_delta_base_cache_entry(struct delta_base_cache_entry *ent,
-                                    struct packed_git *p, off_t base_offset)
+static int delta_base_cache_hash_cmp(const void *va, const void *vb,
+                                    const void *vkey)
 {
-       return (ent->data && ent->p == p && ent->base_offset == base_offset);
+       const struct delta_base_cache_entry *a = va, *b = vb;
+       const struct delta_base_cache_key *key = vkey;
+       if (key)
+               return !delta_base_cache_key_eq(&a->key, key);
+       else
+               return !delta_base_cache_key_eq(&a->key, &b->key);
 }
 
 static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
 {
-       struct delta_base_cache_entry *ent;
-       ent = get_delta_base_cache_entry(p, base_offset);
-       return eq_delta_base_cache_entry(ent, p, base_offset);
+       return !!get_delta_base_cache_entry(p, base_offset);
 }
 
-static void clear_delta_base_cache_entry(struct delta_base_cache_entry *ent)
+/*
+ * Remove the entry from the cache, but do _not_ free the associated
+ * entry data. The caller takes ownership of the "data" buffer, and
+ * should copy out any fields it wants before detaching.
+ */
+static void detach_delta_base_cache_entry(struct delta_base_cache_entry *ent)
 {
-       ent->data = NULL;
-       ent->lru.next->prev = ent->lru.prev;
-       ent->lru.prev->next = ent->lru.next;
+       hashmap_remove(&delta_base_cache, ent, &ent->key);
+       list_del(&ent->lru);
        delta_base_cached -= ent->size;
+       free(ent);
 }
 
 static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
-       unsigned long *base_size, enum object_type *type, int keep_cache)
+       unsigned long *base_size, enum object_type *type)
 {
        struct delta_base_cache_entry *ent;
-       void *ret;
 
        ent = get_delta_base_cache_entry(p, base_offset);
-
-       if (!eq_delta_base_cache_entry(ent, p, base_offset))
+       if (!ent)
                return unpack_entry(p, base_offset, type, base_size);
 
-       ret = ent->data;
-
-       if (!keep_cache)
-               clear_delta_base_cache_entry(ent);
-       else
-               ret = xmemdupz(ent->data, ent->size);
        *type = ent->type;
        *base_size = ent->size;
-       return ret;
+       return xmemdupz(ent->data, ent->size);
 }
 
 static inline void release_delta_base_cache(struct delta_base_cache_entry *ent)
 {
-       if (ent->data) {
-               free(ent->data);
-               ent->data = NULL;
-               ent->lru.next->prev = ent->lru.prev;
-               ent->lru.prev->next = ent->lru.next;
-               delta_base_cached -= ent->size;
-       }
+       free(ent->data);
+       detach_delta_base_cache_entry(ent);
 }
 
 void clear_delta_base_cache(void)
 {
-       unsigned long p;
-       for (p = 0; p < MAX_DELTA_CACHE; p++)
-               release_delta_base_cache(&delta_base_cache[p]);
+       struct hashmap_iter iter;
+       struct delta_base_cache_entry *entry;
+       for (entry = hashmap_iter_first(&delta_base_cache, &iter);
+            entry;
+            entry = hashmap_iter_next(&iter)) {
+               release_delta_base_cache(entry);
+       }
 }
 
 static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
        void *base, unsigned long base_size, enum object_type type)
 {
-       unsigned long hash = pack_entry_hash(p, base_offset);
-       struct delta_base_cache_entry *ent = delta_base_cache + hash;
-       struct delta_base_cache_lru_list *lru;
+       struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent));
+       struct list_head *lru;
 
-       release_delta_base_cache(ent);
        delta_base_cached += base_size;
 
-       for (lru = delta_base_cache_lru.next;
-            delta_base_cached > delta_base_cache_limit
-            && lru != &delta_base_cache_lru;
-            lru = lru->next) {
-               struct delta_base_cache_entry *f = (void *)lru;
-               if (f->type == OBJ_BLOB)
-                       release_delta_base_cache(f);
-       }
-       for (lru = delta_base_cache_lru.next;
-            delta_base_cached > delta_base_cache_limit
-            && lru != &delta_base_cache_lru;
-            lru = lru->next) {
-               struct delta_base_cache_entry *f = (void *)lru;
+       list_for_each(lru, &delta_base_cache_lru) {
+               struct delta_base_cache_entry *f =
+                       list_entry(lru, struct delta_base_cache_entry, lru);
+               if (delta_base_cached <= delta_base_cache_limit)
+                       break;
                release_delta_base_cache(f);
        }
 
-       ent->p = p;
-       ent->base_offset = base_offset;
+       ent->key.p = p;
+       ent->key.base_offset = base_offset;
        ent->type = type;
        ent->data = base;
        ent->size = base_size;
-       ent->lru.next = &delta_base_cache_lru;
-       ent->lru.prev = delta_base_cache_lru.prev;
-       delta_base_cache_lru.prev->next = &ent->lru;
-       delta_base_cache_lru.prev = &ent->lru;
+       list_add_tail(&ent->lru, &delta_base_cache_lru);
+
+       if (!delta_base_cache.cmpfn)
+               hashmap_init(&delta_base_cache, delta_base_cache_hash_cmp, 0);
+       hashmap_entry_init(ent, pack_entry_hash(p, base_offset));
+       hashmap_add(&delta_base_cache, ent);
 }
 
 static void *read_object(const unsigned char *sha1, enum object_type *type,
@@ -2246,11 +2335,11 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
                struct delta_base_cache_entry *ent;
 
                ent = get_delta_base_cache_entry(p, curpos);
-               if (eq_delta_base_cache_entry(ent, p, curpos)) {
+               if (ent) {
                        type = ent->type;
                        data = ent->data;
                        size = ent->size;
-                       clear_delta_base_cache_entry(ent);
+                       detach_delta_base_cache_entry(ent);
                        base_from_cache = 1;
                        break;
                }
@@ -2755,7 +2844,7 @@ static void *read_packed_sha1(const unsigned char *sha1,
 
        if (!find_pack_entry(sha1, &e))
                return NULL;
-       data = cache_or_unpack_entry(e.p, e.offset, size, type, 1);
+       data = cache_or_unpack_entry(e.p, e.offset, size, type);
        if (!data) {
                /*
                 * We're probably in deep shit, but let's try to fetch
index 1b5cdfb7e784d646c15e59afb2fb43587a8ff8e9..0ef2ff4321d1723d05e9328e564313437ec8476e 100644 (file)
@@ -127,7 +127,9 @@ static int add_submodule_odb(const char *path)
        int ret = 0;
        size_t alloc;
 
-       strbuf_git_path_submodule(&objects_directory, path, "objects/");
+       ret = strbuf_git_path_submodule(&objects_directory, path, "objects/");
+       if (ret)
+               goto done;
        if (!is_directory(objects_directory.buf)) {
                ret = -1;
                goto done;
@@ -278,9 +280,9 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
 
 static int prepare_submodule_summary(struct rev_info *rev, const char *path,
                struct commit *left, struct commit *right,
-               int *fast_forward, int *fast_backward)
+               struct commit_list *merge_bases)
 {
-       struct commit_list *merge_bases, *list;
+       struct commit_list *list;
 
        init_revisions(rev, NULL);
        setup_revisions(0, NULL, rev, NULL);
@@ -289,13 +291,6 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path,
        left->object.flags |= SYMMETRIC_LEFT;
        add_pending_object(rev, &left->object, path);
        add_pending_object(rev, &right->object, path);
-       merge_bases = get_merge_bases(left, right);
-       if (merge_bases) {
-               if (merge_bases->item == left)
-                       *fast_forward = 1;
-               else if (merge_bases->item == right)
-                       *fast_backward = 1;
-       }
        for (list = merge_bases; list; list = list->next) {
                list->item->object.flags |= UNINTERESTING;
                add_pending_object(rev, &list->item->object,
@@ -333,31 +328,23 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f,
        strbuf_release(&sb);
 }
 
-void show_submodule_summary(FILE *f, const char *path,
+/* Helper function to display the submodule header line prior to the full
+ * summary output. If it can locate the submodule objects directory it will
+ * attempt to lookup both the left and right commits and put them into the
+ * left and right pointers.
+ */
+static void show_submodule_header(FILE *f, const char *path,
                const char *line_prefix,
-               unsigned char one[20], unsigned char two[20],
+               struct object_id *one, struct object_id *two,
                unsigned dirty_submodule, const char *meta,
-               const char *del, const char *add, const char *reset)
+               const char *reset,
+               struct commit **left, struct commit **right,
+               struct commit_list **merge_bases)
 {
-       struct rev_info rev;
-       struct commit *left = NULL, *right = NULL;
        const char *message = NULL;
        struct strbuf sb = STRBUF_INIT;
        int fast_forward = 0, fast_backward = 0;
 
-       if (is_null_sha1(two))
-               message = "(submodule deleted)";
-       else if (add_submodule_odb(path))
-               message = "(not checked out)";
-       else if (is_null_sha1(one))
-               message = "(new submodule)";
-       else if (!(left = lookup_commit_reference(one)) ||
-                !(right = lookup_commit_reference(two)))
-               message = "(commits not present)";
-       else if (prepare_submodule_summary(&rev, path, left, right,
-                                          &fast_forward, &fast_backward))
-               message = "(revision walker failed)";
-
        if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
                fprintf(f, "%sSubmodule %s contains untracked content\n",
                        line_prefix, path);
@@ -365,30 +352,163 @@ void show_submodule_summary(FILE *f, const char *path,
                fprintf(f, "%sSubmodule %s contains modified content\n",
                        line_prefix, path);
 
-       if (!hashcmp(one, two)) {
+       if (is_null_oid(one))
+               message = "(new submodule)";
+       else if (is_null_oid(two))
+               message = "(submodule deleted)";
+
+       if (add_submodule_odb(path)) {
+               if (!message)
+                       message = "(not initialized)";
+               goto output_header;
+       }
+
+       /*
+        * Attempt to lookup the commit references, and determine if this is
+        * a fast forward or fast backwards update.
+        */
+       *left = lookup_commit_reference(one->hash);
+       *right = lookup_commit_reference(two->hash);
+
+       /*
+        * Warn about missing commits in the submodule project, but only if
+        * they aren't null.
+        */
+       if ((!is_null_oid(one) && !*left) ||
+            (!is_null_oid(two) && !*right))
+               message = "(commits not present)";
+
+       *merge_bases = get_merge_bases(*left, *right);
+       if (*merge_bases) {
+               if ((*merge_bases)->item == *left)
+                       fast_forward = 1;
+               else if ((*merge_bases)->item == *right)
+                       fast_backward = 1;
+       }
+
+       if (!oidcmp(one, two)) {
                strbuf_release(&sb);
                return;
        }
 
+output_header:
        strbuf_addf(&sb, "%s%sSubmodule %s %s..", line_prefix, meta, path,
-                       find_unique_abbrev(one, DEFAULT_ABBREV));
+                       find_unique_abbrev(one->hash, DEFAULT_ABBREV));
        if (!fast_backward && !fast_forward)
                strbuf_addch(&sb, '.');
-       strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV));
+       strbuf_addf(&sb, "%s", find_unique_abbrev(two->hash, DEFAULT_ABBREV));
        if (message)
                strbuf_addf(&sb, " %s%s\n", message, reset);
        else
                strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset);
        fwrite(sb.buf, sb.len, 1, f);
 
-       if (!message) /* only NULL if we succeeded in setting up the walk */
-               print_submodule_summary(&rev, f, line_prefix, del, add, reset);
+       strbuf_release(&sb);
+}
+
+void show_submodule_summary(FILE *f, const char *path,
+               const char *line_prefix,
+               struct object_id *one, struct object_id *two,
+               unsigned dirty_submodule, const char *meta,
+               const char *del, const char *add, const char *reset)
+{
+       struct rev_info rev;
+       struct commit *left = NULL, *right = NULL;
+       struct commit_list *merge_bases = NULL;
+
+       show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
+                             meta, reset, &left, &right, &merge_bases);
+
+       /*
+        * If we don't have both a left and a right pointer, there is no
+        * reason to try and display a summary. The header line should contain
+        * all the information the user needs.
+        */
+       if (!left || !right)
+               goto out;
+
+       /* Treat revision walker failure the same as missing commits */
+       if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
+               fprintf(f, "%s(revision walker failed)\n", line_prefix);
+               goto out;
+       }
+
+       print_submodule_summary(&rev, f, line_prefix, del, add, reset);
+
+out:
+       if (merge_bases)
+               free_commit_list(merge_bases);
+       clear_commit_marks(left, ~0);
+       clear_commit_marks(right, ~0);
+}
+
+void show_submodule_inline_diff(FILE *f, const char *path,
+               const char *line_prefix,
+               struct object_id *one, struct object_id *two,
+               unsigned dirty_submodule, const char *meta,
+               const char *del, const char *add, const char *reset,
+               const struct diff_options *o)
+{
+       const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
+       struct commit *left = NULL, *right = NULL;
+       struct commit_list *merge_bases = NULL;
+       struct strbuf submodule_dir = STRBUF_INIT;
+       struct child_process cp = CHILD_PROCESS_INIT;
+
+       show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
+                             meta, reset, &left, &right, &merge_bases);
+
+       /* We need a valid left and right commit to display a difference */
+       if (!(left || is_null_oid(one)) ||
+           !(right || is_null_oid(two)))
+               goto done;
+
+       if (left)
+               old = one;
+       if (right)
+               new = two;
+
+       fflush(f);
+       cp.git_cmd = 1;
+       cp.dir = path;
+       cp.out = dup(fileno(f));
+       cp.no_stdin = 1;
+
+       /* TODO: other options may need to be passed here. */
+       argv_array_push(&cp.args, "diff");
+       argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
+       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+               argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
+                                o->b_prefix, path);
+               argv_array_pushf(&cp.args, "--dst-prefix=%s%s/",
+                                o->a_prefix, path);
+       } else {
+               argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
+                                o->a_prefix, path);
+               argv_array_pushf(&cp.args, "--dst-prefix=%s%s/",
+                                o->b_prefix, path);
+       }
+       argv_array_push(&cp.args, oid_to_hex(old));
+       /*
+        * If the submodule has modified content, we will diff against the
+        * work tree, under the assumption that the user has asked for the
+        * diff format and wishes to actually see all differences even if they
+        * haven't yet been committed to the submodule yet.
+        */
+       if (!(dirty_submodule & DIRTY_SUBMODULE_MODIFIED))
+               argv_array_push(&cp.args, oid_to_hex(new));
+
+       if (run_command(&cp))
+               fprintf(f, "(diff failed)\n");
+
+done:
+       strbuf_release(&submodule_dir);
+       if (merge_bases)
+               free_commit_list(merge_bases);
        if (left)
                clear_commit_marks(left, ~0);
        if (right)
                clear_commit_marks(right, ~0);
-
-       strbuf_release(&sb);
 }
 
 void set_config_fetch_recurse_submodules(int value)
@@ -1160,4 +1280,5 @@ void prepare_submodule_repo_env(struct argv_array *out)
                if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
                        argv_array_push(out, *var);
        }
+       argv_array_push(out, "GIT_DIR=.git");
 }
index 2af9390998194c7d51a3e23b14723574d6696926..d9e197a948fdab44b7a5df4161a5df9c3a2938ff 100644 (file)
@@ -43,9 +43,15 @@ const char *submodule_strategy_to_string(const struct submodule_update_strategy
 void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
 void show_submodule_summary(FILE *f, const char *path,
                const char *line_prefix,
-               unsigned char one[20], unsigned char two[20],
+               struct object_id *one, struct object_id *two,
                unsigned dirty_submodule, const char *meta,
                const char *del, const char *add, const char *reset);
+void show_submodule_inline_diff(FILE *f, const char *path,
+               const char *line_prefix,
+               struct object_id *one, struct object_id *two,
+               unsigned dirty_submodule, const char *meta,
+               const char *del, const char *add, const char *reset,
+               const struct diff_options *opt);
 void set_config_fetch_recurse_submodules(int value);
 void check_for_new_submodule_commits(unsigned char new_sha1[20]);
 int fetch_populated_submodules(const struct argv_array *options,
diff --git a/t/perf/p0003-delta-base-cache.sh b/t/perf/p0003-delta-base-cache.sh
new file mode 100755 (executable)
index 0000000..62369ea
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='Test operations that emphasize the delta base cache.
+
+We look at both "log --raw", which should put only trees into the delta cache,
+and "log -Sfoo --raw", which should look at both trees and blobs.
+
+Any effects will be emphasized if the test repository is fully packed (loose
+objects obviously do not use the delta base cache at all). It is also
+emphasized if the pack has long delta chains (e.g., as produced by "gc
+--aggressive"), though cache is still quite noticeable even with the default
+depth of 50.
+
+The setting of core.deltaBaseCacheLimit in the source repository is also
+relevant (depending on the size of your test repo), so be sure it is consistent
+between runs.
+'
+. ./perf-lib.sh
+
+test_perf_large_repo
+
+# puts mostly trees into the delta base cache
+test_perf 'log --raw' '
+       git log --raw >/dev/null
+'
+
+test_perf 'log -S' '
+       git log --raw -Sfoo >/dev/null
+'
+
+test_done
diff --git a/t/t0012-help.sh b/t/t0012-help.sh
new file mode 100755 (executable)
index 0000000..8faba2e
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='help'
+
+. ./test-lib.sh
+
+configure_help () {
+       test_config help.format html &&
+
+       # Unless the path has "://" in it, Git tries to make sure
+       # the documentation directory locally exists. Avoid it as
+       # we are only interested in seeing an attempt to correctly
+       # invoke a help browser in this test.
+       test_config help.htmlpath test://html &&
+
+       # Name a custom browser
+       test_config browser.test.cmd ./test-browser &&
+       test_config help.browser test
+}
+
+test_expect_success "setup" '
+       # Just write out which page gets requested
+       write_script test-browser <<-\EOF
+       echo "$*" >test-browser.log
+       EOF
+'
+
+test_expect_success "works for commands and guides by default" '
+       configure_help &&
+       git help status &&
+       echo "test://html/git-status.html" >expect &&
+       test_cmp expect test-browser.log &&
+       git help revisions &&
+       echo "test://html/gitrevisions.html" >expect &&
+       test_cmp expect test-browser.log
+'
+
+test_expect_success "--exclude-guides does not work for guides" '
+       >test-browser.log &&
+       test_must_fail git help --exclude-guides revisions &&
+       test_must_be_empty test-browser.log
+'
+
+test_expect_success "--help does not work for guides" "
+       cat <<-EOF >expect &&
+               git: 'revisions' is not a git command. See 'git --help'.
+       EOF
+       test_must_fail git revisions --help 2>actual &&
+       test_i18ncmp expect actual
+"
+
+test_done
index ca3fa406c34f1d41ac5d2be67804af234b996654..eec3e90f9c04e9b88ebb8c2ee6f12b96b75bac2b 100755 (executable)
@@ -33,18 +33,25 @@ test_expect_success 'symbolic-ref refuses bare sha1' '
 '
 reset_to_sane
 
-test_expect_success 'symbolic-ref deletes HEAD' '
-       git symbolic-ref -d HEAD &&
+test_expect_success 'HEAD cannot be removed' '
+       test_must_fail git symbolic-ref -d HEAD
+'
+
+reset_to_sane
+
+test_expect_success 'symbolic-ref can be deleted' '
+       git symbolic-ref NOTHEAD refs/heads/foo &&
+       git symbolic-ref -d NOTHEAD &&
        test_path_is_file .git/refs/heads/foo &&
-       test_path_is_missing .git/HEAD
+       test_path_is_missing .git/NOTHEAD
 '
 reset_to_sane
 
-test_expect_success 'symbolic-ref deletes dangling HEAD' '
-       git symbolic-ref HEAD refs/heads/missing &&
-       git symbolic-ref -d HEAD &&
+test_expect_success 'symbolic-ref can delete dangling symref' '
+       git symbolic-ref NOTHEAD refs/heads/missing &&
+       git symbolic-ref -d NOTHEAD &&
        test_path_is_missing .git/refs/heads/missing &&
-       test_path_is_missing .git/HEAD
+       test_path_is_missing .git/NOTHEAD
 '
 reset_to_sane
 
index 94ef5000e787f0898ff6ccb57661c972c765db52..566817e2efdccc4747e871f3bdabb90fa5b134ec 100755 (executable)
@@ -306,6 +306,8 @@ diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
 diff --no-index dir dir3
 diff master master^ side
+# Can't use spaces...
+diff --line-prefix=abc master master^ side
 diff --dirstat master~1 master~2
 diff --dirstat initial rearrange
 diff --dirstat-by-file initial rearrange
@@ -325,6 +327,10 @@ test_expect_success 'diff --cached -- file on unborn branch' '
        git diff --cached -- file0 >result &&
        test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result
 '
+test_expect_success 'diff --line-prefix with spaces' '
+       git diff --line-prefix="| | | " --cached -- file0 >result &&
+       test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--line-prefix_--cached_--_file0" result
+'
 
 test_expect_success 'diff-tree --stdin with log formatting' '
        cat >expect <<-\EOF &&
diff --git a/t/t4013/diff.diff_--line-prefix=abc_master_master^_side b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side
new file mode 100644 (file)
index 0000000..99f91e7
--- /dev/null
@@ -0,0 +1,29 @@
+$ git diff --line-prefix=abc master master^ side
+abcdiff --cc dir/sub
+abcindex cead32e,7289e35..992913c
+abc--- a/dir/sub
+abc+++ b/dir/sub
+abc@@@ -1,6 -1,4 +1,8 @@@
+abc  A
+abc  B
+abc +C
+abc +D
+abc +E
+abc +F
+abc+ 1
+abc+ 2
+abcdiff --cc file0
+abcindex b414108,f4615da..10a8a9f
+abc--- a/file0
+abc+++ b/file0
+abc@@@ -1,6 -1,6 +1,9 @@@
+abc  1
+abc  2
+abc  3
+abc +4
+abc +5
+abc +6
+abc+ A
+abc+ B
+abc+ C
+$
diff --git a/t/t4013/diff.diff_--line-prefix_--cached_--_file0 b/t/t4013/diff.diff_--line-prefix_--cached_--_file0
new file mode 100644 (file)
index 0000000..f41ba4d
--- /dev/null
@@ -0,0 +1,15 @@
+| | | diff --git a/file0 b/file0
+| | | new file mode 100644
+| | | index 0000000..10a8a9f
+| | | --- /dev/null
+| | | +++ b/file0
+| | | @@ -0,0 +1,9 @@
+| | | +1
+| | | +2
+| | | +3
+| | | +4
+| | | +5
+| | | +6
+| | | +A
+| | | +B
+| | | +C
index 886494b58f6739d40c2c34724064317b0d3be770..9be65fd4440a68af70e6630c8ded872d7be41f5d 100755 (executable)
@@ -36,6 +36,11 @@ test_no_numbered() {
        test_num_no_numbered $1 2
 }
 
+test_single_cover_letter_numbered() {
+       grep "^Subject: \[PATCH 0/1\]" $1 &&
+       grep "^Subject: \[PATCH 1/1\]" $1
+}
+
 test_single_numbered() {
        grep "^Subject: \[PATCH 1/1\]" $1
 }
@@ -121,4 +126,16 @@ test_expect_success '--start-number && --numbered' '
        grep "^Subject: \[PATCH 3/3\]" patch8
 '
 
+test_expect_success 'single patch with cover-letter defaults to numbers' '
+       git format-patch --cover-letter --stdout HEAD~1 >patch9.single &&
+       test_single_cover_letter_numbered patch9.single
+'
+
+test_expect_success 'Use --no-numbered and --cover-letter single patch' '
+       git format-patch --no-numbered --stdout --cover-letter HEAD~1 >patch10 &&
+       test_no_numbered patch10
+'
+
+
+
 test_done
diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh
new file mode 100755 (executable)
index 0000000..cd70fd5
--- /dev/null
@@ -0,0 +1,127 @@
+#!/bin/sh
+#
+# Copyright (c) 2016 Jacob Keller, based on t4041 by Jens Lehmann
+#
+
+test_description='Test for submodule diff on non-checked out submodule
+
+This test tries to verify that add_submodule_odb works when the submodule was
+initialized previously but the checkout has since been removed.
+'
+
+. ./test-lib.sh
+
+# Tested non-UTF-8 encoding
+test_encoding="ISO8859-1"
+
+# String "added" in German (translated with Google Translate), encoded in UTF-8,
+# used in sample commit log messages in add_file() function below.
+added=$(printf "hinzugef\303\274gt")
+
+add_file () {
+       (
+               cd "$1" &&
+               shift &&
+               for name
+               do
+                       echo "$name" >"$name" &&
+                       git add "$name" &&
+                       test_tick &&
+                       # "git commit -m" would break MinGW, as Windows refuse to pass
+                       # $test_encoding encoded parameter to git.
+                       echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding |
+                       git -c "i18n.commitEncoding=$test_encoding" commit -F -
+               done >/dev/null &&
+               git rev-parse --short --verify HEAD
+       )
+}
+
+commit_file () {
+       test_tick &&
+       git commit "$@" -m "Commit $*" >/dev/null
+}
+
+test_expect_success 'setup - submodules' '
+       test_create_repo sm2 &&
+       add_file . foo &&
+       add_file sm2 foo1 foo2 &&
+       smhead1=$(git -C sm2 rev-parse --short --verify HEAD)
+'
+
+test_expect_success 'setup - git submodule add' '
+       git submodule add ./sm2 sm1 &&
+       commit_file sm1 .gitmodules &&
+       git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 0000000...$smhead1 (new submodule)
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule directory removed' '
+       rm -rf sm1 &&
+       git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 0000000...$smhead1 (new submodule)
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'setup - submodule multiple commits' '
+       git submodule update --checkout sm1 &&
+       smhead2=$(add_file sm1 foo3 foo4) &&
+       commit_file sm1 &&
+       git diff-tree -p --no-commit-id --submodule=log HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $smhead1..$smhead2:
+         > Add foo4 ($added foo4)
+         > Add foo3 ($added foo3)
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule removed multiple commits' '
+       rm -rf sm1 &&
+       git diff-tree -p --no-commit-id --submodule=log HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $smhead1..$smhead2:
+         > Add foo4 ($added foo4)
+         > Add foo3 ($added foo3)
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule not initialized in new clone' '
+       git clone . sm3 &&
+       git -C sm3 diff-tree -p --no-commit-id --submodule=log HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $smhead1...$smhead2 (not initialized)
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'setup submodule moved' '
+       git submodule update --checkout sm1 &&
+       git mv sm1 sm4 &&
+       commit_file sm4 &&
+       git diff-tree -p --no-commit-id --submodule=log HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm4 0000000...$smhead2 (new submodule)
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule moved then removed' '
+       smhead3=$(add_file sm4 foo6 foo7) &&
+       commit_file sm4 &&
+       rm -rf sm4 &&
+       git diff-tree -p --no-commit-id --submodule=log HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm4 $smhead2..$smhead3:
+         > Add foo7 ($added foo7)
+         > Add foo6 ($added foo6)
+       EOF
+       test_cmp expected actual
+'
+
+test_done
diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh
new file mode 100755 (executable)
index 0000000..7e23b55
--- /dev/null
@@ -0,0 +1,749 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin
+# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests)
+# Copyright (c) 2016 Jacob Keller (copy + convert to --submodule=diff)
+#
+
+test_description='Support for diff format verbose submodule difference in git diff
+
+This test tries to verify the sanity of --submodule=diff option of git diff.
+'
+
+. ./test-lib.sh
+
+# Tested non-UTF-8 encoding
+test_encoding="ISO8859-1"
+
+# String "added" in German (translated with Google Translate), encoded in UTF-8,
+# used in sample commit log messages in add_file() function below.
+added=$(printf "hinzugef\303\274gt")
+
+add_file () {
+       (
+               cd "$1" &&
+               shift &&
+               for name
+               do
+                       echo "$name" >"$name" &&
+                       git add "$name" &&
+                       test_tick &&
+                       # "git commit -m" would break MinGW, as Windows refuse to pass
+                       # $test_encoding encoded parameter to git.
+                       echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding |
+                       git -c "i18n.commitEncoding=$test_encoding" commit -F -
+               done >/dev/null &&
+               git rev-parse --short --verify HEAD
+       )
+}
+
+commit_file () {
+       test_tick &&
+       git commit "$@" -m "Commit $*" >/dev/null
+}
+
+test_expect_success 'setup repository' '
+       test_create_repo sm1 &&
+       add_file . foo &&
+       head1=$(add_file sm1 foo1 foo2) &&
+       fullhead1=$(git -C sm1 rev-parse --verify HEAD)
+'
+
+test_expect_success 'added submodule' '
+       git add sm1 &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 0000000...$head1 (new submodule)
+       diff --git a/sm1/foo1 b/sm1/foo1
+       new file mode 100644
+       index 0000000..1715acd
+       --- /dev/null
+       +++ b/sm1/foo1
+       @@ -0,0 +1 @@
+       +foo1
+       diff --git a/sm1/foo2 b/sm1/foo2
+       new file mode 100644
+       index 0000000..54b060e
+       --- /dev/null
+       +++ b/sm1/foo2
+       @@ -0,0 +1 @@
+       +foo2
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'added submodule, set diff.submodule' '
+       test_config diff.submodule log &&
+       git add sm1 &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 0000000...$head1 (new submodule)
+       diff --git a/sm1/foo1 b/sm1/foo1
+       new file mode 100644
+       index 0000000..1715acd
+       --- /dev/null
+       +++ b/sm1/foo1
+       @@ -0,0 +1 @@
+       +foo1
+       diff --git a/sm1/foo2 b/sm1/foo2
+       new file mode 100644
+       index 0000000..54b060e
+       --- /dev/null
+       +++ b/sm1/foo2
+       @@ -0,0 +1 @@
+       +foo2
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success '--submodule=short overrides diff.submodule' '
+       test_config diff.submodule log &&
+       git add sm1 &&
+       git diff --submodule=short --cached >actual &&
+       cat >expected <<-EOF &&
+       diff --git a/sm1 b/sm1
+       new file mode 160000
+       index 0000000..$head1
+       --- /dev/null
+       +++ b/sm1
+       @@ -0,0 +1 @@
+       +Subproject commit $fullhead1
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'diff.submodule does not affect plumbing' '
+       test_config diff.submodule log &&
+       git diff-index -p HEAD >actual &&
+       cat >expected <<-EOF &&
+       diff --git a/sm1 b/sm1
+       new file mode 160000
+       index 0000000..$head1
+       --- /dev/null
+       +++ b/sm1
+       @@ -0,0 +1 @@
+       +Subproject commit $fullhead1
+       EOF
+       test_cmp expected actual
+'
+
+commit_file sm1 &&
+head2=$(add_file sm1 foo3)
+
+test_expect_success 'modified submodule(forward)' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head1..$head2:
+       diff --git a/sm1/foo3 b/sm1/foo3
+       new file mode 100644
+       index 0000000..c1ec6c6
+       --- /dev/null
+       +++ b/sm1/foo3
+       @@ -0,0 +1 @@
+       +foo3
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule(forward)' '
+       git diff --submodule=diff >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head1..$head2:
+       diff --git a/sm1/foo3 b/sm1/foo3
+       new file mode 100644
+       index 0000000..c1ec6c6
+       --- /dev/null
+       +++ b/sm1/foo3
+       @@ -0,0 +1 @@
+       +foo3
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule(forward) --submodule' '
+       git diff --submodule >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head1..$head2:
+         > Add foo3 ($added foo3)
+       EOF
+       test_cmp expected actual
+'
+
+fullhead2=$(cd sm1; git rev-parse --verify HEAD)
+test_expect_success 'modified submodule(forward) --submodule=short' '
+       git diff --submodule=short >actual &&
+       cat >expected <<-EOF &&
+       diff --git a/sm1 b/sm1
+       index $head1..$head2 160000
+       --- a/sm1
+       +++ b/sm1
+       @@ -1 +1 @@
+       -Subproject commit $fullhead1
+       +Subproject commit $fullhead2
+       EOF
+       test_cmp expected actual
+'
+
+commit_file sm1 &&
+head3=$(
+       cd sm1 &&
+       git reset --hard HEAD~2 >/dev/null &&
+       git rev-parse --short --verify HEAD
+)
+
+test_expect_success 'modified submodule(backward)' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head2..$head3 (rewind):
+       diff --git a/sm1/foo2 b/sm1/foo2
+       deleted file mode 100644
+       index 54b060e..0000000
+       --- a/sm1/foo2
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo2
+       diff --git a/sm1/foo3 b/sm1/foo3
+       deleted file mode 100644
+       index c1ec6c6..0000000
+       --- a/sm1/foo3
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo3
+       EOF
+       test_cmp expected actual
+'
+
+head4=$(add_file sm1 foo4 foo5)
+test_expect_success 'modified submodule(backward and forward)' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head2...$head4:
+       diff --git a/sm1/foo2 b/sm1/foo2
+       deleted file mode 100644
+       index 54b060e..0000000
+       --- a/sm1/foo2
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo2
+       diff --git a/sm1/foo3 b/sm1/foo3
+       deleted file mode 100644
+       index c1ec6c6..0000000
+       --- a/sm1/foo3
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo3
+       diff --git a/sm1/foo4 b/sm1/foo4
+       new file mode 100644
+       index 0000000..a0016db
+       --- /dev/null
+       +++ b/sm1/foo4
+       @@ -0,0 +1 @@
+       +foo4
+       diff --git a/sm1/foo5 b/sm1/foo5
+       new file mode 100644
+       index 0000000..d6f2413
+       --- /dev/null
+       +++ b/sm1/foo5
+       @@ -0,0 +1 @@
+       +foo5
+       EOF
+       test_cmp expected actual
+'
+
+commit_file sm1 &&
+mv sm1 sm1-bak &&
+echo sm1 >sm1 &&
+head5=$(git hash-object sm1 | cut -c1-7) &&
+git add sm1 &&
+rm -f sm1 &&
+mv sm1-bak sm1
+
+test_expect_success 'typechanged submodule(submodule->blob), --cached' '
+       git diff --submodule=diff --cached >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head4...0000000 (submodule deleted)
+       diff --git a/sm1/foo1 b/sm1/foo1
+       deleted file mode 100644
+       index 1715acd..0000000
+       --- a/sm1/foo1
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo1
+       diff --git a/sm1/foo4 b/sm1/foo4
+       deleted file mode 100644
+       index a0016db..0000000
+       --- a/sm1/foo4
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo4
+       diff --git a/sm1/foo5 b/sm1/foo5
+       deleted file mode 100644
+       index d6f2413..0000000
+       --- a/sm1/foo5
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo5
+       diff --git a/sm1 b/sm1
+       new file mode 100644
+       index 0000000..9da5fb8
+       --- /dev/null
+       +++ b/sm1
+       @@ -0,0 +1 @@
+       +sm1
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'typechanged submodule(submodule->blob)' '
+       git diff --submodule=diff >actual &&
+       cat >expected <<-EOF &&
+       diff --git a/sm1 b/sm1
+       deleted file mode 100644
+       index 9da5fb8..0000000
+       --- a/sm1
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -sm1
+       Submodule sm1 0000000...$head4 (new submodule)
+       diff --git a/sm1/foo1 b/sm1/foo1
+       new file mode 100644
+       index 0000000..1715acd
+       --- /dev/null
+       +++ b/sm1/foo1
+       @@ -0,0 +1 @@
+       +foo1
+       diff --git a/sm1/foo4 b/sm1/foo4
+       new file mode 100644
+       index 0000000..a0016db
+       --- /dev/null
+       +++ b/sm1/foo4
+       @@ -0,0 +1 @@
+       +foo4
+       diff --git a/sm1/foo5 b/sm1/foo5
+       new file mode 100644
+       index 0000000..d6f2413
+       --- /dev/null
+       +++ b/sm1/foo5
+       @@ -0,0 +1 @@
+       +foo5
+       EOF
+       test_cmp expected actual
+'
+
+rm -rf sm1 &&
+git checkout-index sm1
+test_expect_success 'typechanged submodule(submodule->blob)' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head4...0000000 (submodule deleted)
+       diff --git a/sm1 b/sm1
+       new file mode 100644
+       index 0000000..9da5fb8
+       --- /dev/null
+       +++ b/sm1
+       @@ -0,0 +1 @@
+       +sm1
+       EOF
+       test_cmp expected actual
+'
+
+rm -f sm1 &&
+test_create_repo sm1 &&
+head6=$(add_file sm1 foo6 foo7)
+fullhead6=$(cd sm1; git rev-parse --verify HEAD)
+test_expect_success 'nonexistent commit' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head4...$head6 (commits not present)
+       EOF
+       test_cmp expected actual
+'
+
+commit_file
+test_expect_success 'typechanged submodule(blob->submodule)' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       diff --git a/sm1 b/sm1
+       deleted file mode 100644
+       index 9da5fb8..0000000
+       --- a/sm1
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -sm1
+       Submodule sm1 0000000...$head6 (new submodule)
+       diff --git a/sm1/foo6 b/sm1/foo6
+       new file mode 100644
+       index 0000000..462398b
+       --- /dev/null
+       +++ b/sm1/foo6
+       @@ -0,0 +1 @@
+       +foo6
+       diff --git a/sm1/foo7 b/sm1/foo7
+       new file mode 100644
+       index 0000000..6e9262c
+       --- /dev/null
+       +++ b/sm1/foo7
+       @@ -0,0 +1 @@
+       +foo7
+       EOF
+       test_cmp expected actual
+'
+
+commit_file sm1 &&
+test_expect_success 'submodule is up to date' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule contains untracked content' '
+       echo new > sm1/new-file &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains untracked content
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule contains untracked content (untracked ignored)' '
+       git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'submodule contains untracked content (dirty ignored)' '
+       git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'submodule contains untracked content (all ignored)' '
+       git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'submodule contains untracked and modified content' '
+       echo new > sm1/foo6 &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains untracked content
+       Submodule sm1 contains modified content
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+# NOT OK
+test_expect_success 'submodule contains untracked and modified content (untracked ignored)' '
+       echo new > sm1/foo6 &&
+       git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains modified content
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule contains untracked and modified content (dirty ignored)' '
+       echo new > sm1/foo6 &&
+       git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'submodule contains untracked and modified content (all ignored)' '
+       echo new > sm1/foo6 &&
+       git diff-index -p --ignore-submodules --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'submodule contains modified content' '
+       rm -f sm1/new-file &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains modified content
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+(cd sm1; git commit -mchange foo6 >/dev/null) &&
+head8=$(cd sm1; git rev-parse --short --verify HEAD) &&
+test_expect_success 'submodule is modified' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9..$head8:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content' '
+       echo new > sm1/new-file &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains untracked content
+       Submodule sm1 17243c9..$head8:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content (untracked ignored)' '
+       git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9..$head8:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content (dirty ignored)' '
+       git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9..cfce562:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content (all ignored)' '
+       git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content' '
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains untracked content
+       Submodule sm1 contains modified content
+       Submodule sm1 17243c9..cfce562:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..dfda541 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1,2 @@
+       -foo6
+       +new
+       +modification
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content (untracked ignored)' '
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains modified content
+       Submodule sm1 17243c9..cfce562:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..e20e2d9 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1,3 @@
+       -foo6
+       +new
+       +modification
+       +modification
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content (dirty ignored)' '
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9..cfce562:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..3e75765 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1 @@
+       -foo6
+       +new
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content (all ignored)' '
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --ignore-submodules --submodule=diff HEAD >actual &&
+       ! test -s actual
+'
+
+# NOT OK
+test_expect_success 'modified submodule contains modified content' '
+       rm -f sm1/new-file &&
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 contains modified content
+       Submodule sm1 17243c9..cfce562:
+       diff --git a/sm1/foo6 b/sm1/foo6
+       index 462398b..ac466ca 100644
+       --- a/sm1/foo6
+       +++ b/sm1/foo6
+       @@ -1 +1,5 @@
+       -foo6
+       +new
+       +modification
+       +modification
+       +modification
+       +modification
+       EOF
+       test_cmp expected actual
+'
+
+rm -rf sm1
+test_expect_success 'deleted submodule' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9...0000000 (submodule deleted)
+       EOF
+       test_cmp expected actual
+'
+
+test_create_repo sm2 &&
+head7=$(add_file sm2 foo8 foo9) &&
+git add sm2
+
+test_expect_success 'multiple submodules' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9...0000000 (submodule deleted)
+       Submodule sm2 0000000...a5a65c9 (new submodule)
+       diff --git a/sm2/foo8 b/sm2/foo8
+       new file mode 100644
+       index 0000000..db9916b
+       --- /dev/null
+       +++ b/sm2/foo8
+       @@ -0,0 +1 @@
+       +foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       new file mode 100644
+       index 0000000..9c3b4f6
+       --- /dev/null
+       +++ b/sm2/foo9
+       @@ -0,0 +1 @@
+       +foo9
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'path filter' '
+       git diff-index -p --submodule=diff HEAD sm2 >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm2 0000000...a5a65c9 (new submodule)
+       diff --git a/sm2/foo8 b/sm2/foo8
+       new file mode 100644
+       index 0000000..db9916b
+       --- /dev/null
+       +++ b/sm2/foo8
+       @@ -0,0 +1 @@
+       +foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       new file mode 100644
+       index 0000000..9c3b4f6
+       --- /dev/null
+       +++ b/sm2/foo9
+       @@ -0,0 +1 @@
+       +foo9
+       EOF
+       test_cmp expected actual
+'
+
+commit_file sm2
+test_expect_success 'given commit' '
+       git diff-index -p --submodule=diff HEAD^ >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9...0000000 (submodule deleted)
+       Submodule sm2 0000000...a5a65c9 (new submodule)
+       diff --git a/sm2/foo8 b/sm2/foo8
+       new file mode 100644
+       index 0000000..db9916b
+       --- /dev/null
+       +++ b/sm2/foo8
+       @@ -0,0 +1 @@
+       +foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       new file mode 100644
+       index 0000000..9c3b4f6
+       --- /dev/null
+       +++ b/sm2/foo9
+       @@ -0,0 +1 @@
+       +foo9
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'setup .git file for sm2' '
+       (cd sm2 &&
+        REAL="$(pwd)/../.real" &&
+        mv .git "$REAL"
+        echo "gitdir: $REAL" >.git)
+'
+
+test_expect_success 'diff --submodule=diff with .git file' '
+       git diff --submodule=diff HEAD^ >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 17243c9...0000000 (submodule deleted)
+       Submodule sm2 0000000...a5a65c9 (new submodule)
+       diff --git a/sm2/foo8 b/sm2/foo8
+       new file mode 100644
+       index 0000000..db9916b
+       --- /dev/null
+       +++ b/sm2/foo8
+       @@ -0,0 +1 @@
+       +foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       new file mode 100644
+       index 0000000..9c3b4f6
+       --- /dev/null
+       +++ b/sm2/foo9
+       @@ -0,0 +1 @@
+       +foo9
+       EOF
+       test_cmp expected actual
+'
+
+test_done
index e2db47c36e09e3580c53867909cd3e20bdf320f5..1ccbd5948a735f692469e181933c97e8632404b4 100755 (executable)
@@ -187,6 +187,16 @@ test_expect_success 'git log --no-walk=sorted <commits> sorts by commit time' '
        test_cmp expect actual
 '
 
+cat > expect << EOF
+=== 804a787 sixth
+=== 394ef78 fifth
+=== 5d31159 fourth
+EOF
+test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' '
+       git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+       test_cmp expect actual
+'
+
 cat > expect << EOF
 5d31159 fourth
 804a787 sixth
@@ -284,6 +294,21 @@ test_expect_success 'simple log --graph' '
        test_cmp expect actual
 '
 
+cat > expect <<EOF
+123 * Second
+123 * sixth
+123 * fifth
+123 * fourth
+123 * third
+123 * second
+123 * initial
+EOF
+
+test_expect_success 'simple log --graph --line-prefix="123 "' '
+       git log --graph --line-prefix="123 " --pretty=tformat:%s >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'set up merge history' '
        git checkout -b side HEAD~4 &&
        test_commit side-1 1 1 &&
@@ -313,6 +338,27 @@ test_expect_success 'log --graph with merge' '
        test_cmp expect actual
 '
 
+cat > expect <<\EOF
+| | | *   Merge branch 'side'
+| | | |\
+| | | | * side-2
+| | | | * side-1
+| | | * | Second
+| | | * | sixth
+| | | * | fifth
+| | | * | fourth
+| | | |/
+| | | * third
+| | | * second
+| | | * initial
+EOF
+
+test_expect_success 'log --graph --line-prefix="| | | " with merge' '
+       git log --line-prefix="| | | " --graph --date-order --pretty=tformat:%s |
+               sed "s/ *\$//" >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'log --raw --graph -m with merge' '
        git log --raw --graph --oneline -m master | head -n 500 >actual &&
        grep "initial" actual
@@ -867,6 +913,283 @@ test_expect_success 'log --graph with diff and stats' '
        test_i18ncmp expect actual.sanitized
 '
 
+cat >expect <<\EOF
+*** *   commit COMMIT_OBJECT_NAME
+*** |\  Merge: MERGE_PARENTS
+*** | | Author: A U Thor <author@example.com>
+*** | |
+*** | |     Merge HEADS DESCRIPTION
+*** | |
+*** | * commit COMMIT_OBJECT_NAME
+*** | | Author: A U Thor <author@example.com>
+*** | |
+*** | |     reach
+*** | | ---
+*** | |  reach.t | 1 +
+*** | |  1 file changed, 1 insertion(+)
+*** | |
+*** | | diff --git a/reach.t b/reach.t
+*** | | new file mode 100644
+*** | | index 0000000..10c9591
+*** | | --- /dev/null
+*** | | +++ b/reach.t
+*** | | @@ -0,0 +1 @@
+*** | | +reach
+*** | |
+*** |  \
+*** *-. \   commit COMMIT_OBJECT_NAME
+*** |\ \ \  Merge: MERGE_PARENTS
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     Merge HEADS DESCRIPTION
+*** | | | |
+*** | | * | commit COMMIT_OBJECT_NAME
+*** | | |/  Author: A U Thor <author@example.com>
+*** | | |
+*** | | |       octopus-b
+*** | | |   ---
+*** | | |    octopus-b.t | 1 +
+*** | | |    1 file changed, 1 insertion(+)
+*** | | |
+*** | | |   diff --git a/octopus-b.t b/octopus-b.t
+*** | | |   new file mode 100644
+*** | | |   index 0000000..d5fcad0
+*** | | |   --- /dev/null
+*** | | |   +++ b/octopus-b.t
+*** | | |   @@ -0,0 +1 @@
+*** | | |   +octopus-b
+*** | | |
+*** | * | commit COMMIT_OBJECT_NAME
+*** | |/  Author: A U Thor <author@example.com>
+*** | |
+*** | |       octopus-a
+*** | |   ---
+*** | |    octopus-a.t | 1 +
+*** | |    1 file changed, 1 insertion(+)
+*** | |
+*** | |   diff --git a/octopus-a.t b/octopus-a.t
+*** | |   new file mode 100644
+*** | |   index 0000000..11ee015
+*** | |   --- /dev/null
+*** | |   +++ b/octopus-a.t
+*** | |   @@ -0,0 +1 @@
+*** | |   +octopus-a
+*** | |
+*** * | commit COMMIT_OBJECT_NAME
+*** |/  Author: A U Thor <author@example.com>
+*** |
+*** |       seventh
+*** |   ---
+*** |    seventh.t | 1 +
+*** |    1 file changed, 1 insertion(+)
+*** |
+*** |   diff --git a/seventh.t b/seventh.t
+*** |   new file mode 100644
+*** |   index 0000000..9744ffc
+*** |   --- /dev/null
+*** |   +++ b/seventh.t
+*** |   @@ -0,0 +1 @@
+*** |   +seventh
+*** |
+*** *   commit COMMIT_OBJECT_NAME
+*** |\  Merge: MERGE_PARENTS
+*** | | Author: A U Thor <author@example.com>
+*** | |
+*** | |     Merge branch 'tangle'
+*** | |
+*** | *   commit COMMIT_OBJECT_NAME
+*** | |\  Merge: MERGE_PARENTS
+*** | | | Author: A U Thor <author@example.com>
+*** | | |
+*** | | |     Merge branch 'side' (early part) into tangle
+*** | | |
+*** | * |   commit COMMIT_OBJECT_NAME
+*** | |\ \  Merge: MERGE_PARENTS
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     Merge branch 'master' (early part) into tangle
+*** | | | |
+*** | * | | commit COMMIT_OBJECT_NAME
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     tangle-a
+*** | | | | ---
+*** | | | |  tangle-a | 1 +
+*** | | | |  1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | | diff --git a/tangle-a b/tangle-a
+*** | | | | new file mode 100644
+*** | | | | index 0000000..7898192
+*** | | | | --- /dev/null
+*** | | | | +++ b/tangle-a
+*** | | | | @@ -0,0 +1 @@
+*** | | | | +a
+*** | | | |
+*** * | | |   commit COMMIT_OBJECT_NAME
+*** |\ \ \ \  Merge: MERGE_PARENTS
+*** | | | | | Author: A U Thor <author@example.com>
+*** | | | | |
+*** | | | | |     Merge branch 'side'
+*** | | | | |
+*** | * | | | commit COMMIT_OBJECT_NAME
+*** | | |_|/  Author: A U Thor <author@example.com>
+*** | |/| |
+*** | | | |       side-2
+*** | | | |   ---
+*** | | | |    2 | 1 +
+*** | | | |    1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | |   diff --git a/2 b/2
+*** | | | |   new file mode 100644
+*** | | | |   index 0000000..0cfbf08
+*** | | | |   --- /dev/null
+*** | | | |   +++ b/2
+*** | | | |   @@ -0,0 +1 @@
+*** | | | |   +2
+*** | | | |
+*** | * | | commit COMMIT_OBJECT_NAME
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     side-1
+*** | | | | ---
+*** | | | |  1 | 1 +
+*** | | | |  1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | | diff --git a/1 b/1
+*** | | | | new file mode 100644
+*** | | | | index 0000000..d00491f
+*** | | | | --- /dev/null
+*** | | | | +++ b/1
+*** | | | | @@ -0,0 +1 @@
+*** | | | | +1
+*** | | | |
+*** * | | | commit COMMIT_OBJECT_NAME
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     Second
+*** | | | | ---
+*** | | | |  one | 1 +
+*** | | | |  1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | | diff --git a/one b/one
+*** | | | | new file mode 100644
+*** | | | | index 0000000..9a33383
+*** | | | | --- /dev/null
+*** | | | | +++ b/one
+*** | | | | @@ -0,0 +1 @@
+*** | | | | +case
+*** | | | |
+*** * | | | commit COMMIT_OBJECT_NAME
+*** | |_|/  Author: A U Thor <author@example.com>
+*** |/| |
+*** | | |       sixth
+*** | | |   ---
+*** | | |    a/two | 1 -
+*** | | |    1 file changed, 1 deletion(-)
+*** | | |
+*** | | |   diff --git a/a/two b/a/two
+*** | | |   deleted file mode 100644
+*** | | |   index 9245af5..0000000
+*** | | |   --- a/a/two
+*** | | |   +++ /dev/null
+*** | | |   @@ -1 +0,0 @@
+*** | | |   -ni
+*** | | |
+*** * | | commit COMMIT_OBJECT_NAME
+*** | | | Author: A U Thor <author@example.com>
+*** | | |
+*** | | |     fifth
+*** | | | ---
+*** | | |  a/two | 1 +
+*** | | |  1 file changed, 1 insertion(+)
+*** | | |
+*** | | | diff --git a/a/two b/a/two
+*** | | | new file mode 100644
+*** | | | index 0000000..9245af5
+*** | | | --- /dev/null
+*** | | | +++ b/a/two
+*** | | | @@ -0,0 +1 @@
+*** | | | +ni
+*** | | |
+*** * | | commit COMMIT_OBJECT_NAME
+*** |/ /  Author: A U Thor <author@example.com>
+*** | |
+*** | |       fourth
+*** | |   ---
+*** | |    ein | 1 +
+*** | |    1 file changed, 1 insertion(+)
+*** | |
+*** | |   diff --git a/ein b/ein
+*** | |   new file mode 100644
+*** | |   index 0000000..9d7e69f
+*** | |   --- /dev/null
+*** | |   +++ b/ein
+*** | |   @@ -0,0 +1 @@
+*** | |   +ichi
+*** | |
+*** * | commit COMMIT_OBJECT_NAME
+*** |/  Author: A U Thor <author@example.com>
+*** |
+*** |       third
+*** |   ---
+*** |    ichi | 1 +
+*** |    one  | 1 -
+*** |    2 files changed, 1 insertion(+), 1 deletion(-)
+*** |
+*** |   diff --git a/ichi b/ichi
+*** |   new file mode 100644
+*** |   index 0000000..9d7e69f
+*** |   --- /dev/null
+*** |   +++ b/ichi
+*** |   @@ -0,0 +1 @@
+*** |   +ichi
+*** |   diff --git a/one b/one
+*** |   deleted file mode 100644
+*** |   index 9d7e69f..0000000
+*** |   --- a/one
+*** |   +++ /dev/null
+*** |   @@ -1 +0,0 @@
+*** |   -ichi
+*** |
+*** * commit COMMIT_OBJECT_NAME
+*** | Author: A U Thor <author@example.com>
+*** |
+*** |     second
+*** | ---
+*** |  one | 2 +-
+*** |  1 file changed, 1 insertion(+), 1 deletion(-)
+*** |
+*** | diff --git a/one b/one
+*** | index 5626abf..9d7e69f 100644
+*** | --- a/one
+*** | +++ b/one
+*** | @@ -1 +1 @@
+*** | -one
+*** | +ichi
+*** |
+*** * commit COMMIT_OBJECT_NAME
+***   Author: A U Thor <author@example.com>
+***
+***       initial
+***   ---
+***    one | 1 +
+***    1 file changed, 1 insertion(+)
+***
+***   diff --git a/one b/one
+***   new file mode 100644
+***   index 0000000..5626abf
+***   --- /dev/null
+***   +++ b/one
+***   @@ -0,0 +1 @@
+***   +one
+EOF
+
+test_expect_success 'log --line-prefix="*** " --graph with diff and stats' '
+       git log --line-prefix="*** " --no-renames --graph --pretty=short --stat -p >actual &&
+       sanitize_output >actual.sanitized <actual &&
+       test_i18ncmp expect actual.sanitized
+'
+
 test_expect_success 'dotdot is a parent directory' '
        mkdir -p a/b &&
        ( echo sixth && echo fifth ) >expect &&
index 954d0e43f52375b1ed86a4edb9d274714d3b596d..f3b0a8d30afcb472398bef8623a49ba6bac7dc24 100755 (executable)
@@ -485,4 +485,39 @@ test_expect_success 'fetching submodules respects parallel settings' '
        )
 '
 
+test_expect_success 'fetching submodule into a broken repository' '
+       # Prepare src and src/sub nested in it
+       git init src &&
+       (
+               cd src &&
+               git init sub &&
+               git -C sub commit --allow-empty -m "initial in sub" &&
+               git submodule add -- ./sub sub &&
+               git commit -m "initial in top"
+       ) &&
+
+       # Clone the old-fashoned way
+       git clone src dst &&
+       git -C dst clone ../src/sub sub &&
+
+       # Make sure that old-fashoned layout is still supported
+       git -C dst status &&
+
+       # "diff" would find no change
+       git -C dst diff --exit-code &&
+
+       # Recursive-fetch works fine
+       git -C dst fetch --recurse-submodules &&
+
+       # Break the receiving submodule
+       rm -f dst/sub/.git/HEAD &&
+
+       # NOTE: without the fix the following tests will recurse forever!
+       # They should terminate with an error.
+
+       test_must_fail git -C dst status &&
+       test_must_fail git -C dst diff &&
+       test_must_fail git -C dst fetch --recurse-submodules
+'
+
 test_done
diff --git a/t/t5546-receive-limits.sh b/t/t5546-receive-limits.sh
new file mode 100755 (executable)
index 0000000..10cb0be
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='check receive input limits'
+. ./test-lib.sh
+
+# Let's run tests with different unpack limits: 1 and 10000
+# When the limit is 1, `git receive-pack` will call `git index-pack`.
+# When the limit is 10000, `git receive-pack` will call `git unpack-objects`.
+
+test_pack_input_limit () {
+       case "$1" in
+       index) unpack_limit=1 ;;
+       unpack) unpack_limit=10000 ;;
+       esac
+
+       test_expect_success 'prepare destination repository' '
+               rm -fr dest &&
+               git --bare init dest
+       '
+
+       test_expect_success "set unpacklimit to $unpack_limit" '
+               git --git-dir=dest config receive.unpacklimit "$unpack_limit"
+       '
+
+       test_expect_success 'setting receive.maxInputSize to 512 rejects push' '
+               git --git-dir=dest config receive.maxInputSize 512 &&
+               test_must_fail git push dest HEAD
+       '
+
+       test_expect_success 'bumping limit to 4k allows push' '
+               git --git-dir=dest config receive.maxInputSize 4k &&
+               git push dest HEAD
+       '
+
+       test_expect_success 'prepare destination repository (again)' '
+               rm -fr dest &&
+               git --bare init dest
+       '
+
+       test_expect_success 'lifting the limit allows push' '
+               git --git-dir=dest config receive.maxInputSize 0 &&
+               git push dest HEAD
+       '
+}
+
+test_expect_success "create known-size (1024 bytes) commit" '
+       test-genrandom foo 1024 >one-k &&
+       git add one-k &&
+       test_commit one-k
+'
+
+test_pack_input_limit index
+test_pack_input_limit unpack
+
+test_done
index 4d17363a926c8938c6c8d78e997a53ffb81b3c6c..53cf42fac19c83f2da7de1cd5b6c4ca0cf6e5ccf 100755 (executable)
@@ -232,4 +232,25 @@ test_expect_success 'status --branch with detached HEAD' '
        test_i18ncmp expected actual
 '
 
+## Duplicate the above test and verify --porcelain=v1 arg parsing.
+test_expect_success 'status --porcelain=v1 --branch with detached HEAD' '
+       git reset --hard &&
+       git checkout master^0 &&
+       git status --branch --porcelain=v1 >actual &&
+       cat >expected <<-EOF &&
+       ## HEAD (no branch)
+       ?? .gitconfig
+       ?? actual
+       ?? expect
+       ?? expected
+       ?? mdconflict/
+       EOF
+       test_i18ncmp expected actual
+'
+
+## Verify parser error on invalid --porcelain argument.
+test_expect_success 'status --porcelain=bogus' '
+       test_must_fail git status --porcelain=bogus
+'
+
 test_done
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
new file mode 100755 (executable)
index 0000000..3012a4d
--- /dev/null
@@ -0,0 +1,593 @@
+#!/bin/sh
+
+test_description='git status --porcelain=v2
+
+This test exercises porcelain V2 output for git status.'
+
+
+. ./test-lib.sh
+
+
+test_expect_success setup '
+       test_tick &&
+       git config core.autocrlf false &&
+       echo x >file_x &&
+       echo y >file_y &&
+       echo z >file_z &&
+       mkdir dir1 &&
+       echo a >dir1/file_a &&
+       echo b >dir1/file_b
+'
+
+test_expect_success 'before initial commit, nothing added, only untracked' '
+       cat >expect <<-EOF &&
+       # branch.oid (initial)
+       # branch.head master
+       ? actual
+       ? dir1/
+       ? expect
+       ? file_x
+       ? file_y
+       ? file_z
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=normal >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'before initial commit, things added' '
+       git add file_x file_y file_z dir1 &&
+       OID_A=$(git hash-object -t blob -- dir1/file_a) &&
+       OID_B=$(git hash-object -t blob -- dir1/file_b) &&
+       OID_X=$(git hash-object -t blob -- file_x) &&
+       OID_Y=$(git hash-object -t blob -- file_y) &&
+       OID_Z=$(git hash-object -t blob -- file_z) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid (initial)
+       # branch.head master
+       1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a
+       1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b
+       1 A. N... 000000 100644 100644 $_z40 $OID_X file_x
+       1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y
+       1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'before initial commit, things added (-z)' '
+       lf_to_nul >expect <<-EOF &&
+       # branch.oid (initial)
+       # branch.head master
+       1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a
+       1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b
+       1 A. N... 000000 100644 100644 $_z40 $OID_X file_x
+       1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y
+       1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z
+       ? actual
+       ? expect
+       EOF
+
+       git status -z --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'make first commit, comfirm HEAD oid and branch' '
+       git commit -m initial &&
+       H0=$(git rev-parse HEAD) &&
+       cat >expect <<-EOF &&
+       # branch.oid $H0
+       # branch.head master
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'after first commit, create unstaged changes' '
+       echo x >>file_x &&
+       OID_X1=$(git hash-object -t blob -- file_x) &&
+       rm file_z &&
+       H0=$(git rev-parse HEAD) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid $H0
+       # branch.head master
+       1 .M N... 100644 100644 100644 $OID_X $OID_X file_x
+       1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'after first commit but omit untracked files and branch' '
+       cat >expect <<-EOF &&
+       1 .M N... 100644 100644 100644 $OID_X $OID_X file_x
+       1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z
+       EOF
+
+       git status --porcelain=v2 --untracked-files=no >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'after first commit, stage existing changes' '
+       git add file_x &&
+       git rm file_z &&
+       H0=$(git rev-parse HEAD) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid $H0
+       # branch.head master
+       1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
+       1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rename causes 2 path lines' '
+       git mv file_y renamed_y &&
+       H0=$(git rev-parse HEAD) &&
+
+       q_to_tab >expect <<-EOF &&
+       # branch.oid $H0
+       # branch.head master
+       1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
+       1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
+       2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rename causes 2 path lines (-z)' '
+       H0=$(git rev-parse HEAD) &&
+
+       ## Lines use NUL path separator and line terminator, so double transform here.
+       q_to_nul <<-EOF | lf_to_nul >expect &&
+       # branch.oid $H0
+       # branch.head master
+       1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
+       1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
+       2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all -z >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'make second commit, confirm clean and new HEAD oid' '
+       git commit -m second &&
+       H1=$(git rev-parse HEAD) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid $H1
+       # branch.head master
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'confirm ignored files are not printed' '
+       test_when_finished "rm -f x.ign .gitignore" &&
+       echo x.ign >.gitignore &&
+       echo "ignore me" >x.ign &&
+
+       cat >expect <<-EOF &&
+       ? .gitignore
+       ? actual
+       ? expect
+       EOF
+
+       git status --porcelain=v2 --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'ignored files are printed with --ignored' '
+       test_when_finished "rm -f x.ign .gitignore" &&
+       echo x.ign >.gitignore &&
+       echo "ignore me" >x.ign &&
+
+       cat >expect <<-EOF &&
+       ? .gitignore
+       ? actual
+       ? expect
+       ! x.ign
+       EOF
+
+       git status --porcelain=v2 --ignored --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create and commit permanent ignore file' '
+       cat >.gitignore <<-EOF &&
+       actual*
+       expect*
+       EOF
+
+       git add .gitignore &&
+       git commit -m ignore_trash &&
+       H1=$(git rev-parse HEAD) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid $H1
+       # branch.head master
+       EOF
+
+       git status --porcelain=v2 --branch >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verify --intent-to-add output' '
+       test_when_finished "git rm -f intent1.add intent2.add" &&
+       touch intent1.add &&
+       echo test >intent2.add &&
+
+       git add --intent-to-add intent1.add intent2.add &&
+
+       cat >expect <<-EOF &&
+       1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent1.add
+       1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent2.add
+       EOF
+
+       git status --porcelain=v2 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verify AA (add-add) conflict' '
+       test_when_finished "git reset --hard" &&
+
+       git branch AA_A master &&
+       git checkout AA_A &&
+       echo "Branch AA_A" >conflict.txt &&
+       OID_AA_A=$(git hash-object -t blob -- conflict.txt) &&
+       git add conflict.txt &&
+       git commit -m "branch aa_a" &&
+
+       git branch AA_B master &&
+       git checkout AA_B &&
+       echo "Branch AA_B" >conflict.txt &&
+       OID_AA_B=$(git hash-object -t blob -- conflict.txt) &&
+       git add conflict.txt &&
+       git commit -m "branch aa_b" &&
+
+       git branch AA_M AA_B &&
+       git checkout AA_M &&
+       test_must_fail git merge AA_A &&
+
+       HM=$(git rev-parse HEAD) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid $HM
+       # branch.head AA_M
+       u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verify UU (edit-edit) conflict' '
+       test_when_finished "git reset --hard" &&
+
+       git branch UU_ANC master &&
+       git checkout UU_ANC &&
+       echo "Ancestor" >conflict.txt &&
+       OID_UU_ANC=$(git hash-object -t blob -- conflict.txt) &&
+       git add conflict.txt &&
+       git commit -m "UU_ANC" &&
+
+       git branch UU_A UU_ANC &&
+       git checkout UU_A &&
+       echo "Branch UU_A" >conflict.txt &&
+       OID_UU_A=$(git hash-object -t blob -- conflict.txt) &&
+       git add conflict.txt &&
+       git commit -m "branch uu_a" &&
+
+       git branch UU_B UU_ANC &&
+       git checkout UU_B &&
+       echo "Branch UU_B" >conflict.txt &&
+       OID_UU_B=$(git hash-object -t blob -- conflict.txt) &&
+       git add conflict.txt &&
+       git commit -m "branch uu_b" &&
+
+       git branch UU_M UU_B &&
+       git checkout UU_M &&
+       test_must_fail git merge UU_A &&
+
+       HM=$(git rev-parse HEAD) &&
+
+       cat >expect <<-EOF &&
+       # branch.oid $HM
+       # branch.head UU_M
+       u UU N... 100644 100644 100644 100644 $OID_UU_ANC $OID_UU_B $OID_UU_A conflict.txt
+       EOF
+
+       git status --porcelain=v2 --branch --untracked-files=all >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verify upstream fields in branch header' '
+       git checkout master &&
+       test_when_finished "rm -rf sub_repo" &&
+       git clone . sub_repo &&
+       (
+               ## Confirm local master tracks remote master.
+               cd sub_repo &&
+               HUF=$(git rev-parse HEAD) &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HUF
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual &&
+
+               ## Test ahead/behind.
+               echo xyz >file_xyz &&
+               git add file_xyz &&
+               git commit -m xyz &&
+
+               HUF=$(git rev-parse HEAD) &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HUF
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +1 -0
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual &&
+
+               ## Repeat the above but without --branch.
+               cat >expect <<-EOF &&
+               EOF
+
+               git status --porcelain=v2 --untracked-files=all >actual &&
+               test_cmp expect actual &&
+
+               ## Test upstream-gone case. Fake this by pointing origin/master at
+               ## a non-existing commit.
+               OLD=$(git rev-parse origin/master) &&
+               NEW=$_z40 &&
+               mv .git/packed-refs .git/old-packed-refs &&
+               sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs &&
+
+               HUF=$(git rev-parse HEAD) &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HUF
+               # branch.head master
+               # branch.upstream origin/master
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'create and add submodule, submodule appears clean (A. S...)' '
+       git checkout master &&
+       git clone . sub_repo &&
+       git clone . super_repo &&
+       (       cd super_repo &&
+               git submodule add ../sub_repo sub1 &&
+
+               ## Confirm stage/add of clean submodule.
+               HMOD=$(git hash-object -t blob -- .gitmodules) &&
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$HSUP &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
+               1 A. S... 000000 160000 160000 $_z40 $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'untracked changes in added submodule (AM S..U)' '
+       (       cd super_repo &&
+               ## create untracked file in the submodule.
+               (       cd sub1 &&
+                       echo "xxxx" >file_in_sub
+               ) &&
+
+               HMOD=$(git hash-object -t blob -- .gitmodules) &&
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$HSUP &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
+               1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'staged changes in added submodule (AM S.M.)' '
+       (       cd super_repo &&
+               ## stage the changes in the submodule.
+               (       cd sub1 &&
+                       git add file_in_sub
+               ) &&
+
+               HMOD=$(git hash-object -t blob -- .gitmodules) &&
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$HSUP &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
+               1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'staged and unstaged changes in added (AM S.M.)' '
+       (       cd super_repo &&
+               (       cd sub1 &&
+                       ## make additional unstaged changes (on the same file) in the submodule.
+                       ## This does not cause us to get S.MU (because the submodule does not report
+                       ## a "?" line for the unstaged changes).
+                       echo "more changes" >>file_in_sub
+               ) &&
+
+               HMOD=$(git hash-object -t blob -- .gitmodules) &&
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$HSUP &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
+               1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' '
+       (       cd super_repo &&
+               (       cd sub1 &&
+                       ## stage new changes in tracked file.
+                       git add file_in_sub &&
+                       ## create new untracked file.
+                       echo "yyyy" >>another_file_in_sub
+               ) &&
+
+               HMOD=$(git hash-object -t blob -- .gitmodules) &&
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$HSUP &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
+               1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'commit within the submodule appears as new commit in super (AM SC..)' '
+       (       cd super_repo &&
+               (       cd sub1 &&
+                       ## Make a new commit in the submodule.
+                       git add file_in_sub &&
+                       rm -f another_file_in_sub &&
+                       git commit -m "new commit"
+               ) &&
+
+               HMOD=$(git hash-object -t blob -- .gitmodules) &&
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$HSUP &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +0 -0
+               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
+               1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'stage submodule in super and commit' '
+       (       cd super_repo &&
+               ## Stage the new submodule commit in the super.
+               git add sub1 &&
+               ## Commit the super so that the sub no longer appears as added.
+               git commit -m "super commit" &&
+
+               HSUP=$(git rev-parse HEAD) &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +1 -0
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'make unstaged changes in existing submodule (.M S.M.)' '
+       (       cd super_repo &&
+               (       cd sub1 &&
+                       echo "zzzz" >>file_in_sub
+               ) &&
+
+               HSUP=$(git rev-parse HEAD) &&
+               HSUB=$(cd sub1 && git rev-parse HEAD) &&
+
+               cat >expect <<-EOF &&
+               # branch.oid $HSUP
+               # branch.head master
+               # branch.upstream origin/master
+               # branch.ab +1 -0
+               1 .M S.M. 160000 160000 160000 $HSUB $HSUB sub1
+               EOF
+
+               git status --porcelain=v2 --branch --untracked-files=all >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_done
index eaea19b8f291ef6b9f13a05342dc10317f3bfbe9..1c1e289ffd982ab74ed80fb776b3ecc4d7abf2f1 100755 (executable)
@@ -8,74 +8,121 @@ test_description='test clone --reference'
 
 base_dir=$(pwd)
 
-U=$base_dir/UPLOAD_LOG
-
-test_expect_success 'preparing first repository' \
-'test_create_repo A && cd A &&
-echo first > file1 &&
-git add file1 &&
-git commit -m A-initial'
-
-cd "$base_dir"
-
-test_expect_success 'preparing second repository' \
-'git clone A B && cd B &&
-echo second > file2 &&
-git add file2 &&
-git commit -m B-addition &&
-git repack -a -d &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'preparing superproject' \
-'test_create_repo super && cd super &&
-echo file > file &&
-git add file &&
-git commit -m B-super-initial'
-
-cd "$base_dir"
-
-test_expect_success 'submodule add --reference' \
-'cd super && git submodule add --reference ../B "file://$base_dir/A" sub &&
-git commit -m B-super-added'
-
-cd "$base_dir"
-
-test_expect_success 'after add: existence of info/alternates' \
-'test_line_count = 1 super/.git/modules/sub/objects/info/alternates'
-
-cd "$base_dir"
-
-test_expect_success 'that reference gets used with add' \
-'cd super/sub &&
-echo "0 objects, 0 kilobytes" > expected &&
-git count-objects > current &&
-diff expected current'
-
-cd "$base_dir"
-
-test_expect_success 'cloning superproject' \
-'git clone super super-clone'
-
-cd "$base_dir"
-
-test_expect_success 'update with reference' \
-'cd super-clone && git submodule update --init --reference ../B'
-
-cd "$base_dir"
-
-test_expect_success 'after update: existence of info/alternates' \
-'test_line_count = 1 super-clone/.git/modules/sub/objects/info/alternates'
-
-cd "$base_dir"
-
-test_expect_success 'that reference gets used with update' \
-'cd super-clone/sub &&
-echo "0 objects, 0 kilobytes" > expected &&
-git count-objects > current &&
-diff expected current'
-
-cd "$base_dir"
+test_alternate_is_used () {
+       alternates_file="$1" &&
+       working_dir="$2" &&
+       test_line_count = 1 "$alternates_file" &&
+       echo "0 objects, 0 kilobytes" >expect &&
+       git -C "$working_dir" count-objects >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'preparing first repository' '
+       test_create_repo A &&
+       (
+               cd A &&
+               echo first >file1 &&
+               git add file1 &&
+               git commit -m A-initial
+       )
+'
+
+test_expect_success 'preparing second repository' '
+       git clone A B &&
+       (
+               cd B &&
+               echo second >file2 &&
+               git add file2 &&
+               git commit -m B-addition &&
+               git repack -a -d &&
+               git prune
+       )
+'
+
+test_expect_success 'preparing superproject' '
+       test_create_repo super &&
+       (
+               cd super &&
+               echo file >file &&
+               git add file &&
+               git commit -m B-super-initial
+       )
+'
+
+test_expect_success 'submodule add --reference uses alternates' '
+       (
+               cd super &&
+               git submodule add --reference ../B "file://$base_dir/A" sub &&
+               git commit -m B-super-added &&
+               git repack -ad
+       ) &&
+       test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub
+'
+
+test_expect_success 'that reference gets used with add' '
+       (
+               cd super/sub &&
+               echo "0 objects, 0 kilobytes" >expected &&
+               git count-objects >current &&
+               diff expected current
+       )
+'
+
+# The tests up to this point, and repositories created by them
+# (A, B, super and super/sub), are about setting up the stage
+# for subsequent tests and meant to be kept throughout the
+# remainder of the test.
+# Tests from here on, if they create their own test repository,
+# are expected to clean after themselves.
+
+test_expect_success 'updating superproject keeps alternates' '
+       test_when_finished "rm -rf super-clone" &&
+       git clone super super-clone &&
+       git -C super-clone submodule update --init --reference ../B &&
+       test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub
+'
+
+test_expect_success 'submodules use alternates when cloning a superproject' '
+       test_when_finished "rm -rf super-clone" &&
+       git clone --reference super --recursive super super-clone &&
+       (
+               cd super-clone &&
+               # test superproject has alternates setup correctly
+               test_alternate_is_used .git/objects/info/alternates . &&
+               # test submodule has correct setup
+               test_alternate_is_used .git/modules/sub/objects/info/alternates sub
+       )
+'
+
+test_expect_success 'missing submodule alternate fails clone and submodule update' '
+       test_when_finished "rm -rf super-clone" &&
+       git clone super super2 &&
+       test_must_fail git clone --recursive --reference super2 super2 super-clone &&
+       (
+               cd super-clone &&
+               # test superproject has alternates setup correctly
+               test_alternate_is_used .git/objects/info/alternates . &&
+               # update of the submodule succeeds
+               test_must_fail git submodule update --init &&
+               # and we have no alternates:
+               test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub &&
+               test_must_fail test_path_is_file sub/file1
+       )
+'
+
+test_expect_success 'ignoring missing submodule alternates passes clone and submodule update' '
+       test_when_finished "rm -rf super-clone" &&
+       git clone --reference-if-able super2 --recursive super2 super-clone &&
+       (
+               cd super-clone &&
+               # test superproject has alternates setup correctly
+               test_alternate_is_used .git/objects/info/alternates . &&
+               # update of the submodule succeeds
+               git submodule update --init &&
+               # and we have no alternates:
+               test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub &&
+               test_path_is_file sub/file1
+       )
+'
 
 test_done
index 0db4469c89492ae625307629cb7132178c3b9671..97c9b32c2ecfa608f1e9c37c1c6402a1772e4187 100755 (executable)
@@ -177,7 +177,7 @@ test_expect_success 'prompt - interactive rebase' '
        git checkout b1 &&
        test_when_finished "git checkout master" &&
        git rebase -i HEAD^ &&
-       test_when_finished "git rebase --abort"
+       test_when_finished "git rebase --abort" &&
        __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
index 4f7eadb5963e7d698f9ee6e265b1657e543cdb44..fdaeb3a96bed361f53030cb6ea5c6bdde445163f 100644 (file)
@@ -81,6 +81,10 @@ test_decode_color () {
        '
 }
 
+lf_to_nul () {
+       perl -pe 'y/\012/\000/'
+}
+
 nul_to_q () {
        perl -pe 'y/\000/Q/'
 }
index d731d66e3673f0e9d990007dd690c14d84eae783..eada492d884a2558ff81aa6731db727c4cac3f8a 100644 (file)
@@ -687,9 +687,9 @@ test_done () {
                test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results"
                mkdir -p "$test_results_dir"
                base=${0##*/}
-               test_results_path="$test_results_dir/${base%.sh}-$$.counts"
+               test_results_path="$test_results_dir/${base%.sh}.counts"
 
-               cat >>"$test_results_path" <<-EOF
+               cat >"$test_results_path" <<-EOF
                total $test_count
                success $test_success
                fixed $test_fixed
diff --git a/url.c b/url.c
index 2d89ad190cfe1c57cd20194661329551ff229993..eaf4f07081eae122fbc38e05f60ee2f11a0c65a6 100644 (file)
--- a/url.c
+++ b/url.c
@@ -29,25 +29,6 @@ int is_url(const char *url)
        return (url[0] == ':' && url[1] == '/' && url[2] == '/');
 }
 
-static int url_decode_char(const char *q)
-{
-       int i;
-       unsigned char val = 0;
-       for (i = 0; i < 2; i++) {
-               unsigned char c = *q++;
-               val <<= 4;
-               if (c >= '0' && c <= '9')
-                       val += c - '0';
-               else if (c >= 'a' && c <= 'f')
-                       val += c - 'a' + 10;
-               else if (c >= 'A' && c <= 'F')
-                       val += c - 'A' + 10;
-               else
-                       return -1;
-       }
-       return val;
-}
-
 static char *url_decode_internal(const char **query, int len,
                                 const char *stop_at, struct strbuf *out,
                                 int decode_plus)
@@ -66,7 +47,7 @@ static char *url_decode_internal(const char **query, int len,
                }
 
                if (c == '%') {
-                       int val = url_decode_char(q + 1);
+                       int val = hex2chr(q + 1);
                        if (0 <= val) {
                                strbuf_addch(out, val);
                                q += 3;
diff --git a/usage.c b/usage.c
index 1dad03fb5c9b7f8f7785915d004aff43e39b0d2a..0efa3faf601231db35239161a1a7253b3975b3f8 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -148,6 +148,7 @@ void NORETURN die_errno(const char *fmt, ...)
        va_end(params);
 }
 
+#undef error_errno
 int error_errno(const char *fmt, ...)
 {
        char buf[1024];
index 6225a2d89f2c38bea6ed64295a28e7e07d19522c..539aac15a37adcce6429c4d82f401a7db17fac28 100644 (file)
@@ -139,7 +139,7 @@ void wt_status_prepare(struct wt_status *s)
        s->display_comment_prefix = 0;
 }
 
-static void wt_status_print_unmerged_header(struct wt_status *s)
+static void wt_longstatus_print_unmerged_header(struct wt_status *s)
 {
        int i;
        int del_mod_conflict = 0;
@@ -191,7 +191,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
        status_printf_ln(s, c, "%s", "");
 }
 
-static void wt_status_print_cached_header(struct wt_status *s)
+static void wt_longstatus_print_cached_header(struct wt_status *s)
 {
        const char *c = color(WT_STATUS_HEADER, s);
 
@@ -207,9 +207,9 @@ static void wt_status_print_cached_header(struct wt_status *s)
        status_printf_ln(s, c, "%s", "");
 }
 
-static void wt_status_print_dirty_header(struct wt_status *s,
-                                        int has_deleted,
-                                        int has_dirty_submodules)
+static void wt_longstatus_print_dirty_header(struct wt_status *s,
+                                            int has_deleted,
+                                            int has_dirty_submodules)
 {
        const char *c = color(WT_STATUS_HEADER, s);
 
@@ -226,9 +226,9 @@ static void wt_status_print_dirty_header(struct wt_status *s,
        status_printf_ln(s, c, "%s", "");
 }
 
-static void wt_status_print_other_header(struct wt_status *s,
-                                        const char *what,
-                                        const char *how)
+static void wt_longstatus_print_other_header(struct wt_status *s,
+                                            const char *what,
+                                            const char *how)
 {
        const char *c = color(WT_STATUS_HEADER, s);
        status_printf_ln(s, c, "%s:", what);
@@ -238,7 +238,7 @@ static void wt_status_print_other_header(struct wt_status *s,
        status_printf_ln(s, c, "%s", "");
 }
 
-static void wt_status_print_trailer(struct wt_status *s)
+static void wt_longstatus_print_trailer(struct wt_status *s)
 {
        status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
 }
@@ -304,8 +304,8 @@ static int maxwidth(const char *(*label)(int), int minval, int maxval)
        return result;
 }
 
-static void wt_status_print_unmerged_data(struct wt_status *s,
-                                         struct string_list_item *it)
+static void wt_longstatus_print_unmerged_data(struct wt_status *s,
+                                             struct string_list_item *it)
 {
        const char *c = color(WT_STATUS_UNMERGED, s);
        struct wt_status_change_data *d = it->util;
@@ -331,9 +331,9 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
        strbuf_release(&onebuf);
 }
 
-static void wt_status_print_change_data(struct wt_status *s,
-                                       int change_type,
-                                       struct string_list_item *it)
+static void wt_longstatus_print_change_data(struct wt_status *s,
+                                           int change_type,
+                                           struct string_list_item *it)
 {
        struct wt_status_change_data *d = it->util;
        const char *c = color(change_type, s);
@@ -378,7 +378,7 @@ static void wt_status_print_change_data(struct wt_status *s,
                status = d->worktree_status;
                break;
        default:
-               die("BUG: unhandled change_type %d in wt_status_print_change_data",
+               die("BUG: unhandled change_type %d in wt_longstatus_print_change_data",
                    change_type);
        }
 
@@ -434,6 +434,31 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
                if (S_ISGITLINK(p->two->mode))
                        d->new_submodule_commits = !!oidcmp(&p->one->oid,
                                                            &p->two->oid);
+
+               switch (p->status) {
+               case DIFF_STATUS_ADDED:
+                       die("BUG: worktree status add???");
+                       break;
+
+               case DIFF_STATUS_DELETED:
+                       d->mode_index = p->one->mode;
+                       oidcpy(&d->oid_index, &p->one->oid);
+                       /* mode_worktree is zero for a delete. */
+                       break;
+
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+               case DIFF_STATUS_UNMERGED:
+                       d->mode_index = p->one->mode;
+                       d->mode_worktree = p->two->mode;
+                       oidcpy(&d->oid_index, &p->one->oid);
+                       break;
+
+               case DIFF_STATUS_UNKNOWN:
+                       die("BUG: worktree status unknown???");
+                       break;
+               }
+
        }
 }
 
@@ -479,12 +504,36 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
                if (!d->index_status)
                        d->index_status = p->status;
                switch (p->status) {
+               case DIFF_STATUS_ADDED:
+                       /* Leave {mode,oid}_head zero for an add. */
+                       d->mode_index = p->two->mode;
+                       oidcpy(&d->oid_index, &p->two->oid);
+                       break;
+               case DIFF_STATUS_DELETED:
+                       d->mode_head = p->one->mode;
+                       oidcpy(&d->oid_head, &p->one->oid);
+                       /* Leave {mode,oid}_index zero for a delete. */
+                       break;
+
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
                        d->head_path = xstrdup(p->one->path);
+                       d->score = p->score * 100 / MAX_SCORE;
+                       /* fallthru */
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+                       d->mode_head = p->one->mode;
+                       d->mode_index = p->two->mode;
+                       oidcpy(&d->oid_head, &p->one->oid);
+                       oidcpy(&d->oid_index, &p->two->oid);
                        break;
                case DIFF_STATUS_UNMERGED:
                        d->stagemask = unmerged_mask(p->two->path);
+                       /*
+                        * Don't bother setting {mode,oid}_{head,index} since the print
+                        * code will output the stage values directly and not use the
+                        * values in these fields.
+                        */
                        break;
                }
        }
@@ -565,9 +614,17 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
                if (ce_stage(ce)) {
                        d->index_status = DIFF_STATUS_UNMERGED;
                        d->stagemask |= (1 << (ce_stage(ce) - 1));
-               }
-               else
+                       /*
+                        * Don't bother setting {mode,oid}_{head,index} since the print
+                        * code will output the stage values directly and not use the
+                        * values in these fields.
+                        */
+               } else {
                        d->index_status = DIFF_STATUS_ADDED;
+                       /* Leave {mode,oid}_head zero for adds. */
+                       d->mode_index = ce->ce_mode;
+                       hashcpy(d->oid_index.hash, ce->sha1);
+               }
        }
 }
 
@@ -627,7 +684,7 @@ void wt_status_collect(struct wt_status *s)
        wt_status_collect_untracked(s);
 }
 
-static void wt_status_print_unmerged(struct wt_status *s)
+static void wt_longstatus_print_unmerged(struct wt_status *s)
 {
        int shown_header = 0;
        int i;
@@ -640,17 +697,17 @@ static void wt_status_print_unmerged(struct wt_status *s)
                if (!d->stagemask)
                        continue;
                if (!shown_header) {
-                       wt_status_print_unmerged_header(s);
+                       wt_longstatus_print_unmerged_header(s);
                        shown_header = 1;
                }
-               wt_status_print_unmerged_data(s, it);
+               wt_longstatus_print_unmerged_data(s, it);
        }
        if (shown_header)
-               wt_status_print_trailer(s);
+               wt_longstatus_print_trailer(s);
 
 }
 
-static void wt_status_print_updated(struct wt_status *s)
+static void wt_longstatus_print_updated(struct wt_status *s)
 {
        int shown_header = 0;
        int i;
@@ -664,14 +721,14 @@ static void wt_status_print_updated(struct wt_status *s)
                    d->index_status == DIFF_STATUS_UNMERGED)
                        continue;
                if (!shown_header) {
-                       wt_status_print_cached_header(s);
+                       wt_longstatus_print_cached_header(s);
                        s->commitable = 1;
                        shown_header = 1;
                }
-               wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
+               wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it);
        }
        if (shown_header)
-               wt_status_print_trailer(s);
+               wt_longstatus_print_trailer(s);
 }
 
 /*
@@ -703,7 +760,7 @@ static int wt_status_check_worktree_changes(struct wt_status *s,
        return changes;
 }
 
-static void wt_status_print_changed(struct wt_status *s)
+static void wt_longstatus_print_changed(struct wt_status *s)
 {
        int i, dirty_submodules;
        int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
@@ -711,7 +768,7 @@ static void wt_status_print_changed(struct wt_status *s)
        if (!worktree_changes)
                return;
 
-       wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
+       wt_longstatus_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
 
        for (i = 0; i < s->change.nr; i++) {
                struct wt_status_change_data *d;
@@ -721,12 +778,12 @@ static void wt_status_print_changed(struct wt_status *s)
                if (!d->worktree_status ||
                    d->worktree_status == DIFF_STATUS_UNMERGED)
                        continue;
-               wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
+               wt_longstatus_print_change_data(s, WT_STATUS_CHANGED, it);
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
-static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
+static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted)
 {
        struct child_process sm_summary = CHILD_PROCESS_INIT;
        struct strbuf cmd_stdout = STRBUF_INIT;
@@ -772,10 +829,10 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt
        strbuf_release(&summary);
 }
 
-static void wt_status_print_other(struct wt_status *s,
-                                 struct string_list *l,
-                                 const char *what,
-                                 const char *how)
+static void wt_longstatus_print_other(struct wt_status *s,
+                                     struct string_list *l,
+                                     const char *what,
+                                     const char *how)
 {
        int i;
        struct strbuf buf = STRBUF_INIT;
@@ -785,7 +842,7 @@ static void wt_status_print_other(struct wt_status *s,
        if (!l->nr)
                return;
 
-       wt_status_print_other_header(s, what, how);
+       wt_longstatus_print_other_header(s, what, how);
 
        for (i = 0; i < l->nr; i++) {
                struct string_list_item *it;
@@ -845,7 +902,7 @@ void wt_status_add_cut_line(FILE *fp)
        strbuf_release(&buf);
 }
 
-static void wt_status_print_verbose(struct wt_status *s)
+static void wt_longstatus_print_verbose(struct wt_status *s)
 {
        struct rev_info rev;
        struct setup_revision_opt opt;
@@ -878,7 +935,7 @@ static void wt_status_print_verbose(struct wt_status *s)
        if (s->verbose > 1 && s->commitable) {
                /* print_updated() printed a header, so do we */
                if (s->fp != stdout)
-                       wt_status_print_trailer(s);
+                       wt_longstatus_print_trailer(s);
                status_printf_ln(s, c, _("Changes to be committed:"));
                rev.diffopt.a_prefix = "c/";
                rev.diffopt.b_prefix = "i/";
@@ -896,7 +953,7 @@ static void wt_status_print_verbose(struct wt_status *s)
        }
 }
 
-static void wt_status_print_tracking(struct wt_status *s)
+static void wt_longstatus_print_tracking(struct wt_status *s)
 {
        struct strbuf sb = STRBUF_INIT;
        const char *cp, *ep, *branch_name;
@@ -962,7 +1019,7 @@ static void show_merge_in_progress(struct wt_status *s,
                        status_printf_ln(s, color,
                                _("  (use \"git commit\" to conclude merge)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
 static void show_am_in_progress(struct wt_status *s,
@@ -983,7 +1040,7 @@ static void show_am_in_progress(struct wt_status *s,
                status_printf_ln(s, color,
                        _("  (use \"git am --abort\" to restore the original branch)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
 static char *read_line_from_git_path(const char *filename)
@@ -1207,7 +1264,7 @@ static void show_rebase_in_progress(struct wt_status *s,
                                _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
                }
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
 static void show_cherry_pick_in_progress(struct wt_status *s,
@@ -1226,7 +1283,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s,
                status_printf_ln(s, color,
                        _("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
 static void show_revert_in_progress(struct wt_status *s,
@@ -1245,7 +1302,7 @@ static void show_revert_in_progress(struct wt_status *s,
                status_printf_ln(s, color,
                        _("  (use \"git revert --abort\" to cancel the revert operation)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
 static void show_bisect_in_progress(struct wt_status *s,
@@ -1262,7 +1319,7 @@ static void show_bisect_in_progress(struct wt_status *s,
        if (s->hints)
                status_printf_ln(s, color,
                        _("  (use \"git bisect reset\" to get back to the original branch)"));
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
 }
 
 /*
@@ -1432,8 +1489,8 @@ void wt_status_get_state(struct wt_status_state *state,
                wt_status_get_detached_from(state);
 }
 
-static void wt_status_print_state(struct wt_status *s,
-                                 struct wt_status_state *state)
+static void wt_longstatus_print_state(struct wt_status *s,
+                                     struct wt_status_state *state)
 {
        const char *state_color = color(WT_STATUS_HEADER, s);
        if (state->merge_in_progress)
@@ -1450,7 +1507,7 @@ static void wt_status_print_state(struct wt_status *s,
                show_bisect_in_progress(s, state, state_color);
 }
 
-void wt_status_print(struct wt_status *s)
+static void wt_longstatus_print(struct wt_status *s)
 {
        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
        const char *branch_status_color = color(WT_STATUS_HEADER, s);
@@ -1487,10 +1544,10 @@ void wt_status_print(struct wt_status *s)
                status_printf_more(s, branch_status_color, "%s", on_what);
                status_printf_more(s, branch_color, "%s\n", branch_name);
                if (!s->is_initial)
-                       wt_status_print_tracking(s);
+                       wt_longstatus_print_tracking(s);
        }
 
-       wt_status_print_state(s, &state);
+       wt_longstatus_print_state(s, &state);
        free(state.branch);
        free(state.onto);
        free(state.detached_from);
@@ -1501,19 +1558,19 @@ void wt_status_print(struct wt_status *s)
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
        }
 
-       wt_status_print_updated(s);
-       wt_status_print_unmerged(s);
-       wt_status_print_changed(s);
+       wt_longstatus_print_updated(s);
+       wt_longstatus_print_unmerged(s);
+       wt_longstatus_print_changed(s);
        if (s->submodule_summary &&
            (!s->ignore_submodule_arg ||
             strcmp(s->ignore_submodule_arg, "all"))) {
-               wt_status_print_submodule_summary(s, 0);  /* staged */
-               wt_status_print_submodule_summary(s, 1);  /* unstaged */
+               wt_longstatus_print_submodule_summary(s, 0);  /* staged */
+               wt_longstatus_print_submodule_summary(s, 1);  /* unstaged */
        }
        if (s->show_untracked_files) {
-               wt_status_print_other(s, &s->untracked, _("Untracked files"), "add");
+               wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
                if (s->show_ignored_files)
-                       wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
+                       wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
                if (advice_status_u_option && 2000 < s->untracked_in_ms) {
                        status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
                        status_printf_ln(s, GIT_COLOR_NORMAL,
@@ -1528,7 +1585,7 @@ void wt_status_print(struct wt_status *s)
                        ? _(" (use -u option to show untracked files)") : "");
 
        if (s->verbose)
-               wt_status_print_verbose(s);
+               wt_longstatus_print_verbose(s);
        if (!s->commitable) {
                if (s->amend)
                        status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
@@ -1717,7 +1774,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
        fputc(s->null_termination ? '\0' : '\n', s->fp);
 }
 
-void wt_shortstatus_print(struct wt_status *s)
+static void wt_shortstatus_print(struct wt_status *s)
 {
        int i;
 
@@ -1749,7 +1806,7 @@ void wt_shortstatus_print(struct wt_status *s)
        }
 }
 
-void wt_porcelain_print(struct wt_status *s)
+static void wt_porcelain_print(struct wt_status *s)
 {
        s->use_color = 0;
        s->relative_paths = 0;
@@ -1757,3 +1814,398 @@ void wt_porcelain_print(struct wt_status *s)
        s->no_gettext = 1;
        wt_shortstatus_print(s);
 }
+
+/*
+ * Print branch information for porcelain v2 output.  These lines
+ * are printed when the '--branch' parameter is given.
+ *
+ *    # branch.oid <commit><eol>
+ *    # branch.head <head><eol>
+ *   [# branch.upstream <upstream><eol>
+ *   [# branch.ab +<ahead> -<behind><eol>]]
+ *
+ *      <commit> ::= the current commit hash or the the literal
+ *                   "(initial)" to indicate an initialized repo
+ *                   with no commits.
+ *
+ *        <head> ::= <branch_name> the current branch name or
+ *                   "(detached)" literal when detached head or
+ *                   "(unknown)" when something is wrong.
+ *
+ *    <upstream> ::= the upstream branch name, when set.
+ *
+ *       <ahead> ::= integer ahead value, when upstream set
+ *                   and the commit is present (not gone).
+ *
+ *      <behind> ::= integer behind value, when upstream set
+ *                   and commit is present.
+ *
+ *
+ * The end-of-line is defined by the -z flag.
+ *
+ *                 <eol> ::= NUL when -z,
+ *                           LF when NOT -z.
+ *
+ */
+static void wt_porcelain_v2_print_tracking(struct wt_status *s)
+{
+       struct branch *branch;
+       const char *base;
+       const char *branch_name;
+       struct wt_status_state state;
+       int ab_info, nr_ahead, nr_behind;
+       char eol = s->null_termination ? '\0' : '\n';
+
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, s->branch && !strcmp(s->branch, "HEAD"));
+
+       fprintf(s->fp, "# branch.oid %s%c",
+                       (s->is_initial ? "(initial)" : sha1_to_hex(s->sha1_commit)),
+                       eol);
+
+       if (!s->branch)
+               fprintf(s->fp, "# branch.head %s%c", "(unknown)", eol);
+       else {
+               if (!strcmp(s->branch, "HEAD")) {
+                       fprintf(s->fp, "# branch.head %s%c", "(detached)", eol);
+
+                       if (state.rebase_in_progress || state.rebase_interactive_in_progress)
+                               branch_name = state.onto;
+                       else if (state.detached_from)
+                               branch_name = state.detached_from;
+                       else
+                               branch_name = "";
+               } else {
+                       branch_name = NULL;
+                       skip_prefix(s->branch, "refs/heads/", &branch_name);
+
+                       fprintf(s->fp, "# branch.head %s%c", branch_name, eol);
+               }
+
+               /* Lookup stats on the upstream tracking branch, if set. */
+               branch = branch_get(branch_name);
+               base = NULL;
+               ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0);
+               if (base) {
+                       base = shorten_unambiguous_ref(base, 0);
+                       fprintf(s->fp, "# branch.upstream %s%c", base, eol);
+                       free((char *)base);
+
+                       if (ab_info)
+                               fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol);
+               }
+       }
+
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+}
+
+/*
+ * Convert various submodule status values into a
+ * fixed-length string of characters in the buffer provided.
+ */
+static void wt_porcelain_v2_submodule_state(
+       struct wt_status_change_data *d,
+       char sub[5])
+{
+       if (S_ISGITLINK(d->mode_head) ||
+               S_ISGITLINK(d->mode_index) ||
+               S_ISGITLINK(d->mode_worktree)) {
+               sub[0] = 'S';
+               sub[1] = d->new_submodule_commits ? 'C' : '.';
+               sub[2] = (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) ? 'M' : '.';
+               sub[3] = (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ? 'U' : '.';
+       } else {
+               sub[0] = 'N';
+               sub[1] = '.';
+               sub[2] = '.';
+               sub[3] = '.';
+       }
+       sub[4] = 0;
+}
+
+/*
+ * Fix-up changed entries before we print them.
+ */
+static void wt_porcelain_v2_fix_up_changed(
+       struct string_list_item *it,
+       struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+
+       if (!d->index_status) {
+               /*
+                * This entry is unchanged in the index (relative to the head).
+                * Therefore, the collect_updated_cb was never called for this
+                * entry (during the head-vs-index scan) and so the head column
+                * fields were never set.
+                *
+                * We must have data for the index column (from the
+                * index-vs-worktree scan (otherwise, this entry should not be
+                * in the list of changes)).
+                *
+                * Copy index column fields to the head column, so that our
+                * output looks complete.
+                */
+               assert(d->mode_head == 0);
+               d->mode_head = d->mode_index;
+               oidcpy(&d->oid_head, &d->oid_index);
+       }
+
+       if (!d->worktree_status) {
+               /*
+                * This entry is unchanged in the worktree (relative to the index).
+                * Therefore, the collect_changed_cb was never called for this entry
+                * (during the index-vs-worktree scan) and so the worktree column
+                * fields were never set.
+                *
+                * We must have data for the index column (from the head-vs-index
+                * scan).
+                *
+                * Copy the index column fields to the worktree column so that
+                * our output looks complete.
+                *
+                * Note that we only have a mode field in the worktree column
+                * because the scan code tries really hard to not have to compute it.
+                */
+               assert(d->mode_worktree == 0);
+               d->mode_worktree = d->mode_index;
+       }
+}
+
+/*
+ * Print porcelain v2 info for tracked entries with changes.
+ */
+static void wt_porcelain_v2_print_changed_entry(
+       struct string_list_item *it,
+       struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+       struct strbuf buf_index = STRBUF_INIT;
+       struct strbuf buf_head = STRBUF_INIT;
+       const char *path_index = NULL;
+       const char *path_head = NULL;
+       char key[3];
+       char submodule_token[5];
+       char sep_char, eol_char;
+
+       wt_porcelain_v2_fix_up_changed(it, s);
+       wt_porcelain_v2_submodule_state(d, submodule_token);
+
+       key[0] = d->index_status ? d->index_status : '.';
+       key[1] = d->worktree_status ? d->worktree_status : '.';
+       key[2] = 0;
+
+       if (s->null_termination) {
+               /*
+                * In -z mode, we DO NOT C-quote pathnames.  Current path is ALWAYS first.
+                * A single NUL character separates them.
+                */
+               sep_char = '\0';
+               eol_char = '\0';
+               path_index = it->string;
+               path_head = d->head_path;
+       } else {
+               /*
+                * Path(s) are C-quoted if necessary. Current path is ALWAYS first.
+                * The source path is only present when necessary.
+                * A single TAB separates them (because paths can contain spaces
+                * which are not escaped and C-quoting does escape TAB characters).
+                */
+               sep_char = '\t';
+               eol_char = '\n';
+               path_index = quote_path(it->string, s->prefix, &buf_index);
+               if (d->head_path)
+                       path_head = quote_path(d->head_path, s->prefix, &buf_head);
+       }
+
+       if (path_head)
+               fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c",
+                               key, submodule_token,
+                               d->mode_head, d->mode_index, d->mode_worktree,
+                               oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
+                               key[0], d->score,
+                               path_index, sep_char, path_head, eol_char);
+       else
+               fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c",
+                               key, submodule_token,
+                               d->mode_head, d->mode_index, d->mode_worktree,
+                               oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
+                               path_index, eol_char);
+
+       strbuf_release(&buf_index);
+       strbuf_release(&buf_head);
+}
+
+/*
+ * Print porcelain v2 status info for unmerged entries.
+ */
+static void wt_porcelain_v2_print_unmerged_entry(
+       struct string_list_item *it,
+       struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+       const struct cache_entry *ce;
+       struct strbuf buf_index = STRBUF_INIT;
+       const char *path_index = NULL;
+       int pos, stage, sum;
+       struct {
+               int mode;
+               struct object_id oid;
+       } stages[3];
+       char *key;
+       char submodule_token[5];
+       char unmerged_prefix = 'u';
+       char eol_char = s->null_termination ? '\0' : '\n';
+
+       wt_porcelain_v2_submodule_state(d, submodule_token);
+
+       switch (d->stagemask) {
+       case 1: key = "DD"; break; /* both deleted */
+       case 2: key = "AU"; break; /* added by us */
+       case 3: key = "UD"; break; /* deleted by them */
+       case 4: key = "UA"; break; /* added by them */
+       case 5: key = "DU"; break; /* deleted by us */
+       case 6: key = "AA"; break; /* both added */
+       case 7: key = "UU"; break; /* both modified */
+       default:
+               die("BUG: unhandled unmerged status %x", d->stagemask);
+       }
+
+       /*
+        * Disregard d.aux.porcelain_v2 data that we accumulated
+        * for the head and index columns during the scans and
+        * replace with the actual stage data.
+        *
+        * Note that this is a last-one-wins for each the individual
+        * stage [123] columns in the event of multiple cache entries
+        * for same stage.
+        */
+       memset(stages, 0, sizeof(stages));
+       sum = 0;
+       pos = cache_name_pos(it->string, strlen(it->string));
+       assert(pos < 0);
+       pos = -pos-1;
+       while (pos < active_nr) {
+               ce = active_cache[pos++];
+               stage = ce_stage(ce);
+               if (strcmp(ce->name, it->string) || !stage)
+                       break;
+               stages[stage - 1].mode = ce->ce_mode;
+               hashcpy(stages[stage - 1].oid.hash, ce->sha1);
+               sum |= (1 << (stage - 1));
+       }
+       if (sum != d->stagemask)
+               die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
+
+       if (s->null_termination)
+               path_index = it->string;
+       else
+               path_index = quote_path(it->string, s->prefix, &buf_index);
+
+       fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c",
+                       unmerged_prefix, key, submodule_token,
+                       stages[0].mode, /* stage 1 */
+                       stages[1].mode, /* stage 2 */
+                       stages[2].mode, /* stage 3 */
+                       d->mode_worktree,
+                       oid_to_hex(&stages[0].oid), /* stage 1 */
+                       oid_to_hex(&stages[1].oid), /* stage 2 */
+                       oid_to_hex(&stages[2].oid), /* stage 3 */
+                       path_index,
+                       eol_char);
+
+       strbuf_release(&buf_index);
+}
+
+/*
+ * Print porcelain V2 status info for untracked and ignored entries.
+ */
+static void wt_porcelain_v2_print_other(
+       struct string_list_item *it,
+       struct wt_status *s,
+       char prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       const char *path;
+       char eol_char;
+
+       if (s->null_termination) {
+               path = it->string;
+               eol_char = '\0';
+       } else {
+               path = quote_path(it->string, s->prefix, &buf);
+               eol_char = '\n';
+       }
+
+       fprintf(s->fp, "%c %s%c", prefix, path, eol_char);
+
+       strbuf_release(&buf);
+}
+
+/*
+ * Print porcelain V2 status.
+ *
+ * [<v2_branch>]
+ * [<v2_changed_items>]*
+ * [<v2_unmerged_items>]*
+ * [<v2_untracked_items>]*
+ * [<v2_ignored_items>]*
+ *
+ */
+static void wt_porcelain_v2_print(struct wt_status *s)
+{
+       struct wt_status_change_data *d;
+       struct string_list_item *it;
+       int i;
+
+       if (s->show_branch)
+               wt_porcelain_v2_print_tracking(s);
+
+       for (i = 0; i < s->change.nr; i++) {
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (!d->stagemask)
+                       wt_porcelain_v2_print_changed_entry(it, s);
+       }
+
+       for (i = 0; i < s->change.nr; i++) {
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (d->stagemask)
+                       wt_porcelain_v2_print_unmerged_entry(it, s);
+       }
+
+       for (i = 0; i < s->untracked.nr; i++) {
+               it = &(s->untracked.items[i]);
+               wt_porcelain_v2_print_other(it, s, '?');
+       }
+
+       for (i = 0; i < s->ignored.nr; i++) {
+               it = &(s->ignored.items[i]);
+               wt_porcelain_v2_print_other(it, s, '!');
+       }
+}
+
+void wt_status_print(struct wt_status *s)
+{
+       switch (s->status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(s);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(s);
+               break;
+       case STATUS_FORMAT_PORCELAIN_V2:
+               wt_porcelain_v2_print(s);
+               break;
+       case STATUS_FORMAT_UNSPECIFIED:
+               die("BUG: finalize_deferred_config() should have been called");
+               break;
+       case STATUS_FORMAT_NONE:
+       case STATUS_FORMAT_LONG:
+               wt_longstatus_print(s);
+               break;
+       }
+}
index 2ca93f6957f69cd5652ddc764f5afe81b598a2a9..e40183770700cb593d4e4f2bf914a5b6eff0bb09 100644 (file)
@@ -38,11 +38,24 @@ struct wt_status_change_data {
        int worktree_status;
        int index_status;
        int stagemask;
+       int score;
+       int mode_head, mode_index, mode_worktree;
+       struct object_id oid_head, oid_index;
        char *head_path;
        unsigned dirty_submodule       : 2;
        unsigned new_submodule_commits : 1;
 };
 
+enum wt_status_format {
+       STATUS_FORMAT_NONE = 0,
+       STATUS_FORMAT_LONG,
+       STATUS_FORMAT_SHORT,
+       STATUS_FORMAT_PORCELAIN,
+       STATUS_FORMAT_PORCELAIN_V2,
+
+       STATUS_FORMAT_UNSPECIFIED
+};
+
 struct wt_status {
        int is_initial;
        char *branch;
@@ -66,6 +79,9 @@ struct wt_status {
        int show_branch;
        int hints;
 
+       enum wt_status_format status_format;
+       unsigned char sha1_commit[GIT_SHA1_RAWSZ]; /* when not Initial */
+
        /* These are computed during processing of the individual sections */
        int commitable;
        int workdir_dirty;
@@ -107,9 +123,6 @@ int wt_status_check_rebase(const struct worktree *wt,
 int wt_status_check_bisect(const struct worktree *wt,
                           struct wt_status_state *state);
 
-void wt_shortstatus_print(struct wt_status *s);
-void wt_porcelain_print(struct wt_status *s);
-
 __attribute__((format (printf, 3, 4)))
 void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...);
 __attribute__((format (printf, 3, 4)))