Sync with 2.6.1
authorJunio C Hamano <gitster@pobox.com>
Mon, 5 Oct 2015 19:46:27 +0000 (12:46 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 5 Oct 2015 20:20:08 +0000 (13:20 -0700)
76 files changed:
.gitignore
Documentation/blame-options.txt
Documentation/config.txt
Documentation/git-am.txt
Documentation/git-bisect.txt
Documentation/git-cherry-pick.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-for-each-ref.txt
Documentation/git-grep.txt
Documentation/git-merge.txt
Documentation/git-quiltimport.txt
Documentation/git-rebase.txt
Documentation/git-remote.txt
Documentation/git-rev-list.txt
Documentation/git-revert.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-tag.txt
Documentation/glossary-content.txt
Documentation/rev-list-options.txt
GIT-VERSION-GEN
Makefile
bisect.c
builtin.h
builtin/blame.c
builtin/branch.c
builtin/for-each-ref.c
builtin/remote.c
builtin/rerere.c
builtin/submodule--helper.c [new file with mode: 0644]
builtin/tag.c
cache.h
connect.c
contrib/subtree/git-subtree.sh
contrib/subtree/t/t7900-subtree.sh
date.c
fast-import.c
git-bisect.sh
git-compat-util.h
git-p4.py
git-quiltimport.sh
git-rebase.sh
git-stash.sh
git-submodule.sh
git.c
parse-options-cb.c
parse-options.h
path.c
pkt-line.c
ref-filter.c
ref-filter.h
refs.c
refs.h
rerere.c
rerere.h
run-command.c
run-command.h
t/annotate-tests.sh
t/t0060-path-utils.sh
t/t1400-update-ref.sh
t/t3210-pack-refs.sh
t/t3420-rebase-autostash.sh
t/t5505-remote.sh
t/t5507-remote-environment.sh [new file with mode: 0755]
t/t5801-remote-helpers.sh
t/t6030-bisect-porcelain.sh
t/t6300-for-each-ref.sh
t/t6302-for-each-ref-filter.sh [new file with mode: 0755]
t/t7004-tag.sh
t/t7610-mergetool.sh
t/t7800-difftool.sh
t/t9811-git-p4-label-import.sh
t/test-lib-functions.sh
utf8.c
utf8.h
index 4fd81baf856669fb984a5a0b82a1115e840fc16d..1c2f8321386f89ef8c03d11159c97a0f194c4423 100644 (file)
 /git-status
 /git-stripspace
 /git-submodule
+/git-submodule--helper
 /git-svn
 /git-symbolic-ref
 /git-tag
index a09969ba086609af5d35bbb3c5f30dfea5db00b6..760eab7428357ad4006437ae3032cee958dce803 100644 (file)
@@ -63,11 +63,10 @@ include::line-range-format.txt[]
        `-` to make the command read from the standard input).
 
 --date <format>::
-       The value is one of the following alternatives:
-       {relative,local,default,iso,rfc,short}. If --date is not
+       Specifies the format used to output dates. If --date is not
        provided, the value of the blame.date config variable is
        used. If the blame.date config variable is also not set, the
-       iso format is used. For more information, See the discussion
+       iso format is used. For supported values, see the discussion
        of the --date option at linkgit:git-log[1].
 
 -M|<num>|::
index 0cc87a6f6565bbe794420bc47be8407e4ac47f22..4d3cb107f853463c92af67ba9da70cbf3485060f 100644 (file)
@@ -1829,9 +1829,7 @@ log.abbrevCommit::
 log.date::
        Set the default date-time mode for the 'log' command.
        Setting a value for log.date is similar to using 'git log''s
-       `--date` option.  Possible values are `relative`, `local`,
-       `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
-       for details.
+       `--date` option.  See linkgit:git-log[1] for details.
 
 log.decorate::
        Print out the ref names of any commits that are shown by the log
@@ -2587,6 +2585,16 @@ status.submoduleSummary::
        submodule summary' command, which shows a similar output but does
        not honor these settings.
 
+stash.showPatch::
+       If this is set to true, the `git stash show` command without an
+       option will show the stash in patch form.  Defaults to false.
+       See description of 'show' command in linkgit:git-stash[1].
+
+stash.showStat::
+       If this is set to true, the `git stash show` command without an
+       option will show diffstat of the stash.  Defaults to true.
+       See description of 'show' command in linkgit:git-stash[1].
+
 submodule.<name>.path::
 submodule.<name>.url::
        The path within this project and URL for a submodule. These
index dbea6e7ae9131a1e5c51dec460d58cc6deda701b..452c1feb2319a3ac47836b5039ebeba519be3c87 100644 (file)
@@ -141,7 +141,9 @@ default.   You can use `--no-utf8` to override this.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commits.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --continue::
 -r::
index e97f2de21bdc58cc2de35731f7b339353b121bd0..2044fe6820e0507a6dcc1823f8fe3fce407e4d90 100644 (file)
@@ -16,9 +16,11 @@ DESCRIPTION
 The command takes various subcommands, and different options depending
 on the subcommand:
 
- git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
- git bisect bad [<rev>]
- git bisect good [<rev>...]
+ git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                 [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
+ git bisect (bad|new) [<rev>]
+ git bisect (good|old) [<rev>...]
+ git bisect terms [--term-good | --term-bad]
  git bisect skip [(<rev>|<range>)...]
  git bisect reset [<commit>]
  git bisect visualize
@@ -36,6 +38,13 @@ whether the selected commit is "good" or "bad". It continues narrowing
 down the range until it finds the exact commit that introduced the
 change.
 
+In fact, `git bisect` can be used to find the commit that changed
+*any* property of your project; e.g., the commit that fixed a bug, or
+the commit that caused a benchmark's performance to improve. To
+support this more general usage, the terms "old" and "new" can be used
+in place of "good" and "bad", or you can choose your own terms. See
+section "Alternate terms" below for more information.
+
 Basic bisect commands: start, bad, good
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -111,6 +120,79 @@ bad revision, while `git bisect reset HEAD` will leave you on the
 current bisection commit and avoid switching commits at all.
 
 
+Alternate terms
+~~~~~~~~~~~~~~~
+
+Sometimes you are not looking for the commit that introduced a
+breakage, but rather for a commit that caused a change between some
+other "old" state and "new" state. For example, you might be looking
+for the commit that introduced a particular fix. Or you might be
+looking for the first commit in which the source-code filenames were
+finally all converted to your company's naming standard. Or whatever.
+
+In such cases it can be very confusing to use the terms "good" and
+"bad" to refer to "the state before the change" and "the state after
+the change". So instead, you can use the terms "old" and "new",
+respectively, in place of "good" and "bad". (But note that you cannot
+mix "good" and "bad" with "old" and "new" in a single session.)
+
+In this more general usage, you provide `git bisect` with a "new"
+commit has some property and an "old" commit that doesn't have that
+property. Each time `git bisect` checks out a commit, you test if that
+commit has the property. If it does, mark the commit as "new";
+otherwise, mark it as "old". When the bisection is done, `git bisect`
+will report which commit introduced the property.
+
+To use "old" and "new" instead of "good" and bad, you must run `git
+bisect start` without commits as argument and then run the following
+commands to add the commits:
+
+------------------------------------------------
+git bisect old [<rev>]
+------------------------------------------------
+
+to indicate that a commit was before the sought change, or
+
+------------------------------------------------
+git bisect new [<rev>...]
+------------------------------------------------
+
+to indicate that it was after.
+
+To get a reminder of the currently used terms, use
+
+------------------------------------------------
+git bisect terms
+------------------------------------------------
+
+You can get just the old (respectively new) term with `git bisect term
+--term-old` or `git bisect term --term-good`.
+
+If you would like to use your own terms instead of "bad"/"good" or
+"new"/"old", you can choose any names you like (except existing bisect
+subcommands like `reset`, `start`, ...) by starting the
+bisection using
+
+------------------------------------------------
+git bisect start --term-old <term-old> --term-new <term-new>
+------------------------------------------------
+
+For example, if you are looking for a commit that introduced a
+performance regression, you might use
+
+------------------------------------------------
+git bisect start --term-old fast --term-new slow
+------------------------------------------------
+
+Or if you are looking for the commit that fixed a bug, you might use
+
+------------------------------------------------
+git bisect start --term-new fixed --term-old broken
+------------------------------------------------
+
+Then, use `git bisect <term-old>` and `git bisect <term-new>` instead
+of `git bisect good` and `git bisect bad` to mark commits.
+
 Bisect visualize
 ~~~~~~~~~~~~~~~~
 
@@ -387,6 +469,21 @@ In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit
 has at least one parent whose reachable graph is fully traversable in the sense
 required by 'git pack objects'.
 
+* Look for a fix instead of a regression in the code
++
+------------
+$ git bisect start
+$ git bisect new HEAD    # current commit is marked as new
+$ git bisect old HEAD~10 # the tenth commit from now is marked as old
+------------
++
+or:
+------------
+$ git bisect start --term-old broken --term-new fixed
+$ git bisect fixed
+$ git bisect broken HEAD~10
+------------
+
 Getting help
 ~~~~~~~~~~~~
 
index 1147c71da605c1f5779835137fb6bab5e2c349a6..77da29a474518c56c7dab23f5921aa50566506dd 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff]
-                 [-S[<key-id>]] <commit>...
+                 [-S[<keyid>]] <commit>...
 'git cherry-pick' --continue
 'git cherry-pick' --quit
 'git cherry-pick' --abort
@@ -101,9 +101,11 @@ effect to your index in a row.
 --signoff::
        Add Signed-off-by line at the end of the commit message.
 
--S[<key-id>]::
---gpg-sign[=<key-id>]::
-       GPG-sign commits.
+-S[<keyid>]::
+--gpg-sign[=<keyid>]::
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --ff::
        If the current HEAD is the same as the parent of the
index f5f2a8d326502714299cf85d9819bb065d5af390..a0b5457304008cec1cf728e5ac168c5f547173eb 100644 (file)
@@ -56,7 +56,9 @@ OPTIONS
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commit.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --no-gpg-sign::
        Countermand `commit.gpgSign` configuration variable that is
index 904dafa0f7070fc438f138393a3b356542ae04d9..7f34a5b33103ed1c870f234687079badc393be77 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
           [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-          [-i | -o] [-S[<key-id>]] [--] [<file>...]
+          [-i | -o] [-S[<keyid>]] [--] [<file>...]
 
 DESCRIPTION
 -----------
@@ -314,7 +314,9 @@ changes to tracked files.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commit.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --no-gpg-sign::
        Countermand `commit.gpgSign` configuration variable that is
index 7f8d9a5b5f358baa50bdfe205f555ea904f9f629..c6f073cea42a2a91fba1aeb17505fe6cd7f46ac8 100644 (file)
@@ -10,6 +10,8 @@ SYNOPSIS
 [verse]
 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
                   [(--sort=<key>)...] [--format=<format>] [<pattern>...]
+                  [--points-at <object>] [(--merged | --no-merged) [<object>]]
+                  [--contains [<object>]]
 
 DESCRIPTION
 -----------
@@ -62,6 +64,20 @@ OPTIONS
        the specified host language.  This is meant to produce
        a scriptlet that can directly be `eval`ed.
 
+--points-at <object>::
+       Only list refs which points at the given object.
+
+--merged [<object>]::
+       Only list refs whose tips are reachable from the
+       specified commit (HEAD if not specified).
+
+--no-merged [<object>]::
+       Only list refs whose tips are not reachable from the
+       specified commit (HEAD if not specified).
+
+--contains [<object>]::
+       Only list tags which contain the specified commit (HEAD if not
+       specified).
 
 FIELD NAMES
 -----------
@@ -111,6 +127,17 @@ color::
        Change output color.  Followed by `:<colorname>`, where names
        are described in `color.branch.*`.
 
+align::
+       Left-, middle-, or right-align the content between
+       %(align:...) and %(end). The "align:" is followed by `<width>`
+       and `<position>` in any order separated by a comma, where the
+       `<position>` is either left, right or middle, default being
+       left and `<width>` is the total length of the content with
+       alignment. If the contents length is more than the width then
+       no alignment is performed. If used with '--quote' everything
+       in between %(align:...) and %(end) is quoted, but if nested
+       then only the topmost level performs quoting.
+
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
@@ -123,20 +150,23 @@ The complete message in a commit and tag object is `contents`.
 Its first line is `contents:subject`, where subject is the concatenation
 of all lines of the commit message up to the first blank line.  The next
 line is 'contents:body', where body is all of the lines after the first
-blank line.  Finally, the optional GPG signature is `contents:signature`.
+blank line.  The optional GPG signature is `contents:signature`.  The
+first `N` lines of the message is obtained using `contents:lines=N`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
 All other fields are used to sort in their byte-value order.
 
+There is also an option to sort by versions, this can be done by using
+the fieldname `version:refname` or its alias `v:refname`.
+
 In any case, a field name that refers to a field inapplicable to
 the object referred by the ref does not cause an error.  It
 returns an empty string instead.
 
 As a special case for the date-type fields, you may specify a format for
-the date by adding one of `:default`, `:relative`, `:short`, `:local`,
-`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g.
-`%(taggerdate:relative)`.
+the date by adding `:` followed by date format name (see the
+values the `--date` option to linkgit::git-rev-list[1] takes).
 
 
 EXAMPLES
index 31811f16bdaac49d01d38dcdea82bb191942b1cf..4a44d6da13cb749759f9d6164b88974ba55485ab 100644 (file)
@@ -160,12 +160,15 @@ OPTIONS
        For better compatibility with 'git diff', `--name-only` is a
        synonym for `--files-with-matches`.
 
--O [<pager>]::
---open-files-in-pager [<pager>]::
+-O[<pager>]::
+--open-files-in-pager[=<pager>]::
        Open the matching files in the pager (not the output of 'grep').
        If the pager happens to be "less" or "vi", and the user
        specified only one pattern, the first file is positioned at
-       the first match automatically.
+       the first match automatically. The `pager` argument is
+       optional; if specified, it must be stuck to the option
+       without a space. If `pager` is unspecified, the default pager
+       will be used (see `core.pager` in linkgit:git-config[1]).
 
 -z::
 --null::
index a62d6729b94963787ad435c486e1dc419c776012..07f7295ec8b603fcbd907e780d4ed1109ac5b358 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
-       [-s <strategy>] [-X <strategy-option>] [-S[<key-id>]]
+       [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
        [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
 'git merge' <msg> HEAD <commit>...
 'git merge' --abort
@@ -67,7 +67,9 @@ include::merge-options.txt[]
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign the resulting merge commit.
+       GPG-sign the resulting merge commit. The `keyid` argument is
+       optional and defaults to the committer identity; if specified,
+       it must be stuck to the option without a space.
 
 -m <msg>::
        Set the commit message to be used for the merge commit (in
index d64388cb8e454be17e4c20caf95897a71619c11f..ff633b0db7d54d8db12c4af248fc2bd3939b6f9c 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>]
+               [--series <file>]
 
 
 DESCRIPTION
@@ -42,13 +43,19 @@ OPTIONS
        information can be found in the patch description.
 
 --patches <dir>::
-       The directory to find the quilt patches and the
-       quilt series file.
+       The directory to find the quilt patches.
 +
 The default for the patch directory is patches
 or the value of the $QUILT_PATCHES environment
 variable.
 
+--series <file>::
+       The quilt series file.
++
+The default for the series file is <patches>/series
+or the value of the $QUILT_SERIES environment
+variable.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index ca039546a463bee511ca3a1c7d301689fb56a412..6cca8bb51dcabd47474ad63d908d7ec5d06d8703 100644 (file)
@@ -294,7 +294,9 @@ which makes little sense.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commits.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 -q::
 --quiet::
@@ -432,7 +434,8 @@ If the '--autosquash' option is enabled by default using the
 configuration variable `rebase.autoSquash`, this option can be
 used to override and disable this setting.
 
---[no-]autostash::
+--autostash::
+--no-autostash::
        Automatically create a temporary stash before the operation
        begins, and apply it after the operation ends.  This means
        that you can run rebase on a dirty worktree.  However, use
index 4c6d6de7b77c2f1a70e2335fed0e65e956f99c6f..3c9bf45829e084a2309117b44520ad4275e9d4d7 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
 'git remote remove' <name>
 'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>)
 'git remote set-branches' [--add] <name> <branch>...
+'git remote get-url' [--push] [--all] <name>
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
@@ -131,6 +132,15 @@ The named branches will be interpreted as if specified with the
 With `--add`, instead of replacing the list of currently tracked
 branches, adds to that list.
 
+'get-url'::
+
+Retrieves the URLs for a remote. Configurations for `insteadOf` and
+`pushInsteadOf` are expanded here. By default, only the first URL is listed.
++
+With '--push', push URLs are queried rather than fetch URLs.
++
+With '--all', all URLs for the remote will be listed.
+
 'set-url'::
 
 Changes URLs for the remote. Sets first URL for remote <name> that matches
index 7b49c85347ec583c4d9849a3f0bc878d776e9519..ef22f1775b634812a6d0595ca3d88ce0b0c51506 100644 (file)
@@ -45,7 +45,7 @@ SYNOPSIS
             [ --regexp-ignore-case | -i ]
             [ --extended-regexp | -E ]
             [ --fixed-strings | -F ]
-            [ --date=(local|relative|default|iso|iso-strict|rfc|short) ]
+            [ --date=<format>]
             [ [ --objects | --objects-edge | --objects-edge-aggressive ]
               [ --unpacked ] ]
             [ --pretty | --header ]
index cceb5f2f7fa0c41174215901a091158161d89bc3..b15139ffdcda488c9e2314540885965723292a48 100644 (file)
@@ -8,7 +8,7 @@ git-revert - Revert some existing commits
 SYNOPSIS
 --------
 [verse]
-'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<key-id>]] <commit>...
+'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>...
 'git revert' --continue
 'git revert' --quit
 'git revert' --abort
@@ -80,9 +80,11 @@ more details.
 This is useful when reverting more than one commits'
 effect to your index in a row.
 
--S[<key-id>]::
---gpg-sign[=<key-id>]::
-       GPG-sign commits.
+-S[<keyid>]::
+--gpg-sign[=<keyid>]::
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 -s::
 --signoff::
index 375213fe463fa3cd9cb7644cff60ee656c13dcd8..92df596e5fe9757fee399078e05e3ba44196fda3 100644 (file)
@@ -95,6 +95,8 @@ show [<stash>]::
        shows the latest one. By default, the command shows the diffstat, but
        it will accept any format known to 'git diff' (e.g., `git stash show
        -p stash@{1}` to view the second most recent stash in patch form).
+       You can use stash.showStat and/or stash.showPatch config variables
+       to change the default behavior.
 
 pop [--index] [-q|--quiet] [<stash>]::
 
index 335f3123353482cfd708420dac3b766f181e89ff..e1e8f57cdd217b43b9b04bc54381e9b155d9cbde 100644 (file)
@@ -53,8 +53,9 @@ OPTIONS
 --untracked-files[=<mode>]::
        Show untracked files.
 +
-The mode parameter is optional (defaults to 'all'), and is used to
-specify the handling of untracked files.
+The mode parameter is used to specify the handling of untracked files.
+It is optional: it defaults to 'all', and if specified, it must be
+stuck to the option (e.g. `-uno`, but not `-u no`).
 +
 The possible options are:
 +
index 84f6496bf228454acaa04e570f7857ba1975cda4..7220e5eca1bddfcaf9828522c7f04ab78418874e 100644 (file)
@@ -9,11 +9,12 @@ git-tag - Create, list, delete or verify a tag object signed with GPG
 SYNOPSIS
 --------
 [verse]
-'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
+'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>]
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
-       [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
+       [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
+       [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -24,19 +25,19 @@ to delete, list or verify tags.
 
 Unless `-f` is given, the named tag must not yet exist.
 
-If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
+If one of `-a`, `-s`, or `-u <keyid>` is passed, the command
 creates a 'tag' object, and requires a tag message.  Unless
 `-m <msg>` or `-F <file>` is given, an editor is started for the user to type
 in the tag message.
 
-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
+If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <keyid>`
 are absent, `-a` is implied.
 
 Otherwise just a tag reference for the SHA-1 object name of the commit object is
 created (i.e. a lightweight tag).
 
 A GnuPG signed tag object will be created when `-s` or `-u
-<key-id>` is used.  When `-u <key-id>` is not used, the
+<keyid>` is used.  When `-u <keyid>` is not used, the
 committer identity for the current user is used to find the
 GnuPG key for signing.         The configuration variable `gpg.program`
 is used to specify custom GnuPG binary.
@@ -63,8 +64,8 @@ OPTIONS
 --sign::
        Make a GPG-signed tag, using the default e-mail address's key.
 
--u <key-id>::
---local-user=<key-id>::
+-u <keyid>::
+--local-user=<keyid>::
        Make a GPG-signed tag, using the given key.
 
 -f::
@@ -94,14 +95,16 @@ OPTIONS
        using fnmatch(3)).  Multiple patterns may be given; if any of
        them matches, the tag is shown.
 
---sort=<type>::
-       Sort in a specific order. Supported type is "refname"
-       (lexicographic order), "version:refname" or "v:refname" (tag
+--sort=<key>::
+       Sort based on the key given.  Prefix `-` to sort in
+       descending order of the value. You may use the --sort=<key> option
+       multiple times, in which case the last key becomes the primary
+       key. Also supports "version:refname" or "v:refname" (tag
        names are treated as versions). The "version:refname" sort
        order can also be affected by the
-       "versionsort.prereleaseSuffix" configuration variable. Prepend
-       "-" to reverse sort order. When this option is not given, the
-       sort order defaults to the value configured for the 'tag.sort'
+       "versionsort.prereleaseSuffix" configuration variable.
+       The keys supported are the same as those in `git for-each-ref`.
+       Sort order defaults to the value configured for the 'tag.sort'
        variable if it exists, or lexicographic order otherwise. See
        linkgit:git-config[1].
 
@@ -125,14 +128,14 @@ This option is only applicable when listing tags without annotation lines.
        Use the given tag message (instead of prompting).
        If multiple `-m` options are given, their values are
        concatenated as separate paragraphs.
-       Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
+       Implies `-a` if none of `-a`, `-s`, or `-u <keyid>`
        is given.
 
 -F <file>::
 --file=<file>::
        Take the tag message from the given file.  Use '-' to
        read the message from the standard input.
-       Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
+       Implies `-a` if none of `-a`, `-s`, or `-u <keyid>`
        is given.
 
 --cleanup=<mode>::
@@ -156,6 +159,16 @@ This option is only applicable when listing tags without annotation lines.
        The object that the new tag will refer to, usually a commit.
        Defaults to HEAD.
 
+<format>::
+       A string that interpolates `%(fieldname)` from the object
+       pointed at by a ref being shown.  The format is the same as
+       that of linkgit:git-for-each-ref[1].  When unspecified,
+       defaults to `%(refname:short)`.
+
+--[no-]merged [<commit>]::
+       Only list tags whose tips are reachable, or not reachable
+       if '--no-merged' is used, from the specified commit ('HEAD'
+       if not specified).
 
 CONFIGURATION
 -------------
@@ -166,7 +179,7 @@ it in the repository configuration as follows:
 
 -------------------------------------
 [user]
-    signingKey = <gpg-key-id>
+    signingKey = <gpg-keyid>
 -------------------------------------
 
 
index 8c6478b2f2cab195dcc07f47cfc59cbace286fda..e225974253833c97980e5ff190e291c8323757c9 100644 (file)
@@ -413,8 +413,9 @@ exclude;;
 
 [[def_per_worktree_ref]]per-worktree ref::
        Refs that are per-<<def_working_tree,worktree>>, rather than
-       global.  This is presently only <<def_HEAD,HEAD>>, but might
-       later include other unusual refs.
+       global.  This is presently only <<def_HEAD,HEAD>> and any refs
+       that start with `refs/bisect/`, but might later include other
+       unusual refs.
 
 [[def_pseudoref]]pseudoref::
        Pseudorefs are a class of files under `$GIT_DIR` which behave
index f1c52208f08c3dc9f50e8d18f546a96276a47fec..4f009d44240e3725e25bc8a9a3acd999d69cc487 100644 (file)
@@ -701,15 +701,19 @@ include::pretty-options.txt[]
 --relative-date::
        Synonym for `--date=relative`.
 
---date=(relative|local|default|iso|iso-strict|rfc|short|raw)::
+--date=<format>::
        Only takes effect for dates shown in human-readable format, such
        as when using `--pretty`. `log.date` config variable sets a default
-       value for the log command's `--date` option.
+       value for the log command's `--date` option. By default, dates
+       are shown in the original time zone (either committer's or
+       author's). If `-local` is appended to the format (e.g.,
+       `iso-local`), the user's local time zone is used instead.
 +
 `--date=relative` shows dates relative to the current time,
-e.g. ``2 hours ago''.
+e.g. ``2 hours ago''. The `-local` option cannot be used with
+`--raw` or `--relative`.
 +
-`--date=local` shows timestamps in user's local time zone.
+`--date=local` is an alias for `--date=default-local`.
 +
 `--date=iso` (or `--date=iso8601`) shows timestamps in a ISO 8601-like format.
 The differences to the strict ISO 8601 format are:
@@ -732,10 +736,15 @@ format, often found in email messages.
 `--date=format:...` feeds the format `...` to your system `strftime`.
 Use `--date=format:%c` to show the date in your system locale's
 preferred format.  See the `strftime` manual for a complete list of
-format placeholders.
+format placeholders. When using `-local`, the correct syntax is
+`--date=format-local:...`.
 +
-`--date=default` shows timestamps in the original time zone
-(either committer's or author's).
+`--date=default` is the default format, and is similar to
+`--date=rfc2822`, with a few exceptions:
+
+       - there is no comma after the day-of-week
+
+       - the time zone is omitted when the local time zone is used
 
 ifdef::git-rev-list[]
 --header::
index e1aba8533f5b6d63ac01fb06961843a8f32f75e9..97f58d1dbb5fa4b0b921061371befe9822116941 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.6.1
+DEF_VER=v2.6.0
 
 LF='
 '
index 8d5df7ea1ee672753e2f29302ef9a1a7db77638e..36bc54ccd13f8c8048a9fe00f3b2763fdf81278a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -375,6 +375,9 @@ ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)
 ALL_LDFLAGS = $(LDFLAGS)
 STRIP ?= strip
 
+# Create as necessary, replace existing, make ranlib unneeded.
+ARFLAGS = rcs
+
 # Among the variables below, these:
 #   gitexecdir
 #   template_dir
@@ -902,6 +905,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-ref.o
 BUILTIN_OBJS += builtin/stripspace.o
+BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
 BUILTIN_OBJS += builtin/tag.o
 BUILTIN_OBJS += builtin/unpack-file.o
@@ -1465,13 +1469,13 @@ endif
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
-ifneq ($(findstring $(MAKEFLAGS),w),w)
+ifneq ($(findstring w,$(MAKEFLAGS)),w)
 PRINT_DIR = --no-print-directory
 else # "make -w"
 NO_SUBDIR = :
 endif
 
-ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifneq ($(findstring s,$(MAKEFLAGS)),s)
 ifndef V
        QUIET_CC       = @echo '   ' CC $@;
        QUIET_AR       = @echo '   ' AR $@;
@@ -1995,13 +1999,13 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 $(VCSSVN_LIB): $(VCSSVN_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 export DEFAULT_EDITOR DEFAULT_PAGER
 
index 041a13d093a21597c60d799c0f6943998f2d6a8a..053d1a2ab91a6c41a0fe957569ff905d12b00bee 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -730,6 +730,11 @@ static void handle_bad_merge_base(void)
                                "This means the bug has been fixed "
                                "between %s and [%s].\n",
                                bad_hex, bad_hex, good_hex);
+               } else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
+                       fprintf(stderr, "The merge base %s is new.\n"
+                               "The property has changed "
+                               "between %s and [%s].\n",
+                               bad_hex, bad_hex, good_hex);
                } else {
                        fprintf(stderr, "The merge base %s is %s.\n"
                                "This means the first '%s' commit is "
@@ -762,11 +767,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
 }
 
 /*
- * "check_merge_bases" checks that merge bases are not "bad".
+ * "check_merge_bases" checks that merge bases are not "bad" (or "new").
  *
- * - If one is "bad", it means the user assumed something wrong
+ * - If one is "bad" (or "new"), it means the user assumed something wrong
  * and we must exit with a non 0 error code.
- * - If one is "good", that's good, we have nothing to do.
+ * - If one is "good" (or "old"), that's good, we have nothing to do.
  * - If one is "skipped", we can't know but we should warn.
  * - If we don't know, we should check it out and ask the user to test.
  */
index 79aaf0afe8912d154573df3baa6ea5c28f8097d6..6b95006a0a38d8bec522342fb72a5edeb0af7f07 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -120,6 +120,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
+extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
index 245d253d041021429265eb81fb008e528613af34..203a981cd083a0ea971c7315f60f04e721d1e468 100644 (file)
@@ -1371,8 +1371,15 @@ static void pass_whole_blame(struct scoreboard *sb,
  */
 static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
 {
-       if (!reverse)
+       if (!reverse) {
+               if (revs->first_parent_only &&
+                   commit->parents &&
+                   commit->parents->next) {
+                       free_commit_list(commit->parents->next);
+                       commit->parents->next = NULL;
+               }
                return commit->parents;
+       }
        return lookup_decoration(&revs->children, &commit->object);
 }
 
@@ -2605,7 +2612,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                   fewer display columns. */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
-       case DATE_LOCAL:
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
@@ -2685,6 +2691,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
        else if (contents_from)
                die("--contents and --children do not blend well.");
+       else if (revs.first_parent_only)
+               die("combining --first-parent and --reverse is not supported");
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
index ff05869949b7c5c4cd0034e7a0ba54e2adeae6b4..3ba4d1bd3b2507303d95c449a6ab926db8f350d9 100644 (file)
@@ -636,6 +636,10 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
        cb.pattern = pattern;
        cb.ret = 0;
        for_each_rawref(append_ref, &cb);
+       /*
+        * The following implementation is currently duplicated in ref-filter. It
+        * will eventually be removed when we port branch.c to use ref-filter APIs.
+        */
        if (merge_filter != NO_FILTER) {
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
@@ -746,6 +750,10 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        strbuf_release(&newsection);
 }
 
+/*
+ * This function is duplicated in ref-filter. It will eventually be removed
+ * when we port branch.c to use ref-filter APIs.
+ */
 static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
 {
        merge_filter = ((opt->long_name[0] == 'n')
@@ -821,18 +829,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__COLOR(&branch_use_color, N_("use colored output")),
                OPT_SET_INT('r', "remotes",     &kinds, N_("act on remote-tracking branches"),
                        REF_REMOTE_BRANCH),
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t) "HEAD",
-               },
+               OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")),
+               OPT_WITH(&with_commit, N_("print only branches that contain the commit")),
                OPT__ABBREV(&abbrev),
 
                OPT_GROUP(N_("Specific git-branch actions:")),
index 7919206187c9968cc974c10bb9b5f75c7574e630..4e9f6c29bf1e0c1cc7b44548f49077c2e4a81ec8 100644 (file)
@@ -7,6 +7,9 @@
 
 static char const * const for_each_ref_usage[] = {
        N_("git for-each-ref [<options>] [<pattern>]"),
+       N_("git for-each-ref [--points-at <object>]"),
+       N_("git for-each-ref [(--merged | --no-merged) [<object>]]"),
+       N_("git for-each-ref [--contains [<object>]]"),
        NULL
 };
 
@@ -34,9 +37,18 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
                            N_("field name to sort on"), &parse_opt_ref_sorting),
+               OPT_CALLBACK(0, "points-at", &filter.points_at,
+                            N_("object"), N_("print only refs which points at the given object"),
+                            parse_opt_object_name),
+               OPT_MERGED(&filter, N_("print only refs that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
                OPT_END(),
        };
 
+       memset(&array, 0, sizeof(array));
+       memset(&filter, 0, sizeof(filter));
+
        parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
        if (maxcount < 0) {
                error("invalid --count argument: `%d'", maxcount);
@@ -55,9 +67,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
        /* for warn_ambiguous_refs */
        git_config(git_default_config, NULL);
 
-       memset(&array, 0, sizeof(array));
-       memset(&filter, 0, sizeof(filter));
        filter.name_patterns = argv;
+       filter.match_as_path = 1;
        filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
        ref_array_sort(sorting, &array);
 
index 181668dedddef9bf79ab91d9740607cf31115a16..e4c3ea130c98b01f87bed617a6386b03158b42d5 100644 (file)
@@ -18,6 +18,7 @@ static const char * const builtin_remote_usage[] = {
        N_("git remote prune [-n | --dry-run] <name>"),
        N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"),
        N_("git remote set-branches [--add] <name> <branch>..."),
+       N_("git remote get-url [--push] [--all] <name>"),
        N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
        N_("git remote set-url --add <name> <newurl>"),
        N_("git remote set-url --delete <name> <url>"),
@@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = {
        NULL
 };
 
+static const char * const builtin_remote_geturl_usage[] = {
+       N_("git remote get-url [--push] [--all] <name>"),
+       NULL
+};
+
 static const char * const builtin_remote_seturl_usage[] = {
        N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
        N_("git remote set-url --add <name> <newurl>"),
@@ -1467,6 +1473,57 @@ static int set_branches(int argc, const char **argv)
        return set_remote_branches(argv[0], argv + 1, add_mode);
 }
 
+static int get_url(int argc, const char **argv)
+{
+       int i, push_mode = 0, all_mode = 0;
+       const char *remotename = NULL;
+       struct remote *remote;
+       const char **url;
+       int url_nr;
+       struct option options[] = {
+               OPT_BOOL('\0', "push", &push_mode,
+                        N_("query push URLs rather than fetch URLs")),
+               OPT_BOOL('\0', "all", &all_mode,
+                        N_("return all URLs")),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0);
+
+       if (argc != 1)
+               usage_with_options(builtin_remote_geturl_usage, options);
+
+       remotename = argv[0];
+
+       if (!remote_is_configured(remotename))
+               die(_("No such remote '%s'"), remotename);
+       remote = remote_get(remotename);
+
+       url_nr = 0;
+       if (push_mode) {
+               url = remote->pushurl;
+               url_nr = remote->pushurl_nr;
+       }
+       /* else fetch mode */
+
+       /* Use the fetch URL when no push URLs were found or requested. */
+       if (!url_nr) {
+               url = remote->url;
+               url_nr = remote->url_nr;
+       }
+
+       if (!url_nr)
+               die(_("no URLs configured for remote '%s'"), remotename);
+
+       if (all_mode) {
+               for (i = 0; i < url_nr; i++)
+                       printf_ln("%s", url[i]);
+       } else {
+               printf_ln("%s", *url);
+       }
+
+       return 0;
+}
+
 static int set_url(int argc, const char **argv)
 {
        int i, push_mode = 0, add_mode = 0, delete_mode = 0;
@@ -1576,6 +1633,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = set_head(argc, argv);
        else if (!strcmp(argv[0], "set-branches"))
                result = set_branches(argc, argv);
+       else if (!strcmp(argv[0], "get-url"))
+               result = get_url(argc, argv);
        else if (!strcmp(argv[0], "set-url"))
                result = set_url(argc, argv);
        else if (!strcmp(argv[0], "show"))
index 88e1359ebcaad338d96dd942a5f88f54d6cce271..1bf72423bf72d7b7d224c14808b0652d78305d63 100644 (file)
@@ -104,9 +104,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                        return 0;
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
-                       const char *name = (const char *)merge_rr.items[i].util;
-                       if (diff_two(rerere_path(name, "preimage"), path, path, path))
-                               die("unable to generate diff for %s", name);
+                       const struct rerere_id *id = merge_rr.items[i].util;
+                       if (diff_two(rerere_path(id, "preimage"), path, path, path))
+                               die("unable to generate diff for %s", rerere_path(id, NULL));
                }
        } else
                usage_with_options(rerere_usage, options);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
new file mode 100644 (file)
index 0000000..f4c3eff
--- /dev/null
@@ -0,0 +1,282 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "dir.h"
+#include "utf8.h"
+#include "submodule.h"
+#include "submodule-config.h"
+#include "string-list.h"
+#include "run-command.h"
+
+struct module_list {
+       const struct cache_entry **entries;
+       int alloc, nr;
+};
+#define MODULE_LIST_INIT { NULL, 0, 0 }
+
+static int module_list_compute(int argc, const char **argv,
+                              const char *prefix,
+                              struct pathspec *pathspec,
+                              struct module_list *list)
+{
+       int i, result = 0;
+       char *max_prefix, *ps_matched = NULL;
+       int max_prefix_len;
+       parse_pathspec(pathspec, 0,
+                      PATHSPEC_PREFER_FULL |
+                      PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+                      prefix, argv);
+
+       /* Find common prefix for all pathspec's */
+       max_prefix = common_prefix(pathspec);
+       max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
+
+       if (pathspec->nr)
+               ps_matched = xcalloc(pathspec->nr, 1);
+
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
+
+       for (i = 0; i < active_nr; i++) {
+               const struct cache_entry *ce = active_cache[i];
+
+               if (!S_ISGITLINK(ce->ce_mode) ||
+                   !match_pathspec(pathspec, ce->name, ce_namelen(ce),
+                                   max_prefix_len, ps_matched, 1))
+                       continue;
+
+               ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+               list->entries[list->nr++] = ce;
+               while (i + 1 < active_nr &&
+                      !strcmp(ce->name, active_cache[i + 1]->name))
+                       /*
+                        * Skip entries with the same name in different stages
+                        * to make sure an entry is returned only once.
+                        */
+                       i++;
+       }
+       free(max_prefix);
+
+       if (ps_matched && report_path_error(ps_matched, pathspec, prefix))
+               result = -1;
+
+       free(ps_matched);
+
+       return result;
+}
+
+static int module_list(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+
+       struct option module_list_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper list [--prefix=<path>] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_list_options,
+                            git_submodule_helper_usage, 0);
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) {
+               printf("#unmatched\n");
+               return 1;
+       }
+
+       for (i = 0; i < list.nr; i++) {
+               const struct cache_entry *ce = list.entries[i];
+
+               if (ce_stage(ce))
+                       printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1));
+               else
+                       printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce));
+
+               utf8_fprintf(stdout, "%s\n", ce->name);
+       }
+       return 0;
+}
+
+static int module_name(int argc, const char **argv, const char *prefix)
+{
+       const struct submodule *sub;
+
+       if (argc != 2)
+               usage(_("git submodule--helper name <path>"));
+
+       gitmodules_config();
+       sub = submodule_from_path(null_sha1, argv[1]);
+
+       if (!sub)
+               die(_("no submodule mapping found in .gitmodules for path '%s'"),
+                   argv[1]);
+
+       printf("%s\n", sub->name);
+
+       return 0;
+}
+static int clone_submodule(const char *path, const char *gitdir, const char *url,
+                          const char *depth, const char *reference, int quiet)
+{
+       struct child_process cp;
+       child_process_init(&cp);
+
+       argv_array_push(&cp.args, "clone");
+       argv_array_push(&cp.args, "--no-checkout");
+       if (quiet)
+               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 (gitdir && *gitdir)
+               argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+
+       argv_array_push(&cp.args, url);
+       argv_array_push(&cp.args, path);
+
+       cp.git_cmd = 1;
+       cp.env = local_repo_env;
+       cp.no_stdin = 1;
+
+       return run_command(&cp);
+}
+
+static int module_clone(int argc, const char **argv, const char *prefix)
+{
+       const char *path = NULL, *name = NULL, *url = NULL;
+       const char *reference = NULL, *depth = NULL;
+       int quiet = 0;
+       FILE *submodule_dot_git;
+       char *sm_gitdir, *cwd, *p;
+       struct strbuf rel_path = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT;
+
+       struct option module_clone_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT_STRING(0, "path", &path,
+                          N_("path"),
+                          N_("where the new submodule will be cloned to")),
+               OPT_STRING(0, "name", &name,
+                          N_("string"),
+                          N_("name of the new submodule")),
+               OPT_STRING(0, "url", &url,
+                          N_("string"),
+                          N_("url where to clone the submodule from")),
+               OPT_STRING(0, "reference", &reference,
+                          N_("string"),
+                          N_("reference repository")),
+               OPT_STRING(0, "depth", &depth,
+                          N_("string"),
+                          N_("depth for shallow clones")),
+               OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
+                  "[--reference <repository>] [--name <name>] [--url <url>]"
+                  "[--depth <depth>] [--] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_clone_options,
+                            git_submodule_helper_usage, 0);
+
+       strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
+       sm_gitdir = strbuf_detach(&sb, NULL);
+
+       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))
+                       die(_("clone of '%s' into submodule path '%s' failed"),
+                           url, path);
+       } else {
+               if (safe_create_leading_directories_const(path) < 0)
+                       die(_("could not create directory '%s'"), path);
+               strbuf_addf(&sb, "%s/index", sm_gitdir);
+               unlink_or_warn(sb.buf);
+               strbuf_reset(&sb);
+       }
+
+       /* Write a .git file in the submodule to redirect to the superproject. */
+       if (safe_create_leading_directories_const(path) < 0)
+               die(_("could not create directory '%s'"), path);
+
+       if (path && *path)
+               strbuf_addf(&sb, "%s/.git", path);
+       else
+               strbuf_addstr(&sb, ".git");
+
+       if (safe_create_leading_directories_const(sb.buf) < 0)
+               die(_("could not create leading directories of '%s'"), sb.buf);
+       submodule_dot_git = fopen(sb.buf, "w");
+       if (!submodule_dot_git)
+               die_errno(_("cannot open file '%s'"), sb.buf);
+
+       fprintf(submodule_dot_git, "gitdir: %s\n",
+               relative_path(sm_gitdir, path, &rel_path));
+       if (fclose(submodule_dot_git))
+               die(_("could not close file %s"), sb.buf);
+       strbuf_reset(&sb);
+       strbuf_reset(&rel_path);
+
+       cwd = xgetcwd();
+       /* Redirect the worktree of the submodule in the superproject's config */
+       if (!is_absolute_path(sm_gitdir)) {
+               strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir);
+               free(sm_gitdir);
+               sm_gitdir = strbuf_detach(&sb, NULL);
+       }
+
+       strbuf_addf(&sb, "%s/%s", cwd, path);
+       p = git_pathdup_submodule(path, "config");
+       if (!p)
+               die(_("could not get submodule directory for '%s'"), path);
+       git_config_set_in_file(p, "core.worktree",
+                              relative_path(sb.buf, sm_gitdir, &rel_path));
+       strbuf_release(&sb);
+       strbuf_release(&rel_path);
+       free(sm_gitdir);
+       free(cwd);
+       free(p);
+       return 0;
+}
+
+struct cmd_struct {
+       const char *cmd;
+       int (*fn)(int, const char **, const char *);
+};
+
+static struct cmd_struct commands[] = {
+       {"list", module_list},
+       {"name", module_name},
+       {"clone", module_clone},
+};
+
+int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       if (argc < 2)
+               die(_("fatal: submodule--helper subcommand must be "
+                     "called with a subcommand"));
+
+       for (i = 0; i < ARRAY_SIZE(commands); i++)
+               if (!strcmp(argv[1], commands[i].cmd))
+                       return commands[i].fn(argc - 1, argv + 1, prefix);
+
+       die(_("fatal: '%s' is not a valid submodule--helper "
+             "subcommand"), argv[1]);
+}
index cba0e2266623d57fe96fcec1910c562bf424d143..9e17dca7cac30738c966d0e23258f56c0cb9bc70 100644 (file)
 #include "gpg-interface.h"
 #include "sha1-array.h"
 #include "column.h"
+#include "ref-filter.h"
 
 static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-               "\n\t\t[<pattern>...]"),
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
 };
 
-#define STRCMP_SORT     0      /* must be zero */
-#define VERCMP_SORT     1
-#define SORT_MASK       0x7fff
-#define REVERSE_SORT    0x8000
-
-static int tag_sort;
-
-struct tag_filter {
-       const char **patterns;
-       int lines;
-       int sort;
-       struct string_list tags;
-       struct commit_list *with_commit;
-};
-
-static struct sha1_array points_at;
 static unsigned int colopts;
 
-static int match_pattern(const char **patterns, const char *ref)
-{
-       /* no pattern means match everything */
-       if (!*patterns)
-               return 1;
-       for (; *patterns; patterns++)
-               if (!wildmatch(*patterns, ref, 0, NULL))
-                       return 1;
-       return 0;
-}
-
-static const unsigned char *match_points_at(const char *refname,
-                                           const unsigned char *sha1)
-{
-       const unsigned char *tagged_sha1 = NULL;
-       struct object *obj;
-
-       if (sha1_array_lookup(&points_at, sha1) >= 0)
-               return sha1;
-       obj = parse_object(sha1);
-       if (!obj)
-               die(_("malformed object at '%s'"), refname);
-       if (obj->type == OBJ_TAG)
-               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
-       if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
-               return tagged_sha1;
-       return NULL;
-}
-
-static int in_commit_list(const struct commit_list *want, struct commit *c)
-{
-       for (; want; want = want->next)
-               if (!hashcmp(want->item->object.sha1, c->object.sha1))
-                       return 1;
-       return 0;
-}
-
-enum contains_result {
-       CONTAINS_UNKNOWN = -1,
-       CONTAINS_NO = 0,
-       CONTAINS_YES = 1
-};
-
-/*
- * Test whether the candidate or one of its parents is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
- */
-static enum contains_result contains_test(struct commit *candidate,
-                           const struct commit_list *want)
-{
-       /* was it previously marked as containing a want commit? */
-       if (candidate->object.flags & TMP_MARK)
-               return 1;
-       /* or marked as not possibly containing a want commit? */
-       if (candidate->object.flags & UNINTERESTING)
-               return 0;
-       /* or are we it? */
-       if (in_commit_list(want, candidate)) {
-               candidate->object.flags |= TMP_MARK;
-               return 1;
-       }
-
-       if (parse_commit(candidate) < 0)
-               return 0;
-
-       return -1;
-}
-
-/*
- * Mimicking the real stack, this stack lives on the heap, avoiding stack
- * overflows.
- *
- * At each recursion step, the stack items points to the commits whose
- * ancestors are to be inspected.
- */
-struct stack {
-       int nr, alloc;
-       struct stack_entry {
-               struct commit *commit;
-               struct commit_list *parents;
-       } *stack;
-};
-
-static void push_to_stack(struct commit *candidate, struct stack *stack)
-{
-       int index = stack->nr++;
-       ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
-       stack->stack[index].commit = candidate;
-       stack->stack[index].parents = candidate->parents;
-}
-
-static enum contains_result contains(struct commit *candidate,
-               const struct commit_list *want)
-{
-       struct stack stack = { 0, 0, NULL };
-       int result = contains_test(candidate, want);
-
-       if (result != CONTAINS_UNKNOWN)
-               return result;
-
-       push_to_stack(candidate, &stack);
-       while (stack.nr) {
-               struct stack_entry *entry = &stack.stack[stack.nr - 1];
-               struct commit *commit = entry->commit;
-               struct commit_list *parents = entry->parents;
-
-               if (!parents) {
-                       commit->object.flags |= UNINTERESTING;
-                       stack.nr--;
-               }
-               /*
-                * If we just popped the stack, parents->item has been marked,
-                * therefore contains_test will return a meaningful 0 or 1.
-                */
-               else switch (contains_test(parents->item, want)) {
-               case CONTAINS_YES:
-                       commit->object.flags |= TMP_MARK;
-                       stack.nr--;
-                       break;
-               case CONTAINS_NO:
-                       entry->parents = parents->next;
-                       break;
-               case CONTAINS_UNKNOWN:
-                       push_to_stack(parents->item, &stack);
-                       break;
-               }
-       }
-       free(stack.stack);
-       return contains_test(candidate, want);
-}
-
-static void show_tag_lines(const struct object_id *oid, int lines)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
+       struct ref_array array;
+       char *to_free = NULL;
        int i;
-       unsigned long size;
-       enum object_type type;
-       char *buf, *sp, *eol;
-       size_t len;
-
-       buf = read_sha1_file(oid->hash, &type, &size);
-       if (!buf)
-               die_errno("unable to read object %s", oid_to_hex(oid));
-       if (type != OBJ_COMMIT && type != OBJ_TAG)
-               goto free_return;
-       if (!size)
-               die("an empty %s object %s?",
-                   typename(type), oid_to_hex(oid));
-
-       /* skip header */
-       sp = strstr(buf, "\n\n");
-       if (!sp)
-               goto free_return;
-
-       /* only take up to "lines" lines, and strip the signature from a tag */
-       if (type == OBJ_TAG)
-               size = parse_signature(buf, size);
-       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
-               if (i)
-                       printf("\n    ");
-               eol = memchr(sp, '\n', size - (sp - buf));
-               len = eol ? eol - sp : size - (sp - buf);
-               fwrite(sp, len, 1, stdout);
-               if (!eol)
-                       break;
-               sp = eol + 1;
-       }
-free_return:
-       free(buf);
-}
-
-static int show_reference(const char *refname, const struct object_id *oid,
-                         int flag, void *cb_data)
-{
-       struct tag_filter *filter = cb_data;
 
-       if (match_pattern(filter->patterns, refname)) {
-               if (filter->with_commit) {
-                       struct commit *commit;
+       memset(&array, 0, sizeof(array));
 
-                       commit = lookup_commit_reference_gently(oid->hash, 1);
-                       if (!commit)
-                               return 0;
-                       if (!contains(commit, filter->with_commit))
-                               return 0;
-               }
-
-               if (points_at.nr && !match_points_at(refname, oid->hash))
-                       return 0;
+       if (filter->lines == -1)
+               filter->lines = 0;
 
-               if (!filter->lines) {
-                       if (filter->sort)
-                               string_list_append(&filter->tags, refname);
-                       else
-                               printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-               show_tag_lines(oid, filter->lines);
-               putchar('\n');
+       if (!format) {
+               if (filter->lines) {
+                       to_free = xstrfmt("%s %%(contents:lines=%d)",
+                                         "%(align:15)%(refname:short)%(end)",
+                                         filter->lines);
+                       format = to_free;
+               } else
+                       format = "%(refname:short)";
        }
 
-       return 0;
-}
+       verify_ref_format(format);
+       filter_refs(&array, filter, FILTER_REFS_TAGS);
+       ref_array_sort(sorting, &array);
 
-static int sort_by_version(const void *a_, const void *b_)
-{
-       const struct string_list_item *a = a_;
-       const struct string_list_item *b = b_;
-       return versioncmp(a->string, b->string);
-}
+       for (i = 0; i < array.nr; i++)
+               show_ref_array_item(array.items[i], format, 0);
+       ref_array_clear(&array);
+       free(to_free);
 
-static int list_tags(const char **patterns, int lines,
-                    struct commit_list *with_commit, int sort)
-{
-       struct tag_filter filter;
-
-       filter.patterns = patterns;
-       filter.lines = lines;
-       filter.sort = sort;
-       filter.with_commit = with_commit;
-       memset(&filter.tags, 0, sizeof(filter.tags));
-       filter.tags.strdup_strings = 1;
-
-       for_each_tag_ref(show_reference, (void *)&filter);
-       if (sort) {
-               int i;
-               if ((sort & SORT_MASK) == VERCMP_SORT)
-                       qsort(filter.tags.items, filter.tags.nr,
-                             sizeof(struct string_list_item), sort_by_version);
-               if (sort & REVERSE_SORT)
-                       for (i = filter.tags.nr - 1; i >= 0; i--)
-                               printf("%s\n", filter.tags.items[i].string);
-               else
-                       for (i = 0; i < filter.tags.nr; i++)
-                               printf("%s\n", filter.tags.items[i].string);
-               string_list_clear(&filter.tags, 0);
-       }
        return 0;
 }
 
@@ -348,35 +126,26 @@ static const char tag_template_nocleanup[] =
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
 
-/*
- * Parse a sort string, and return 0 if parsed successfully. Will return
- * non-zero when the sort string does not parse into a known type. If var is
- * given, the error message becomes a warning and includes information about
- * the configuration value.
- */
-static int parse_sort_string(const char *var, const char *arg, int *sort)
+/* Parse arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
 {
-       int type = 0, flags = 0;
+       struct ref_sorting *s;
+       int len;
 
-       if (skip_prefix(arg, "-", &arg))
-               flags |= REVERSE_SORT;
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
 
-       if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
-               type = VERCMP_SORT;
-       else
-               type = STRCMP_SORT;
-
-       if (strcmp(arg, "refname")) {
-               if (!var)
-                       return error(_("unsupported sort specification '%s'"), arg);
-               else {
-                       warning(_("unsupported sort specification '%s' in variable '%s'"),
-                               var, arg);
-                       return -1;
-               }
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
 
-       *sort = (type | flags);
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
 
        return 0;
 }
@@ -384,11 +153,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
        int status;
+       struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
 
        if (!strcmp(var, "tag.sort")) {
                if (!value)
                        return config_error_nonbool(var);
-               parse_sort_string(var, value, &tag_sort);
+               parse_sorting_string(value, sorting_tail);
                return 0;
        }
 
@@ -546,30 +316,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
        return check_refname_format(sb->buf, 0);
 }
 
-static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
-                       const char *arg, int unset)
-{
-       unsigned char sha1[20];
-
-       if (unset) {
-               sha1_array_clear(&points_at);
-               return 0;
-       }
-       if (!arg)
-               return error(_("switch 'points-at' requires an object"));
-       if (get_sha1(arg, sha1))
-               return error(_("malformed object name '%s'"), arg);
-       sha1_array_append(&points_at, sha1);
-       return 0;
-}
-
-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
-{
-       int *sort = opt->value;
-
-       return parse_sort_string(NULL, arg, sort);
-}
-
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -578,17 +324,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        const char *object_ref, *tag;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
-       int annotate = 0, force = 0, lines = -1;
        int create_reflog = 0;
+       int annotate = 0, force = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
-       struct commit_list *with_commit = NULL;
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+       const char *format = NULL;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
-               { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+               { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
@@ -610,32 +358,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_MERGED(&filter, N_("print only tags that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
                {
-                       OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
-                       PARSE_OPT_NONEG, parse_opt_sort
-               },
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
-                       N_("print only tags of the object"), 0, parse_opt_points_at
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only tags of the object"), 0, parse_opt_object_name
                },
+               OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
 
-       git_config(git_tag_config, NULL);
+       git_config(git_tag_config, sorting_tail);
 
        memset(&opt, 0, sizeof(opt));
+       memset(&filter, 0, sizeof(filter));
+       filter.lines = -1;
 
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
@@ -652,11 +393,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                usage_with_options(git_tag_usage, options);
 
        finalize_colopts(&colopts, -1);
-       if (cmdmode == 'l' && lines != -1) {
+       if (cmdmode == 'l' && filter.lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
+       if (!sorting)
+               sorting = ref_default_sorting();
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
@@ -665,19 +408,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               if (lines != -1 && tag_sort)
-                       die(_("--sort and -n are incompatible"));
-               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+               filter.name_patterns = argv;
+               ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        }
-       if (lines != -1)
+       if (filter.lines != -1)
                die(_("-n option is only allowed with -l."));
-       if (with_commit)
+       if (filter.with_commit)
                die(_("--contains option is only allowed with -l."));
-       if (points_at.nr)
+       if (filter.points_at.nr)
                die(_("--points-at option is only allowed with -l."));
+       if (filter.merge_commit)
+               die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
        if (cmdmode == 'v')
diff --git a/cache.h b/cache.h
index 79066e57dc806d118366d9807e0af338b72e2fb2..752031e84ab8231a3bf516951b05aa8a6434aa25 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1091,7 +1091,6 @@ struct date_mode {
                DATE_NORMAL = 0,
                DATE_RELATIVE,
                DATE_SHORT,
-               DATE_LOCAL,
                DATE_ISO8601,
                DATE_ISO8601_STRICT,
                DATE_RFC2822,
@@ -1099,6 +1098,7 @@ struct date_mode {
                DATE_RAW
        } type;
        const char *strftime_fmt;
+       int local;
 };
 
 /*
index 27a706f76621621a25b7e58188e5d1da9b9a2ccd..ced4961398d397e0e21661ce7105be293c3585c2 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -724,10 +724,13 @@ struct child_process *git_connect(int fd[2], const char *url,
                strbuf_addch(&cmd, ' ');
                sq_quote_buf(&cmd, path);
 
+               /* remove repo-local variables from the environment */
+               conn->env = local_repo_env;
+               conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
                        const char *ssh;
-                       int putty, tortoiseplink = 0;
+                       int putty = 0, tortoiseplink = 0;
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -750,13 +753,17 @@ struct child_process *git_connect(int fd[2], const char *url,
                        }
 
                        ssh = getenv("GIT_SSH_COMMAND");
-                       if (ssh) {
-                               conn->use_shell = 1;
-                               putty = 0;
-                       } else {
+                       if (!ssh) {
                                const char *base;
                                char *ssh_dup;
 
+                               /*
+                                * GIT_SSH is the no-shell version of
+                                * GIT_SSH_COMMAND (and must remain so for
+                                * historical compatibility).
+                                */
+                               conn->use_shell = 0;
+
                                ssh = getenv("GIT_SSH");
                                if (!ssh)
                                        ssh = "ssh";
@@ -766,8 +773,9 @@ struct child_process *git_connect(int fd[2], const char *url,
 
                                tortoiseplink = !strcasecmp(base, "tortoiseplink") ||
                                        !strcasecmp(base, "tortoiseplink.exe");
-                               putty = !strcasecmp(base, "plink") ||
-                                       !strcasecmp(base, "plink.exe") || tortoiseplink;
+                               putty = tortoiseplink ||
+                                       !strcasecmp(base, "plink") ||
+                                       !strcasecmp(base, "plink.exe");
 
                                free(ssh_dup);
                        }
@@ -782,9 +790,6 @@ struct child_process *git_connect(int fd[2], const char *url,
                        }
                        argv_array_push(&conn->args, ssh_host);
                } else {
-                       /* remove repo-local variables from the environment */
-                       conn->env = local_repo_env;
-                       conn->use_shell = 1;
                        transport_check_allowed("file");
                }
                argv_array_push(&conn->args, cmd.buf);
index 9f065718513c5c1e82d355aa9dab40ee421b765e..308b777b0aa43a7466fb3402cc67c1f023f57a7a 100755 (executable)
@@ -648,7 +648,7 @@ cmd_split()
                debug "Merging split branch into HEAD..."
                latest_old=$(cache_get latest_old)
                git merge -s ours \
-                       -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+                       -m "$(rejoin_msg "$dir" $latest_old $latest_new)" \
                        $latest_new >&2 || exit $?
        fi
        if [ -n "$branch" ]; then
@@ -735,7 +735,7 @@ cmd_push()
            refspec=$2
            echo "git push using: " $repository $refspec
            localrev=$(git subtree split --prefix="$prefix") || die
-           git push $repository $localrev:refs/heads/$refspec
+           git push "$repository" $localrev:refs/heads/$refspec
        else
            die "'$dir' must already exist. Try 'git subtree add'."
        fi
index 90519823be381390b7f89a1eb2f6007f1e8c86f4..dfbe443deaf1739e47d3f0f791331a3ee6773174 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 #
 # Copyright (c) 2012 Avery Pennaraum
+# Copyright (c) 2015 Alexey Shumkin
 #
 test_description='Basic porcelain support for subtrees
 
@@ -32,25 +33,6 @@ check_equal()
        fi
 }
 
-fixnl()
-{
-       t=""
-       while read x; do
-               t="$t$x "
-       done
-       echo $t
-}
-
-multiline()
-{
-       while read x; do
-               set -- $x
-               for d in "$@"; do
-                       echo "$d"
-               done
-       done
-}
-
 undo()
 {
        git reset --hard HEAD~
@@ -62,11 +44,11 @@ last_commit_message()
 }
 
 test_expect_success 'init subproj' '
-       test_create_repo subproj
+       test_create_repo "sub proj"
 '
 
 # To the subproject!
-cd subproj
+cd ./"sub proj"
 
 test_expect_success 'add sub1' '
        create sub1 &&
@@ -106,39 +88,39 @@ test_expect_success 'add main4' '
 '
 
 test_expect_success 'fetch subproj history' '
-       git fetch ./subproj sub1 &&
+       git fetch ./"sub proj" sub1 &&
        git branch sub1 FETCH_HEAD
 '
 
 test_expect_success 'no subtree exists in main tree' '
-       test_must_fail git subtree merge --prefix=subdir sub1
+       test_must_fail git subtree merge --prefix="sub dir" sub1
 '
 
 test_expect_success 'no pull from non-existant subtree' '
-       test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+       test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" sub1
 '
 
 test_expect_success 'check if --message works for add' '
-       git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+       git subtree add --prefix="sub dir" --message="Added subproject" sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject" &&
        undo
 '
 
 test_expect_success 'check if --message works as -m and --prefix as -P' '
-       git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
+       git subtree add -P "sub dir" -m "Added subproject using git subtree" sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
        undo
 '
 
 test_expect_success 'check if --message works with squash too' '
-       git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+       git subtree add -P "sub dir" -m "Added subproject with squash" --squash sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
        undo
 '
 
 test_expect_success 'add subproj to mainline' '
-       git subtree add --prefix=subdir/ FETCH_HEAD &&
-       check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+       git subtree add --prefix="sub dir"/ FETCH_HEAD &&
+       check_equal ''"$(last_commit_message)"'' "Add '"'sub dir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
 '
 
 # this shouldn't actually do anything, since FETCH_HEAD is already a parent
@@ -147,7 +129,7 @@ test_expect_success 'merge fetched subproj' '
 '
 
 test_expect_success 'add main-sub5' '
-       create subdir/main-sub5 &&
+       create "sub dir/main-sub5" &&
        git commit -m "main-sub5"
 '
 
@@ -157,29 +139,29 @@ test_expect_success 'add main6' '
 '
 
 test_expect_success 'add main-sub7' '
-       create subdir/main-sub7 &&
+       create "sub dir/main-sub7" &&
        git commit -m "main-sub7"
 '
 
 test_expect_success 'fetch new subproj history' '
-       git fetch ./subproj sub2 &&
+       git fetch ./"sub proj" sub2 &&
        git branch sub2 FETCH_HEAD
 '
 
 test_expect_success 'check if --message works for merge' '
-       git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+       git subtree merge --prefix="sub dir" -m "Merged changes from subproject" sub2 &&
        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
        undo
 '
 
 test_expect_success 'check if --message for merge works with squash too' '
-       git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
+       git subtree merge --prefix "sub dir" -m "Merged changes from subproject using squash" --squash sub2 &&
        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
        undo
 '
 
 test_expect_success 'merge new subproj history into subdir' '
-       git subtree merge --prefix=subdir FETCH_HEAD &&
+       git subtree merge --prefix="sub dir" FETCH_HEAD &&
        git branch pre-split &&
        check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" &&
        undo
@@ -208,53 +190,53 @@ test_expect_success 'Check that the <prefix> exists for a split' '
 '
 
 test_expect_success 'check if --message works for split+rejoin' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        git branch spl1 "$spl1" &&
        check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
        undo
 '
 
 test_expect_success 'check split with --branch' '
-       spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+       spl1=$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
        undo &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr1 &&
        check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
 '
 
 test_expect_success 'check hash of split' '
-       spl1=$(git subtree split --prefix subdir) &&
-       git subtree split --prefix subdir --branch splitbr1test &&
+       spl1=$(git subtree split --prefix "sub dir") &&
+       git subtree split --prefix "sub dir" --branch splitbr1test &&
        check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" &&
        new_hash=$(git rev-parse splitbr1test~2) &&
        check_equal ''"$new_hash"'' "$subdir_hash"
 '
 
 test_expect_success 'check split with --branch for an existing branch' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        undo &&
        git branch splitbr2 sub1 &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr2 &&
        check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
 '
 
 test_expect_success 'check split with --branch for an incompatible branch' '
-       test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+       test_must_fail git subtree split --prefix "sub dir" --onto FETCH_HEAD --branch subdir
 '
 
 test_expect_success 'check split+rejoin' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        undo &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
-       check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --rejoin &&
+       check_equal ''"$(last_commit_message)"'' "Split '"'"'sub dir/'"'"' into commit '"'"'"$spl1"'"'"'"
 '
 
 test_expect_success 'add main-sub8' '
-       create subdir/main-sub8 &&
+       create "sub dir/main-sub8" &&
        git commit -m "main-sub8"
 '
 
 # To the subproject!
-cd ./subproj
+cd ./"sub proj"
 
 test_expect_success 'merge split into subproj' '
        git fetch .. spl1 &&
@@ -271,22 +253,22 @@ test_expect_success 'add sub9' '
 cd ..
 
 test_expect_success 'split for sub8' '
-       split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' &&
+       split2=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir/" --rejoin)"'' &&
        git branch split2 "$split2"
 '
 
 test_expect_success 'add main-sub10' '
-       create subdir/main-sub10 &&
+       create "sub dir/main-sub10" &&
        git commit -m "main-sub10"
 '
 
 test_expect_success 'split for sub10' '
-       spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+       spl3=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --rejoin)"'' &&
        git branch spl3 "$spl3"
 '
 
 # To the subproject!
-cd ./subproj
+cd ./"sub proj"
 
 test_expect_success 'merge split into subproj' '
        git fetch .. spl3 &&
@@ -295,42 +277,64 @@ test_expect_success 'merge split into subproj' '
        git branch subproj-merge-spl3
 '
 
-chkm="main4 main6"
-chkms="main-sub10 main-sub5 main-sub7 main-sub8"
-chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
-chks="sub1 sub2 sub3 sub9"
-chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+chkm="main4
+main6"
+chkms="main-sub10
+main-sub5
+main-sub7
+main-sub8"
+chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chkms
+TXT
+)
+chks="sub1
+sub2
+sub3
+sub9"
+chks_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chks
+TXT
+)
 
 test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
-       subfiles=''"$(git ls-files | fixnl)"'' &&
-       check_equal "$subfiles" "$chkms $chks"
+       subfiles="$(git ls-files)" &&
+       check_equal "$subfiles" "$chkms
+$chks"
 '
-
 test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
-       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
-       check_equal "$allchanges" "$chkms $chks"
+       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+       check_equal "$allchanges" "$chkms
+$chks"
 '
 
 # Back to mainline
 cd ..
 
 test_expect_success 'pull from subproj' '
-       git fetch ./subproj subproj-merge-spl3 &&
+       git fetch ./"sub proj" subproj-merge-spl3 &&
        git branch subproj-merge-spl3 FETCH_HEAD &&
-       git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+       git subtree pull --prefix="sub dir" ./"sub proj" subproj-merge-spl3
 '
 
 test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
-       mainfiles=''"$(git ls-files | fixnl)"'' &&
-       check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+       mainfiles=$(git ls-files) &&
+       check_equal "$mainfiles" "$chkm
+$chkms_sub
+$chks_sub"
 '
 
 test_expect_success 'make sure each filename changed exactly once in the entire history' '
        # main-sub?? and /subdir/main-sub?? both change, because those are the
        # changes that were split into their own history.  And subdir/sub?? never
        # change, since they were *only* changed in the subtree branch.
-       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
-       check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+       check_equal "$allchanges" ''"$(cat <<TXT | sort
+$chkms
+$chkm
+$chks
+$chkms_sub
+TXT
+)"''
 '
 
 test_expect_success 'make sure the --rejoin commits never make it into subproj' '
@@ -377,7 +381,7 @@ cd ../main
 test_expect_success 'add sub as subdir in main' '
        git fetch ../sub master &&
        git branch sub2 FETCH_HEAD &&
-       git subtree add --prefix subdir sub2
+       git subtree add --prefix "sub dir" sub2
 '
 
 cd ../sub
@@ -392,16 +396,16 @@ cd ../main
 test_expect_success 'merge from sub' '
        git fetch ../sub master &&
        git branch sub3 FETCH_HEAD &&
-       git subtree merge --prefix subdir sub3
+       git subtree merge --prefix "sub dir" sub3
 '
 
 test_expect_success 'add main-sub4' '
-       create subdir/main-sub4 &&
+       create "sub dir/main-sub4" &&
        git commit -m "main-sub4"
 '
 
 test_expect_success 'split for main-sub4 without --onto' '
-       git subtree split --prefix subdir --branch mainsub4
+       git subtree split --prefix "sub dir" --branch mainsub4
 '
 
 # at this point, the new commit parent should be sub3 if it is not,
@@ -468,4 +472,50 @@ test_expect_success 'verify one file change per commit' '
        ))
 '
 
+# test push
+
+cd ../..
+
+mkdir test-push
+
+cd test-push
+
+test_expect_success 'init main' '
+       test_create_repo main
+'
+
+test_expect_success 'init sub' '
+       test_create_repo "sub project"
+'
+
+cd ./"sub project"
+
+test_expect_success 'add subproject' '
+       create "sub project" &&
+       git commit -m "Sub project: 1" &&
+       git branch sub-branch-1
+'
+
+cd ../main
+
+test_expect_success 'make first commit and add subproject' '
+       create "main-1" &&
+       git commit -m "main: 1" &&
+       git subtree add "../sub project" --prefix "sub dir" --message "Added subproject" sub-branch-1 &&
+       check_equal "$(last_commit_message)" "Added subproject"
+'
+
+test_expect_success 'make second commit to a subproject file and push it into a sub project' '
+       create "sub dir/sub1" &&
+       git commit -m "Sub project: 2" &&
+       git subtree push "../sub project" --prefix "sub dir" sub-branch-1
+'
+
+cd ../"sub project"
+
+test_expect_success 'Test second commit is pushed' '
+       git checkout sub-branch-1 &&
+       check_equal "$(last_commit_message)" "Sub project: 2"
+'
+
 test_done
diff --git a/date.c b/date.c
index 8f9156909b8d9d2cad6425aa44deebdcd01b2168..7c9f76998ac7a00fb5f8781dd8e460f899edbfac 100644 (file)
--- a/date.c
+++ b/date.c
@@ -166,6 +166,7 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
        if (type == DATE_STRFTIME)
                die("BUG: cannot create anonymous strftime date_mode struct");
        mode.type = type;
+       mode.local = 0;
        return &mode;
 }
 
@@ -174,6 +175,9 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
        struct tm *tm;
        static struct strbuf timebuf = STRBUF_INIT;
 
+       if (mode->local)
+               tz = local_tzoffset(time);
+
        if (mode->type == DATE_RAW) {
                strbuf_reset(&timebuf);
                strbuf_addf(&timebuf, "%lu %+05d", time, tz);
@@ -189,9 +193,6 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
                return timebuf.buf;
        }
 
-       if (mode->type == DATE_LOCAL)
-               tz = local_tzoffset(time);
-
        tm = time_to_tm(time, tz);
        if (!tm) {
                tm = time_to_tm(0, 0);
@@ -232,7 +233,7 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                tm->tm_year + 1900,
-                               (mode->type == DATE_LOCAL) ? 0 : ' ',
+                               mode->local ? 0 : ' ',
                                tz);
        return timebuf.buf;
 }
@@ -770,31 +771,50 @@ int parse_date(const char *date, struct strbuf *result)
        return 0;
 }
 
+static enum date_mode_type parse_date_type(const char *format, const char **end)
+{
+       if (skip_prefix(format, "relative", end))
+               return DATE_RELATIVE;
+       if (skip_prefix(format, "iso8601-strict", end) ||
+           skip_prefix(format, "iso-strict", end))
+               return DATE_ISO8601_STRICT;
+       if (skip_prefix(format, "iso8601", end) ||
+           skip_prefix(format, "iso", end))
+               return DATE_ISO8601;
+       if (skip_prefix(format, "rfc2822", end) ||
+           skip_prefix(format, "rfc", end))
+               return DATE_RFC2822;
+       if (skip_prefix(format, "short", end))
+               return DATE_SHORT;
+       if (skip_prefix(format, "default", end))
+               return DATE_NORMAL;
+       if (skip_prefix(format, "raw", end))
+               return DATE_RAW;
+       if (skip_prefix(format, "format", end))
+               return DATE_STRFTIME;
+
+       die("unknown date format %s", format);
+}
+
 void parse_date_format(const char *format, struct date_mode *mode)
 {
-       if (!strcmp(format, "relative"))
-               mode->type = DATE_RELATIVE;
-       else if (!strcmp(format, "iso8601") ||
-                !strcmp(format, "iso"))
-               mode->type = DATE_ISO8601;
-       else if (!strcmp(format, "iso8601-strict") ||
-                !strcmp(format, "iso-strict"))
-               mode->type = DATE_ISO8601_STRICT;
-       else if (!strcmp(format, "rfc2822") ||
-                !strcmp(format, "rfc"))
-               mode->type = DATE_RFC2822;
-       else if (!strcmp(format, "short"))
-               mode->type = DATE_SHORT;
-       else if (!strcmp(format, "local"))
-               mode->type = DATE_LOCAL;
-       else if (!strcmp(format, "default"))
-               mode->type = DATE_NORMAL;
-       else if (!strcmp(format, "raw"))
-               mode->type = DATE_RAW;
-       else if (skip_prefix(format, "format:", &format)) {
-               mode->type = DATE_STRFTIME;
-               mode->strftime_fmt = xstrdup(format);
-       } else
+       const char *p;
+
+       /* historical alias */
+       if (!strcmp(format, "local"))
+               format = "default-local";
+
+       mode->type = parse_date_type(format, &p);
+       mode->local = 0;
+
+       if (skip_prefix(p, "-local", &p))
+               mode->local = 1;
+
+       if (mode->type == DATE_STRFTIME) {
+               if (!skip_prefix(p, ":", &p))
+                       die("date format missing colon separator: %s", format);
+               mode->strftime_fmt = xstrdup(p);
+       } else if (*p)
                die("unknown date format %s", format);
 }
 
index 6c7c3c9b669eb272f4da530aef459c9c39c4d294..adcbfc67dca7aecd8f1680f3e30b05dcfa400b59 100644 (file)
@@ -424,7 +424,7 @@ static void write_crash_report(const char *err)
        fprintf(rpt, "fast-import crash report:\n");
        fprintf(rpt, "    fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
        fprintf(rpt, "    parent process     : %"PRIuMAX"\n", (uintmax_t) getppid());
-       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(LOCAL)));
+       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601)));
        fputc('\n', rpt);
 
        fputs("fatal: ", rpt);
index ea63223ab3b5d4f8fb0a9af45db4278aef010fb7..5d1cb00d86b3700b2012cae1ed3b3b8b744acaf2 100755 (executable)
@@ -1,14 +1,19 @@
 #!/bin/sh
 
-USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
+USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect help
        print this long help message.
-git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
+git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
        reset bisect state and start bisection.
-git bisect bad [<rev>]
-       mark <rev> a known-bad revision.
-git bisect good [<rev>...]
-       mark <rev>... known-good revisions.
+git bisect (bad|new) [<rev>]
+       mark <rev> a known-bad revision/
+               a revision after change in a given property.
+git bisect (good|old) [<rev>...]
+       mark <rev>... known-good revisions/
+               revisions before change in a given property.
+git bisect terms [--term-good | --term-bad]
+       show the terms used for old and new commits (default: bad, good)
 git bisect skip [(<rev>|<range>)...]
        mark <rev>... untestable revisions.
 git bisect next
@@ -95,6 +100,24 @@ bisect_start() {
                --no-checkout)
                        mode=--no-checkout
                        shift ;;
+               --term-good|--term-old)
+                       shift
+                       must_write_terms=1
+                       TERM_GOOD=$1
+                       shift ;;
+               --term-good=*|--term-old=*)
+                       must_write_terms=1
+                       TERM_GOOD=${1#*=}
+                       shift ;;
+               --term-bad|--term-new)
+                       shift
+                       must_write_terms=1
+                       TERM_BAD=$1
+                       shift ;;
+               --term-bad=*|--term-new=*)
+                       must_write_terms=1
+                       TERM_BAD=${1#*=}
+                       shift ;;
                --*)
                        die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
                *)
@@ -294,7 +317,7 @@ bisect_next_check() {
                false
                ;;
        t,,"$TERM_GOOD")
-               # have bad but not good.  we could bisect although
+               # have bad (or new) but not good (or old).  we could bisect although
                # this is less optimum.
                eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
                if test -t 0
@@ -451,6 +474,8 @@ bisect_replay () {
                        eval "$cmd" ;;
                "$TERM_GOOD"|"$TERM_BAD"|skip)
                        bisect_write "$command" "$rev" ;;
+               terms)
+                       bisect_terms $rev ;;
                *)
                        die "$(gettext "?? what are you talking about?")" ;;
                esac
@@ -535,9 +560,42 @@ get_terms () {
 write_terms () {
        TERM_BAD=$1
        TERM_GOOD=$2
+       if test "$TERM_BAD" = "$TERM_GOOD"
+       then
+               die "$(gettext "please use two different terms")"
+       fi
+       check_term_format "$TERM_BAD" bad
+       check_term_format "$TERM_GOOD" good
        printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
 }
 
+check_term_format () {
+       term=$1
+       git check-ref-format refs/bisect/"$term" ||
+       die "$(eval_gettext "'\$term' is not a valid term")"
+       case "$term" in
+       help|start|terms|skip|next|reset|visualize|replay|log|run)
+               die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
+               ;;
+       bad|new)
+               if test "$2" != bad
+               then
+                       # In theory, nothing prevents swapping
+                       # completely good and bad, but this situation
+                       # could be confusing and hasn't been tested
+                       # enough. Forbid it for now.
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       good|old)
+               if test "$2" != good
+               then
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       esac
+}
+
 check_and_set_terms () {
        cmd="$1"
        case "$cmd" in
@@ -554,14 +612,51 @@ check_and_set_terms () {
                                write_terms bad good
                        fi
                        ;;
+               new|old)
+                       if ! test -s "$GIT_DIR/BISECT_TERMS"
+                       then
+                               write_terms new old
+                       fi
+                       ;;
                esac ;;
        esac
 }
 
 bisect_voc () {
        case "$1" in
-       bad) echo "bad" ;;
-       good) echo "good" ;;
+       bad) echo "bad|new" ;;
+       good) echo "good|old" ;;
+       esac
+}
+
+bisect_terms () {
+       get_terms
+       if ! test -s "$GIT_DIR/BISECT_TERMS"
+       then
+               die "$(gettext "no terms defined")"
+       fi
+       case "$#" in
+       0)
+               gettextln "Your current terms are $TERM_GOOD for the old state
+and $TERM_BAD for the new state."
+               ;;
+       1)
+               arg=$1
+               case "$arg" in
+                       --term-good|--term-old)
+                               printf '%s\n' "$TERM_GOOD"
+                               ;;
+                       --term-bad|--term-new)
+                               printf '%s\n' "$TERM_BAD"
+                               ;;
+                       *)
+                               die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
+Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
+                               ;;
+               esac
+               ;;
+       *)
+               usage ;;
        esac
 }
 
@@ -577,7 +672,7 @@ case "$#" in
                git bisect -h ;;
        start)
                bisect_start "$@" ;;
-       bad|good|"$TERM_BAD"|"$TERM_GOOD")
+       bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
                bisect_state "$cmd" "$@" ;;
        skip)
                bisect_skip "$@" ;;
@@ -594,6 +689,8 @@ case "$#" in
                bisect_log ;;
        run)
                bisect_run "$@" ;;
+       terms)
+               bisect_terms "$@" ;;
        *)
                usage ;;
        esac
index f649e81f1107722f4c7d051201920ae2a0e7846a..1df82fa598daa131c9e84023083a014ac3ee3f9f 100644 (file)
@@ -814,6 +814,9 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
        char *p;
 
        errno = 0;
+       /* negative values would be accepted by strtoul */
+       if (strchr(s, '-'))
+               return -1;
        ul = strtoul(s, &p, base);
        if (errno || *p || p == s || (unsigned int) ul != ul)
                return -1;
index 0093fa3d8391b4b56c5613b063996cc6dc5c7b60..2677c89c064d7152451798d29d69b277ede3cbad 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -2329,8 +2329,11 @@ def make_email(self, userid):
         else:
             return "%s <a@b>" % userid
 
-    # Stream a p4 tag
     def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
+        """ Stream a p4 tag.
+        commit is either a git commit, or a fast-import mark, ":<p4commit>"
+        """
+
         if verbose:
             print "writing tag %s for commit %s" % (labelName, commit)
         gitStream.write("tag %s\n" % labelName)
@@ -2381,7 +2384,7 @@ def commit(self, details, files, branch, parent = ""):
             self.clientSpecDirs.update_client_spec_path_cache(files)
 
         self.gitStream.write("commit %s\n" % branch)
-#        gitStream.write("mark :%s\n" % details["change"])
+        self.gitStream.write("mark :%s\n" % details["change"])
         self.committedChanges.add(int(details["change"]))
         committer = ""
         if author not in self.users:
@@ -2500,13 +2503,19 @@ def importP4Labels(self, stream, p4Labels):
             if change.has_key('change'):
                 # find the corresponding git commit; take the oldest commit
                 changelist = int(change['change'])
-                gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
-                     "--reverse", ":/\[git-p4:.*change = %d\]" % changelist])
-                if len(gitCommit) == 0:
-                    print "could not find git commit for changelist %d" % changelist
-                else:
-                    gitCommit = gitCommit.strip()
+                if changelist in self.committedChanges:
+                    gitCommit = ":%d" % changelist       # use a fast-import mark
                     commitFound = True
+                else:
+                    gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
+                        "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
+                    if len(gitCommit) == 0:
+                        print "importing label %s: could not find git commit for changelist %d" % (name, changelist)
+                    else:
+                        commitFound = True
+                        gitCommit = gitCommit.strip()
+
+                if commitFound:
                     # Convert from p4 time format
                     try:
                         tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
index 167d79fea809b918e81c2228ee27baa5fab23db4..6d3a88decdeee3f85d9ee43ef8e716ccd1a6328b 100755 (executable)
@@ -6,7 +6,8 @@ git quiltimport [options]
 --
 n,dry-run     dry run
 author=       author name and email address for patches without any
-patches=      path to the quilt series and patches
+patches=      path to the quilt patches
+series=       path to the quilt series file
 "
 SUBDIRECTORY_ON=Yes
 . git-sh-setup
@@ -27,6 +28,10 @@ do
                shift
                QUILT_PATCHES="$1"
                ;;
+       --series)
+               shift
+               QUILT_SERIES="$1"
+               ;;
        --)
                shift
                break;;
@@ -53,6 +58,13 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
        exit 1
 fi
 
+# Quilt series file
+: ${QUILT_SERIES:=$QUILT_PATCHES/series}
+if ! [ -e "$QUILT_SERIES" ] ; then
+       echo "The \"$QUILT_SERIES\" file does not exist."
+       exit 1
+fi
+
 # Temporary directories
 tmp_dir="$GIT_DIR"/rebase-apply
 tmp_msg="$tmp_dir/msg"
@@ -135,5 +147,5 @@ do
                commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
                git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
-done 3<"$QUILT_PATCHES/series"
+done 3<"$QUILT_SERIES"
 rm -rf $tmp_dir || exit 5
index 1757404bc271ba4e1a08eb30fe27542c35e3008e..af7ba5fd90c3000892ed31893e1812514e4f3773 100755 (executable)
@@ -14,7 +14,7 @@ git-rebase --continue | --abort | --skip | --edit-todo
  Available options are
 v,verbose!         display a diffstat of what changed upstream
 q,quiet!           be quiet. implies --no-stat
-autostash!         automatically stash/stash pop before and after
+autostash          automatically stash/stash pop before and after
 fork-point         use 'merge-base --fork-point' to refine upstream
 onto=!             rebase onto given branch instead of upstream
 p,preserve-merges! try to recreate merges instead of ignoring them
@@ -292,6 +292,9 @@ do
        --autostash)
                autostash=true
                ;;
+       --no-autostash)
+               autostash=false
+               ;;
        --verbose)
                verbose=t
                diffstat=t
index 1d5ba7a4f935fd08572c235b7c5d4390eb8d6528..c7c65e25f50e7b558952a5180081ad499f2a90fc 100755 (executable)
@@ -305,7 +305,25 @@ show_stash () {
        ALLOW_UNKNOWN_FLAGS=t
        assert_stash_like "$@"
 
-       git diff ${FLAGS:---stat} $b_commit $w_commit
+       if test -z "$FLAGS"
+       then
+               if test "$(git config --bool stash.showStat || echo true)" = "true"
+               then
+                       FLAGS=--stat
+               fi
+
+               if test "$(git config --bool stash.showPatch || echo false)" = "true"
+               then
+                       FLAGS=${FLAGS}${FLAGS:+ }-p
+               fi
+
+               if test -z "$FLAGS"
+               then
+                       return 0
+               fi
+       fi
+
+       git diff ${FLAGS} $b_commit $w_commit
 }
 
 show_help () {
index 82e35582cd8e741280229f377b4f61d7d985bc6a..9bc5c5f94d1d7b24dffae649cca371299540aa46 100755 (executable)
@@ -154,48 +154,6 @@ relative_path ()
        echo "$result$target"
 }
 
-#
-# Get submodule info for registered submodules
-# $@ = path to limit submodule list
-#
-module_list()
-{
-       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
-       (
-               git ls-files -z --error-unmatch --stage -- "$@" ||
-               echo "unmatched pathspec exists"
-       ) |
-       @@PERL@@ -e '
-       my %unmerged = ();
-       my ($null_sha1) = ("0" x 40);
-       my @out = ();
-       my $unmatched = 0;
-       $/ = "\0";
-       while (<STDIN>) {
-               if (/^unmatched pathspec/) {
-                       $unmatched = 1;
-                       next;
-               }
-               chomp;
-               my ($mode, $sha1, $stage, $path) =
-                       /^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
-               next unless $mode eq "160000";
-               if ($stage ne "0") {
-                       if (!$unmerged{$path}++) {
-                               push @out, "$mode $null_sha1 U\t$path\n";
-                       }
-                       next;
-               }
-               push @out, "$_\n";
-       }
-       if ($unmatched) {
-               print "#unmatched\n";
-       } else {
-               print for (@out);
-       }
-       '
-}
-
 die_if_unmatched ()
 {
        if test "$1" = "#unmatched"
@@ -229,98 +187,6 @@ get_submodule_config () {
        printf '%s' "${value:-$default}"
 }
 
-
-#
-# Map submodule path to submodule name
-#
-# $1 = path
-#
-module_name()
-{
-       # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
-       sm_path="$1"
-       re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
-       name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
-               sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
-       test -z "$name" &&
-       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
-       printf '%s\n' "$name"
-}
-
-#
-# Clone a submodule
-#
-# $1 = submodule path
-# $2 = submodule name
-# $3 = URL to clone
-# $4 = reference repository to reuse (empty for independent)
-# $5 = depth argument for shallow clones (empty for deep)
-#
-# Prior to calling, cmd_update checks that a possibly existing
-# path is not a git repository.
-# Likewise, cmd_add checks that path does not exist at all,
-# since it is the location of a new submodule.
-#
-module_clone()
-{
-       sm_path=$1
-       name=$2
-       url=$3
-       reference="$4"
-       depth="$5"
-       quiet=
-       if test -n "$GIT_QUIET"
-       then
-               quiet=-q
-       fi
-
-       gitdir=
-       gitdir_base=
-       base_name=$(dirname "$name")
-
-       gitdir=$(git rev-parse --git-dir)
-       gitdir_base="$gitdir/modules/$base_name"
-       gitdir="$gitdir/modules/$name"
-
-       if test -d "$gitdir"
-       then
-               mkdir -p "$sm_path"
-               rm -f "$gitdir/index"
-       else
-               mkdir -p "$gitdir_base"
-               (
-                       clear_local_git_env
-                       git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \
-                               --separate-git-dir "$gitdir" "$url" "$sm_path"
-               ) ||
-               die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
-       fi
-
-       # We already are at the root of the work tree but cd_to_toplevel will
-       # resolve any symlinks that might be present in $PWD
-       a=$(cd_to_toplevel && cd "$gitdir" && pwd)/
-       b=$(cd_to_toplevel && cd "$sm_path" && pwd)/
-       # Remove all common leading directories after a sanity check
-       if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then
-               die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")"
-       fi
-       while test "${a%%/*}" = "${b%%/*}"
-       do
-               a=${a#*/}
-               b=${b#*/}
-       done
-       # Now chop off the trailing '/'s that were added in the beginning
-       a=${a%/}
-       b=${b%/}
-
-       # Turn each leading "*/" component into "../"
-       rel=$(printf '%s\n' "$b" | sed -e 's|[^/][^/]*|..|g')
-       printf '%s\n' "gitdir: $rel/$a" >"$sm_path/.git"
-
-       rel=$(printf '%s\n' "$a" | sed -e 's|[^/][^/]*|..|g')
-       (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
-}
-
 isnumber()
 {
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
@@ -481,7 +347,7 @@ Use -f if you really want to add it." >&2
                                echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
                        fi
                fi
-               module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit
+               git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit
                (
                        clear_local_git_env
                        cd "$sm_path" &&
@@ -541,7 +407,7 @@ cmd_foreach()
        # command in the subshell (and a recursive call to this function)
        exec 3<&0
 
-       module_list |
+       git submodule--helper list --prefix "$wt_prefix"|
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
@@ -549,7 +415,7 @@ cmd_foreach()
                then
                        displaypath=$(relative_path "$sm_path")
                        say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
-                       name=$(module_name "$sm_path")
+                       name=$(git submodule--helper name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
                                clear_local_git_env
@@ -601,11 +467,11 @@ cmd_init()
                shift
        done
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
 
                displaypath=$(relative_path "$sm_path")
 
@@ -683,11 +549,11 @@ cmd_deinit()
                die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
        fi
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
 
                displaypath=$(relative_path "$sm_path")
 
@@ -799,7 +665,7 @@ cmd_update()
        fi
 
        cloned_modules=
-       module_list "$@" | {
+       git submodule--helper list --prefix "$wt_prefix" "$@" | {
        err=
        while read mode sha1 stage sm_path
        do
@@ -809,7 +675,7 @@ cmd_update()
                        echo >&2 "Skipping unmerged submodule $prefix$sm_path"
                        continue
                fi
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                branch=$(get_submodule_config "$name" branch master)
                if ! test -z "$update"
@@ -843,7 +709,7 @@ Maybe you want to use 'update --init'?")"
 
                if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git
                then
-                       module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit
+                       git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit
                        cloned_modules="$cloned_modules;$name"
                        subsha1=
                else
@@ -1073,7 +939,7 @@ cmd_summary() {
                        # Respect the ignore setting for --for-status.
                        if test -n "$for_status"
                        then
-                               name=$(module_name "$sm_path")
+                               name=$(git submodule--helper name "$sm_path")
                                ignore_config=$(get_submodule_config "$name" ignore none)
                                test $status != A && test $ignore_config = all && continue
                        fi
@@ -1231,11 +1097,11 @@ cmd_status()
                shift
        done
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                displaypath=$(relative_path "$prefix$sm_path")
                if test "$stage" = U
@@ -1308,11 +1174,11 @@ cmd_sync()
                esac
        done
        cd_to_toplevel
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path")
+               name=$(git submodule--helper name "$sm_path")
                url=$(git config -f .gitmodules --get submodule."$name".url)
 
                # Possibly a url relative to parent
diff --git a/git.c b/git.c
index 5feba410cab6d95e3b9ff9745db56a9d045f0c20..6ed824cacfccddcb01104835b45ab934ff3f443e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -417,7 +417,7 @@ static struct cmd_struct commands[] = {
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
        { "init", cmd_init_db, NO_SETUP },
        { "init-db", cmd_init_db, NO_SETUP },
-       { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP },
+       { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
        { "log", cmd_log, RUN_SETUP },
        { "ls-files", cmd_ls_files, RUN_SETUP },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
@@ -470,6 +470,7 @@ static struct cmd_struct commands[] = {
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
+       { "submodule--helper", cmd_submodule__helper, RUN_SETUP },
        { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
        { "tag", cmd_tag, RUN_SETUP },
        { "unpack-file", cmd_unpack_file, RUN_SETUP },
index 5ab6ed6b0875ff703e3d55abbd0e9d8b0aa1a3c7..239898d946f06d102030569fd45780f523fdcd5a 100644 (file)
@@ -5,6 +5,7 @@
 #include "color.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "sha1-array.h"
 
 /*----- some often used options -----*/
 
@@ -77,7 +78,7 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
-int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+int parse_opt_commits(const struct option *opt, const char *arg, int unset)
 {
        unsigned char sha1[20];
        struct commit *commit;
@@ -93,6 +94,22 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+int parse_opt_object_name(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(opt->value);
+               return 0;
+       }
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(opt->value, sha1);
+       return 0;
+}
+
 int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 {
        int *target = opt->value;
index 3f1cc3aee0faaed923f736393db45877de14edc3..e8b55ea87aaea6a0e16239fd9b2025c4927d2145 100644 (file)
@@ -223,7 +223,8 @@ extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
 extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
 extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
-extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_object_name(const struct option *, const char *, int);
+extern int parse_opt_commits(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
 extern int parse_opt_noop_cb(const struct option *, const char *, int);
@@ -251,5 +252,12 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru }
 #define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv }
+#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \
+       { OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \
+         PARSE_OPT_LASTARG_DEFAULT | flag, \
+         parse_opt_commits, (intptr_t) "HEAD" \
+       }
+#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0)
+#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN)
 
 #endif
diff --git a/path.c b/path.c
index 95acbafa6883b4418f19a208cb9889f5642f3925..65beb2dca3335912b81b19564ae1db3ca51067be 100644 (file)
--- a/path.c
+++ b/path.c
@@ -91,54 +91,271 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
                buf->buf[newlen] = '/';
 }
 
-static const char *common_list[] = {
-       "/branches", "/hooks", "/info", "!/logs", "/lost-found",
-       "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
-       "config", "!gc.pid", "packed-refs", "shallow",
-       NULL
+struct common_dir {
+       /* Not considered garbage for report_linked_checkout_garbage */
+       unsigned ignore_garbage:1;
+       unsigned is_dir:1;
+       /* Not common even though its parent is */
+       unsigned exclude:1;
+       const char *dirname;
 };
 
-static void update_common_dir(struct strbuf *buf, int git_dir_len)
+static struct common_dir common_list[] = {
+       { 0, 1, 0, "branches" },
+       { 0, 1, 0, "hooks" },
+       { 0, 1, 0, "info" },
+       { 0, 0, 1, "info/sparse-checkout" },
+       { 1, 1, 0, "logs" },
+       { 1, 1, 1, "logs/HEAD" },
+       { 0, 1, 1, "logs/refs/bisect" },
+       { 0, 1, 0, "lost-found" },
+       { 0, 1, 0, "objects" },
+       { 0, 1, 0, "refs" },
+       { 0, 1, 1, "refs/bisect" },
+       { 0, 1, 0, "remotes" },
+       { 0, 1, 0, "worktrees" },
+       { 0, 1, 0, "rr-cache" },
+       { 0, 1, 0, "svn" },
+       { 0, 0, 0, "config" },
+       { 1, 0, 0, "gc.pid" },
+       { 0, 0, 0, "packed-refs" },
+       { 0, 0, 0, "shallow" },
+       { 0, 0, 0, NULL }
+};
+
+/*
+ * A compressed trie.  A trie node consists of zero or more characters that
+ * are common to all elements with this prefix, optionally followed by some
+ * children.  If value is not NULL, the trie node is a terminal node.
+ *
+ * For example, consider the following set of strings:
+ * abc
+ * def
+ * definite
+ * definition
+ *
+ * The trie would look look like:
+ * root: len = 0, children a and d non-NULL, value = NULL.
+ *    a: len = 2, contents = bc, value = (data for "abc")
+ *    d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
+ *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
+ *           e: len = 0, children all NULL, value = (data for "definite")
+ *           i: len = 2, contents = on, children all NULL,
+ *              value = (data for "definition")
+ */
+struct trie {
+       struct trie *children[256];
+       int len;
+       char *contents;
+       void *value;
+};
+
+static struct trie *make_trie_node(const char *key, void *value)
 {
-       char *base = buf->buf + git_dir_len;
-       const char **p;
-
-       if (is_dir_file(base, "logs", "HEAD") ||
-           is_dir_file(base, "info", "sparse-checkout"))
-               return; /* keep this in $GIT_DIR */
-       for (p = common_list; *p; p++) {
-               const char *path = *p;
-               int is_dir = 0;
-               if (*path == '!')
-                       path++;
-               if (*path == '/') {
-                       path++;
-                       is_dir = 1;
+       struct trie *new_node = xcalloc(1, sizeof(*new_node));
+       new_node->len = strlen(key);
+       if (new_node->len) {
+               new_node->contents = xmalloc(new_node->len);
+               memcpy(new_node->contents, key, new_node->len);
+       }
+       new_node->value = value;
+       return new_node;
+}
+
+/*
+ * Add a key/value pair to a trie.  The key is assumed to be \0-terminated.
+ * If there was an existing value for this key, return it.
+ */
+static void *add_to_trie(struct trie *root, const char *key, void *value)
+{
+       struct trie *child;
+       void *old;
+       int i;
+
+       if (!*key) {
+               /* we have reached the end of the key */
+               old = root->value;
+               root->value = value;
+               return old;
+       }
+
+       for (i = 0; i < root->len; i++) {
+               if (root->contents[i] == key[i])
+                       continue;
+
+               /*
+                * Split this node: child will contain this node's
+                * existing children.
+                */
+               child = malloc(sizeof(*child));
+               memcpy(child->children, root->children, sizeof(root->children));
+
+               child->len = root->len - i - 1;
+               if (child->len) {
+                       child->contents = xstrndup(root->contents + i + 1,
+                                                  child->len);
                }
-               if (is_dir && dir_prefix(base, path)) {
-                       replace_dir(buf, git_dir_len, get_git_common_dir());
-                       return;
+               child->value = root->value;
+               root->value = NULL;
+               root->len = i;
+
+               memset(root->children, 0, sizeof(root->children));
+               root->children[(unsigned char)root->contents[i]] = child;
+
+               /* This is the newly-added child. */
+               root->children[(unsigned char)key[i]] =
+                       make_trie_node(key + i + 1, value);
+               return NULL;
+       }
+
+       /* We have matched the entire compressed section */
+       if (key[i]) {
+               child = root->children[(unsigned char)key[root->len]];
+               if (child) {
+                       return add_to_trie(child, key + root->len + 1, value);
+               } else {
+                       child = make_trie_node(key + root->len + 1, value);
+                       root->children[(unsigned char)key[root->len]] = child;
+                       return NULL;
                }
-               if (!is_dir && !strcmp(base, path)) {
-                       replace_dir(buf, git_dir_len, get_git_common_dir());
-                       return;
+       }
+
+       old = root->value;
+       root->value = value;
+       return old;
+}
+
+typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
+
+/*
+ * Search a trie for some key.  Find the longest /-or-\0-terminated
+ * prefix of the key for which the trie contains a value.  Call fn
+ * with the unmatched portion of the key and the found value, and
+ * return its return value.  If there is no such prefix, return -1.
+ *
+ * The key is partially normalized: consecutive slashes are skipped.
+ *
+ * For example, consider the trie containing only [refs,
+ * refs/worktree] (both with values).
+ *
+ * | key             | unmatched  | val from node | return value |
+ * |-----------------|------------|---------------|--------------|
+ * | a               | not called | n/a           | -1           |
+ * | refs            | \0         | refs          | as per fn    |
+ * | refs/           | /          | refs          | as per fn    |
+ * | refs/w          | /w         | refs          | as per fn    |
+ * | refs/worktree   | \0         | refs/worktree | as per fn    |
+ * | refs/worktree/  | /          | refs/worktree | as per fn    |
+ * | refs/worktree/a | /a         | refs/worktree | as per fn    |
+ * |-----------------|------------|---------------|--------------|
+ *
+ */
+static int trie_find(struct trie *root, const char *key, match_fn fn,
+                    void *baton)
+{
+       int i;
+       int result;
+       struct trie *child;
+
+       if (!*key) {
+               /* we have reached the end of the key */
+               if (root->value && !root->len)
+                       return fn(key, root->value, baton);
+               else
+                       return -1;
+       }
+
+       for (i = 0; i < root->len; i++) {
+               /* Partial path normalization: skip consecutive slashes. */
+               if (key[i] == '/' && key[i+1] == '/') {
+                       key++;
+                       continue;
                }
+               if (root->contents[i] != key[i])
+                       return -1;
        }
+
+       /* Matched the entire compressed section */
+       key += i;
+       if (!*key)
+               /* End of key */
+               return fn(key, root->value, baton);
+
+       /* Partial path normalization: skip consecutive slashes */
+       while (key[0] == '/' && key[1] == '/')
+               key++;
+
+       child = root->children[(unsigned char)*key];
+       if (child)
+               result = trie_find(child, key + 1, fn, baton);
+       else
+               result = -1;
+
+       if (result >= 0 || (*key != '/' && *key != 0))
+               return result;
+       if (root->value)
+               return fn(key, root->value, baton);
+       else
+               return -1;
+}
+
+static struct trie common_trie;
+static int common_trie_done_setup;
+
+static void init_common_trie(void)
+{
+       struct common_dir *p;
+
+       if (common_trie_done_setup)
+               return;
+
+       for (p = common_list; p->dirname; p++)
+               add_to_trie(&common_trie, p->dirname, p);
+
+       common_trie_done_setup = 1;
+}
+
+/*
+ * Helper function for update_common_dir: returns 1 if the dir
+ * prefix is common.
+ */
+static int check_common(const char *unmatched, void *value, void *baton)
+{
+       struct common_dir *dir = value;
+
+       if (!dir)
+               return 0;
+
+       if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
+               return !dir->exclude;
+
+       if (!dir->is_dir && unmatched[0] == 0)
+               return !dir->exclude;
+
+       return 0;
+}
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+       char *base = buf->buf + git_dir_len;
+       init_common_trie();
+       if (trie_find(&common_trie, base, check_common, NULL) > 0)
+               replace_dir(buf, git_dir_len, get_git_common_dir());
 }
 
 void report_linked_checkout_garbage(void)
 {
        struct strbuf sb = STRBUF_INIT;
-       const char **p;
+       const struct common_dir *p;
        int len;
 
        if (!git_common_dir_env)
                return;
        strbuf_addf(&sb, "%s/", get_git_dir());
        len = sb.len;
-       for (p = common_list; *p; p++) {
-               const char *path = *p;
-               if (*path == '!')
+       for (p = common_list; p->dirname; p++) {
+               const char *path = p->dirname;
+               if (p->ignore_garbage)
                        continue;
                strbuf_setlen(&sb, len);
                strbuf_addstr(&sb, path);
index 08a1427c0d4f6743182b235ec9d9c710c3c99b5b..62fdb37079fa31698846044448b5cfb398e45528 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pkt-line.h"
+#include "run-command.h"
 
 char packet_buffer[LARGE_PACKET_MAX];
 static const char *packet_trace_prefix = "git";
@@ -11,6 +12,11 @@ void packet_trace_identity(const char *prog)
        packet_trace_prefix = xstrdup(prog);
 }
 
+static const char *get_trace_prefix(void)
+{
+       return in_async() ? "sideband" : packet_trace_prefix;
+}
+
 static int packet_trace_pack(const char *buf, unsigned int len, int sideband)
 {
        if (!sideband) {
@@ -57,7 +63,7 @@ static void packet_trace(const char *buf, unsigned int len, int write)
        strbuf_init(&out, len+32);
 
        strbuf_addf(&out, "packet: %12s%c ",
-                   packet_trace_prefix, write ? '>' : '<');
+                   get_trace_prefix(), write ? '>' : '<');
 
        /* XXX we should really handle printable utf8 */
        for (i = 0; i < len; i++) {
index f38dee4f605df344315e0202487aeebc623f6582..fd839ac4b337d3498201a7fdf3b80d0409258c6c 100644 (file)
@@ -9,6 +9,10 @@
 #include "tag.h"
 #include "quote.h"
 #include "ref-filter.h"
+#include "revision.h"
+#include "utf8.h"
+#include "git-compat-util.h"
+#include "version.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -43,15 +47,48 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
-       { "contents:subject" },
-       { "contents:body" },
-       { "contents:signature" },
        { "upstream" },
        { "push" },
        { "symref" },
        { "flag" },
        { "HEAD" },
        { "color" },
+       { "align" },
+       { "end" },
+};
+
+#define REF_FORMATTING_STATE_INIT  { 0, NULL }
+
+struct align {
+       align_type position;
+       unsigned int width;
+};
+
+struct contents {
+       unsigned int lines;
+       struct object_id oid;
+};
+
+struct ref_formatting_stack {
+       struct ref_formatting_stack *prev;
+       struct strbuf output;
+       void (*at_end)(struct ref_formatting_stack *stack);
+       void *at_end_data;
+};
+
+struct ref_formatting_state {
+       int quote_style;
+       struct ref_formatting_stack *stack;
+};
+
+struct atom_value {
+       const char *s;
+       union {
+               struct align align;
+               struct contents contents;
+       } u;
+       void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
+       unsigned long ul; /* used for sorting when not FIELD_STR */
 };
 
 /*
@@ -123,6 +160,120 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
        return at;
 }
 
+static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
+{
+       switch (quote_style) {
+       case QUOTE_NONE:
+               strbuf_addstr(s, str);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_buf(s, str);
+               break;
+       case QUOTE_PERL:
+               perl_quote_buf(s, str);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_buf(s, str);
+               break;
+       case QUOTE_TCL:
+               tcl_quote_buf(s, str);
+               break;
+       }
+}
+
+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+{
+       /*
+        * Quote formatting is only done when the stack has a single
+        * element. Otherwise quote formatting is done on the
+        * element's entire output strbuf when the %(end) atom is
+        * encountered.
+        */
+       if (!state->stack->prev)
+               quote_formatting(&state->stack->output, v->s, state->quote_style);
+       else
+               strbuf_addstr(&state->stack->output, v->s);
+}
+
+static void push_stack_element(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));
+
+       strbuf_init(&s->output, 0);
+       s->prev = *stack;
+       *stack = s;
+}
+
+static void pop_stack_element(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *current = *stack;
+       struct ref_formatting_stack *prev = current->prev;
+
+       if (prev)
+               strbuf_addbuf(&prev->output, &current->output);
+       strbuf_release(&current->output);
+       free(current);
+       *stack = prev;
+}
+
+static void end_align_handler(struct ref_formatting_stack *stack)
+{
+       struct align *align = (struct align *)stack->at_end_data;
+       struct strbuf s = STRBUF_INIT;
+
+       strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
+       strbuf_swap(&stack->output, &s);
+       strbuf_release(&s);
+}
+
+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *new;
+
+       push_stack_element(&state->stack);
+       new = state->stack;
+       new->at_end = end_align_handler;
+       new->at_end_data = &atomv->u.align;
+}
+
+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *current = state->stack;
+       struct strbuf s = STRBUF_INIT;
+
+       if (!current->at_end)
+               die(_("format: %%(end) atom used without corresponding atom"));
+       current->at_end(current);
+
+       /*
+        * Perform quote formatting when the stack element is that of
+        * a supporting atom. If nested then perform quote formatting
+        * only on the topmost supporting atom.
+        */
+       if (!state->stack->prev->prev) {
+               quote_formatting(&s, current->output.buf, state->quote_style);
+               strbuf_swap(&current->output, &s);
+       }
+       strbuf_release(&s);
+       pop_stack_element(&state->stack);
+}
+
+static int match_atom_name(const char *name, const char *atom_name, const char **val)
+{
+       const char *body;
+
+       if (!skip_prefix(name, atom_name, &body))
+               return 0; /* doesn't even begin with "atom_name" */
+       if (!body[0]) {
+               *val = NULL; /* %(atom_name) and no customization */
+               return 1;
+       }
+       if (body[0] != ':')
+               return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */
+       *val = body + 1; /* "atom_name:val" */
+       return 1;
+}
+
 /*
  * In a format string, find the next occurrence of %(atom).
  */
@@ -497,6 +648,30 @@ static void find_subpos(const char *buf, unsigned long sz,
        *nonsiglen = *sig - buf;
 }
 
+/*
+ * If 'lines' is greater than 0, append that many lines from the given
+ * 'buf' of length 'size' to the given strbuf.
+ */
+static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)
+{
+       int i;
+       const char *sp, *eol;
+       size_t len;
+
+       sp = buf;
+
+       for (i = 0; i < lines && sp < buf + size; i++) {
+               if (i)
+                       strbuf_addstr(out, "\n    ");
+               eol = memchr(sp, '\n', size - (sp - buf));
+               len = eol ? eol - sp : size - (sp - buf);
+               strbuf_add(out, sp, len);
+               if (!eol)
+                       break;
+               sp = eol + 1;
+       }
+}
+
 /* See grab_values */
 static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 {
@@ -507,6 +682,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
                struct atom_value *v = &val[i];
+               const char *valp = NULL;
                if (!!deref != (*name == '*'))
                        continue;
                if (deref)
@@ -516,7 +692,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                    strcmp(name, "contents") &&
                    strcmp(name, "contents:subject") &&
                    strcmp(name, "contents:body") &&
-                   strcmp(name, "contents:signature"))
+                   strcmp(name, "contents:signature") &&
+                   !starts_with(name, "contents:lines="))
                        continue;
                if (!subpos)
                        find_subpos(buf, sz,
@@ -536,6 +713,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
+               else if (skip_prefix(name, "contents:lines=", &valp)) {
+                       struct strbuf s = STRBUF_INIT;
+                       const char *contents_end = bodylen + bodypos - siglen;
+
+                       if (strtoul_ui(valp, 10, &v->u.contents.lines))
+                               die(_("positive value expected contents:lines=%s"), valp);
+                       /*  Size is the length of the message after removing the signature */
+                       append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);
+                       v->s = strbuf_detach(&s, NULL);
+               }
        }
 }
 
@@ -621,8 +808,11 @@ static void populate_value(struct ref_array_item *ref)
                int deref = 0;
                const char *refname;
                const char *formatp;
+               const char *valp;
                struct branch *branch = NULL;
 
+               v->handler = append_atom;
+
                if (*name == '*') {
                        deref = 1;
                        name++;
@@ -653,10 +843,12 @@ static void populate_value(struct ref_array_item *ref)
                        refname = branch_get_push(branch, NULL);
                        if (!refname)
                                continue;
-               } else if (starts_with(name, "color:")) {
+               } else if (match_atom_name(name, "color", &valp)) {
                        char color[COLOR_MAXLEN] = "";
 
-                       if (color_parse(name + 6, color) < 0)
+                       if (!valp)
+                               die(_("expected format: %%(color:<color>)"));
+                       if (color_parse(valp, color) < 0)
                                die(_("unable to parse format"));
                        v->s = xstrdup(color);
                        continue;
@@ -686,6 +878,48 @@ static void populate_value(struct ref_array_item *ref)
                        else
                                v->s = " ";
                        continue;
+               } else if (match_atom_name(name, "align", &valp)) {
+                       struct align *align = &v->u.align;
+                       struct strbuf **s, **to_free;
+                       int width = -1;
+
+                       if (!valp)
+                               die(_("expected format: %%(align:<width>,<position>)"));
+
+                       /*
+                        * TODO: Implement a function similar to strbuf_split_str()
+                        * which would omit the separator from the end of each value.
+                        */
+                       s = to_free = strbuf_split_str(valp, ',', 0);
+
+                       align->position = ALIGN_LEFT;
+
+                       while (*s) {
+                               /*  Strip trailing comma */
+                               if (s[1])
+                                       strbuf_setlen(s[0], s[0]->len - 1);
+                               if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))
+                                       ;
+                               else if (!strcmp(s[0]->buf, "left"))
+                                       align->position = ALIGN_LEFT;
+                               else if (!strcmp(s[0]->buf, "right"))
+                                       align->position = ALIGN_RIGHT;
+                               else if (!strcmp(s[0]->buf, "middle"))
+                                       align->position = ALIGN_MIDDLE;
+                               else
+                                       die(_("improper format entered align:%s"), s[0]->buf);
+                               s++;
+                       }
+
+                       if (width < 0)
+                               die(_("positive width expected with the %%(align) atom"));
+                       align->width = width;
+                       strbuf_list_free(to_free);
+                       v->handler = align_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "end")) {
+                       v->handler = end_atom_handler;
+                       continue;
                } else
                        continue;
 
@@ -817,11 +1051,143 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
        *v = &ref->value[atom];
 }
 
+enum contains_result {
+       CONTAINS_UNKNOWN = -1,
+       CONTAINS_NO = 0,
+       CONTAINS_YES = 1
+};
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct contains_stack {
+       int nr, alloc;
+       struct contains_stack_entry {
+               struct commit *commit;
+               struct commit_list *parents;
+       } *contains_stack;
+};
+
+static int in_commit_list(const struct commit_list *want, struct commit *c)
+{
+       for (; want; want = want->next)
+               if (!hashcmp(want->item->object.sha1, c->object.sha1))
+                       return 1;
+       return 0;
+}
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
+                           const struct commit_list *want)
+{
+       /* was it previously marked as containing a want commit? */
+       if (candidate->object.flags & TMP_MARK)
+               return 1;
+       /* or marked as not possibly containing a want commit? */
+       if (candidate->object.flags & UNINTERESTING)
+               return 0;
+       /* or are we it? */
+       if (in_commit_list(want, candidate)) {
+               candidate->object.flags |= TMP_MARK;
+               return 1;
+       }
+
+       if (parse_commit(candidate) < 0)
+               return 0;
+
+       return -1;
+}
+
+static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
+{
+       ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
+       contains_stack->contains_stack[contains_stack->nr].commit = candidate;
+       contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
+}
+
+static enum contains_result contains_tag_algo(struct commit *candidate,
+               const struct commit_list *want)
+{
+       struct contains_stack contains_stack = { 0, 0, NULL };
+       int result = contains_test(candidate, want);
+
+       if (result != CONTAINS_UNKNOWN)
+               return result;
+
+       push_to_contains_stack(candidate, &contains_stack);
+       while (contains_stack.nr) {
+               struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
+               struct commit *commit = entry->commit;
+               struct commit_list *parents = entry->parents;
+
+               if (!parents) {
+                       commit->object.flags |= UNINTERESTING;
+                       contains_stack.nr--;
+               }
+               /*
+                * If we just popped the stack, parents->item has been marked,
+                * therefore contains_test will return a meaningful 0 or 1.
+                */
+               else switch (contains_test(parents->item, want)) {
+               case CONTAINS_YES:
+                       commit->object.flags |= TMP_MARK;
+                       contains_stack.nr--;
+                       break;
+               case CONTAINS_NO:
+                       entry->parents = parents->next;
+                       break;
+               case CONTAINS_UNKNOWN:
+                       push_to_contains_stack(parents->item, &contains_stack);
+                       break;
+               }
+       }
+       free(contains_stack.contains_stack);
+       return contains_test(candidate, want);
+}
+
+static int commit_contains(struct ref_filter *filter, struct commit *commit)
+{
+       if (filter->with_commit_tag_algo)
+               return contains_tag_algo(commit, filter->with_commit);
+       return is_descendant_of(commit, filter->with_commit);
+}
+
+/*
+ * Return 1 if the refname matches one of the patterns, otherwise 0.
+ * A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
+ * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
+ * matches "refs/heads/mas*", too).
+ */
+static int match_pattern(const char **patterns, const char *refname)
+{
+       /*
+        * When no '--format' option is given we need to skip the prefix
+        * for matching refs of tags and branches.
+        */
+       (void)(skip_prefix(refname, "refs/tags/", &refname) ||
+              skip_prefix(refname, "refs/heads/", &refname) ||
+              skip_prefix(refname, "refs/remotes/", &refname) ||
+              skip_prefix(refname, "refs/", &refname));
+
+       for (; *patterns; patterns++) {
+               if (!wildmatch(*patterns, refname, 0, NULL))
+                       return 1;
+       }
+       return 0;
+}
+
 /*
  * Return 1 if the refname matches one of the patterns, otherwise 0.
  * A pattern can be path prefix (e.g. a refname "refs/heads/master"
- * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref
- * matches "refs/heads/m*",too).
+ * matches a pattern "refs/heads/" but not "refs/heads/m") or a
+ * wildcard (e.g. the same ref matches "refs/heads/m*", too).
  */
 static int match_name_as_path(const char **pattern, const char *refname)
 {
@@ -842,6 +1208,48 @@ static int match_name_as_path(const char **pattern, const char *refname)
        return 0;
 }
 
+/* Return 1 if the refname matches one of the patterns, otherwise 0. */
+static int filter_pattern_match(struct ref_filter *filter, const char *refname)
+{
+       if (!*filter->name_patterns)
+               return 1; /* No pattern always matches */
+       if (filter->match_as_path)
+               return match_name_as_path(filter->name_patterns, refname);
+       return match_pattern(filter->name_patterns, refname);
+}
+
+/*
+ * Given a ref (sha1, refname), check if the ref belongs to the array
+ * of sha1s. If the given ref is a tag, check if the given tag points
+ * at one of the sha1s in the given sha1 array.
+ * the given sha1_array.
+ * NEEDSWORK:
+ * 1. Only a single level of inderection is obtained, we might want to
+ * change this to account for multiple levels (e.g. annotated tags
+ * pointing to annotated tags pointing to a commit.)
+ * 2. As the refs are cached we might know what refname peels to without
+ * the need to parse the object via parse_object(). peel_ref() might be a
+ * more efficient alternative to obtain the pointee.
+ */
+static const unsigned char *match_points_at(struct sha1_array *points_at,
+                                           const unsigned char *sha1,
+                                           const char *refname)
+{
+       const unsigned char *tagged_sha1 = NULL;
+       struct object *obj;
+
+       if (sha1_array_lookup(points_at, sha1) >= 0)
+               return sha1;
+       obj = parse_object(sha1);
+       if (!obj)
+               die(_("malformed object at '%s'"), refname);
+       if (obj->type == OBJ_TAG)
+               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+       if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
+               return tagged_sha1;
+       return NULL;
+}
+
 /* Allocate space for a new ref_array_item and copy the objectname and flag to it */
 static struct ref_array_item *new_ref_array_item(const char *refname,
                                                 const unsigned char *objectname,
@@ -857,6 +1265,34 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
        return ref;
 }
 
+static int filter_ref_kind(struct ref_filter *filter, const char *refname)
+{
+       unsigned int i;
+
+       static struct {
+               const char *prefix;
+               unsigned int kind;
+       } ref_kind[] = {
+               { "refs/heads/" , FILTER_REFS_BRANCHES },
+               { "refs/remotes/" , FILTER_REFS_REMOTES },
+               { "refs/tags/", FILTER_REFS_TAGS}
+       };
+
+       if (filter->kind == FILTER_REFS_BRANCHES ||
+           filter->kind == FILTER_REFS_REMOTES ||
+           filter->kind == FILTER_REFS_TAGS)
+               return filter->kind;
+       else if (!strcmp(refname, "HEAD"))
+               return FILTER_REFS_DETACHED_HEAD;
+
+       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+               if (starts_with(refname, ref_kind[i].prefix))
+                       return ref_kind[i].kind;
+       }
+
+       return FILTER_REFS_OTHERS;
+}
+
 /*
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
@@ -866,6 +1302,8 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
        struct ref_filter_cbdata *ref_cbdata = cb_data;
        struct ref_filter *filter = ref_cbdata->filter;
        struct ref_array_item *ref;
+       struct commit *commit = NULL;
+       unsigned int kind;
 
        if (flag & REF_BAD_NAME) {
                warning("ignoring ref with broken name %s", refname);
@@ -877,18 +1315,43 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
                return 0;
        }
 
-       if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
+       /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
+       kind = filter_ref_kind(filter, refname);
+       if (!(kind & filter->kind))
+               return 0;
+
+       if (!filter_pattern_match(filter, refname))
                return 0;
 
+       if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
+               return 0;
+
+       /*
+        * A merge filter is applied on refs pointing to commits. Hence
+        * obtain the commit using the 'oid' available and discard all
+        * non-commits early. The actual filtering is done later.
+        */
+       if (filter->merge_commit || filter->with_commit) {
+               commit = lookup_commit_reference_gently(oid->hash, 1);
+               if (!commit)
+                       return 0;
+               /* We perform the filtering for the '--contains' option */
+               if (filter->with_commit &&
+                   !commit_contains(filter, commit))
+                       return 0;
+       }
+
        /*
         * We do not open the object yet; sort may only need refname
         * to do its job and the resulting list may yet to be pruned
         * by maxcount logic.
         */
        ref = new_ref_array_item(refname, oid->hash, flag);
+       ref->commit = commit;
 
        REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
        ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
+       ref->kind = kind;
        return 0;
 }
 
@@ -911,6 +1374,50 @@ void ref_array_clear(struct ref_array *array)
        array->nr = array->alloc = 0;
 }
 
+static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
+{
+       struct rev_info revs;
+       int i, old_nr;
+       struct ref_filter *filter = ref_cbdata->filter;
+       struct ref_array *array = ref_cbdata->array;
+       struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
+
+       init_revisions(&revs, NULL);
+
+       for (i = 0; i < array->nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               add_pending_object(&revs, &item->commit->object, item->refname);
+               to_clear[i] = item->commit;
+       }
+
+       filter->merge_commit->object.flags |= UNINTERESTING;
+       add_pending_object(&revs, &filter->merge_commit->object, "");
+
+       revs.limited = 1;
+       if (prepare_revision_walk(&revs))
+               die(_("revision walk setup failed"));
+
+       old_nr = array->nr;
+       array->nr = 0;
+
+       for (i = 0; i < old_nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               struct commit *commit = item->commit;
+
+               int is_merged = !!(commit->object.flags & UNINTERESTING);
+
+               if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
+                       array->items[array->nr++] = array->items[i];
+               else
+                       free_array_item(item);
+       }
+
+       for (i = 0; i < old_nr; i++)
+               clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
+       clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
+       free(to_clear);
+}
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -920,17 +1427,44 @@ void ref_array_clear(struct ref_array *array)
 int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
 {
        struct ref_filter_cbdata ref_cbdata;
+       int ret = 0;
+       unsigned int broken = 0;
 
        ref_cbdata.array = array;
        ref_cbdata.filter = filter;
 
-       if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
-               return for_each_rawref(ref_filter_handler, &ref_cbdata);
-       else if (type & FILTER_REFS_ALL)
-               return for_each_ref(ref_filter_handler, &ref_cbdata);
-       else
+       if (type & FILTER_REFS_INCLUDE_BROKEN)
+               broken = 1;
+       filter->kind = type & FILTER_REFS_KIND_MASK;
+
+       /*  Simple per-ref filtering */
+       if (!filter->kind)
                die("filter_refs: invalid type");
-       return 0;
+       else {
+               /*
+                * For common cases where we need only branches or remotes or tags,
+                * we only iterate through those refs. If a mix of refs is needed,
+                * we iterate over all refs and filter out required refs with the help
+                * of filter_ref_kind().
+                */
+               if (filter->kind == FILTER_REFS_BRANCHES)
+                       ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind == FILTER_REFS_REMOTES)
+                       ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind == FILTER_REFS_TAGS)
+                       ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind & FILTER_REFS_ALL)
+                       ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken);
+               if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
+                       head_ref(ref_filter_handler, &ref_cbdata);
+       }
+
+
+       /*  Filters that need revision walking */
+       if (filter->merge_commit)
+               do_merge_filter(&ref_cbdata);
+
+       return ret;
 }
 
 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
@@ -941,19 +1475,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
 
        get_ref_atom_value(a, s->atom, &va);
        get_ref_atom_value(b, s->atom, &vb);
-       switch (cmp_type) {
-       case FIELD_STR:
+       if (s->version)
+               cmp = versioncmp(va->s, vb->s);
+       else if (cmp_type == FIELD_STR)
                cmp = strcmp(va->s, vb->s);
-               break;
-       default:
+       else {
                if (va->ul < vb->ul)
                        cmp = -1;
                else if (va->ul == vb->ul)
                        cmp = 0;
                else
                        cmp = 1;
-               break;
        }
+
        return (s->reverse) ? -cmp : cmp;
 }
 
@@ -978,32 +1512,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 void print_value(struct atom_value *v, int quote_style)
-{
-       struct strbuf sb = STRBUF_INIT;
-       switch (quote_style) {
-       case QUOTE_NONE:
-               fputs(v->s, stdout);
-               break;
-       case QUOTE_SHELL:
-               sq_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PERL:
-               perl_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PYTHON:
-               python_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_TCL:
-               tcl_quote_buf(&sb, v->s);
-               break;
-       }
-       if (quote_style != QUOTE_NONE) {
-               fputs(sb.buf, stdout);
-               strbuf_release(&sb);
-       }
-}
-
 static int hex1(char ch)
 {
        if ('0' <= ch && ch <= '9')
@@ -1022,8 +1530,10 @@ static int hex2(const char *cp)
                return -1;
 }
 
-static void emit(const char *cp, const char *ep)
+static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
 {
+       struct strbuf *s = &state->stack->output;
+
        while (*cp && (!ep || cp < ep)) {
                if (*cp == '%') {
                        if (cp[1] == '%')
@@ -1031,13 +1541,13 @@ static void emit(const char *cp, const char *ep)
                        else {
                                int ch = hex2(cp + 1);
                                if (0 <= ch) {
-                                       putchar(ch);
+                                       strbuf_addch(s, ch);
                                        cp += 3;
                                        continue;
                                }
                        }
                }
-               putchar(*cp);
+               strbuf_addch(s, *cp);
                cp++;
        }
 }
@@ -1045,19 +1555,24 @@ static void emit(const char *cp, const char *ep)
 void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
 {
        const char *cp, *sp, *ep;
+       struct strbuf *final_buf;
+       struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
+
+       state.quote_style = quote_style;
+       push_stack_element(&state.stack);
 
        for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
                struct atom_value *atomv;
 
                ep = strchr(sp, ')');
                if (cp < sp)
-                       emit(cp, sp);
+                       append_literal(cp, sp, &state);
                get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
-               print_value(atomv, quote_style);
+               atomv->handler(atomv, &state);
        }
        if (*cp) {
                sp = cp + strlen(cp);
-               emit(cp, sp);
+               append_literal(cp, sp, &state);
        }
        if (need_color_reset_at_eol) {
                struct atom_value resetv;
@@ -1066,8 +1581,13 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
                if (color_parse("reset", color) < 0)
                        die("BUG: couldn't parse 'reset' as a color");
                resetv.s = color;
-               print_value(&resetv, quote_style);
+               append_atom(&resetv, &state);
        }
+       if (state.stack->prev)
+               die(_("format: %%(end) atom missing"));
+       final_buf = &state.stack->output;
+       fwrite(final_buf->buf, 1, final_buf->len, stdout);
+       pop_stack_element(&state.stack);
        putchar('\n');
 }
 
@@ -1100,7 +1620,29 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
                s->reverse = 1;
                arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
        len = strlen(arg);
        s->atom = parse_ref_filter_atom(arg, arg+len);
        return 0;
 }
+
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_filter *rf = opt->value;
+       unsigned char sha1[20];
+
+       rf->merge = starts_with(opt->long_name, "no")
+               ? REF_FILTER_MERGED_OMIT
+               : REF_FILTER_MERGED_INCLUDE;
+
+       if (get_sha1(arg, sha1))
+               die(_("malformed object name %s"), arg);
+
+       rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
+       if (!rf->merge_commit)
+               return opterror(opt, "must point to a commit", 0);
+
+       return 0;
+}
index 699798400b329a12bc5dfa09590853a18616f876..a5cfa5e677dbdf17156c6287f57fa5694b9d5120 100644 (file)
 #define QUOTE_PYTHON 4
 #define QUOTE_TCL 8
 
-#define FILTER_REFS_INCLUDE_BROKEN 0x1
-#define FILTER_REFS_ALL 0x2
+#define FILTER_REFS_INCLUDE_BROKEN 0x0001
+#define FILTER_REFS_TAGS           0x0002
+#define FILTER_REFS_BRANCHES       0x0004
+#define FILTER_REFS_REMOTES        0x0008
+#define FILTER_REFS_OTHERS         0x0010
+#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
+                                   FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
+#define FILTER_REFS_DETACHED_HEAD  0x0020
+#define FILTER_REFS_KIND_MASK      (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)
 
-struct atom_value {
-       const char *s;
-       unsigned long ul; /* used for sorting when not FIELD_STR */
-};
+struct atom_value;
 
 struct ref_sorting {
        struct ref_sorting *next;
        int atom; /* index into used_atom array (internal) */
-       unsigned reverse : 1;
+       unsigned reverse : 1,
+               version : 1;
 };
 
 struct ref_array_item {
        unsigned char objectname[20];
        int flag;
+       unsigned int kind;
        const char *symref;
+       struct commit *commit;
        struct atom_value *value;
        char refname[FLEX_ARRAY];
 };
@@ -42,6 +49,20 @@ struct ref_array {
 
 struct ref_filter {
        const char **name_patterns;
+       struct sha1_array points_at;
+       struct commit_list *with_commit;
+
+       enum {
+               REF_FILTER_MERGED_NONE = 0,
+               REF_FILTER_MERGED_INCLUDE,
+               REF_FILTER_MERGED_OMIT
+       } merge;
+       struct commit *merge_commit;
+
+       unsigned int with_commit_tag_algo : 1,
+               match_as_path : 1;
+       unsigned int kind,
+               lines;
 };
 
 struct ref_filter_cbdata {
@@ -49,6 +70,15 @@ struct ref_filter_cbdata {
        struct ref_filter *filter;
 };
 
+/*  Macros for checking --merged and --no-merged options */
+#define _OPT_MERGED_NO_MERGED(option, filter, h) \
+       { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \
+         PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \
+         parse_opt_merge_filter, (intptr_t) "HEAD" \
+       }
+#define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h)
+#define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h)
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -70,5 +100,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
 /*  Default sort option based on refname */
 struct ref_sorting *ref_default_sorting(void);
+/*  Function to parse --merged and --no-merged options */
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
 
 #endif /*  REF_FILTER_H  */
diff --git a/refs.c b/refs.c
index 4e15f60d98ea8affdef226bce199935fa694b195..91c88bad4a764e20c810276fc5b9c1689ba328c3 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -304,6 +304,11 @@ struct ref_entry {
 };
 
 static void read_loose_refs(const char *dirname, struct ref_dir *dir);
+static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
+static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
+                                         const char *dirname, size_t len,
+                                         int incomplete);
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
 
 static struct ref_dir *get_ref_dir(struct ref_entry *entry)
 {
@@ -312,6 +317,24 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
        dir = &entry->u.subdir;
        if (entry->flag & REF_INCOMPLETE) {
                read_loose_refs(entry->name, dir);
+
+               /*
+                * Manually add refs/bisect, which, being
+                * per-worktree, might not appear in the directory
+                * listing for refs/ in the main repo.
+                */
+               if (!strcmp(entry->name, "refs/")) {
+                       int pos = search_ref_dir(dir, "refs/bisect/", 12);
+                       if (pos < 0) {
+                               struct ref_entry *child_entry;
+                               child_entry = create_dir_entry(dir->ref_cache,
+                                                              "refs/bisect/",
+                                                              12, 1);
+                               add_entry_to_dir(dir, child_entry);
+                               read_loose_refs("refs/bisect",
+                                               &child_entry->u.subdir);
+                       }
+               }
                entry->flag &= ~REF_INCOMPLETE;
        }
        return dir;
@@ -2108,6 +2131,15 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
        return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
 }
 
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+{
+       unsigned int flag = 0;
+
+       if (broken)
+               flag = DO_FOR_EACH_INCLUDE_BROKEN;
+       return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
+}
+
 int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data)
 {
@@ -2649,6 +2681,8 @@ struct pack_refs_cb_data {
        struct ref_to_prune *ref_to_prune;
 };
 
+static int is_per_worktree_ref(const char *refname);
+
 /*
  * An each_ref_entry_fn that is run over loose references only.  If
  * the loose reference can be packed, add an entry in the packed ref
@@ -2662,6 +2696,10 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
        struct ref_entry *packed_entry;
        int is_tag_ref = starts_with(entry->name, "refs/tags/");
 
+       /* Do not pack per-worktree refs: */
+       if (is_per_worktree_ref(entry->name))
+               return 0;
+
        /* ALWAYS pack tags */
        if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
                return 0;
@@ -2856,7 +2894,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 
 static int is_per_worktree_ref(const char *refname)
 {
-       return !strcmp(refname, "HEAD");
+       return !strcmp(refname, "HEAD") ||
+               starts_with(refname, "refs/bisect/");
 }
 
 static int is_pseudoref_syntax(const char *refname)
diff --git a/refs.h b/refs.h
index e9a5f3230ab09b667e7ae28b2b0bcd26bd2f067e..6d30c980d182f27ab78d43342c0865df7906693c 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -173,6 +173,7 @@ typedef int each_ref_fn(const char *refname,
 extern int head_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
+extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);
 extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
index d90057b7695918e260e1778a522011dabaa3a37b..403c700c326e4551aea183523af6ed46e1476432 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -20,45 +20,74 @@ static int rerere_enabled = -1;
 /* automatically update cleanly resolved paths to the index */
 static int rerere_autoupdate;
 
-const char *rerere_path(const char *hex, const char *file)
+static void free_rerere_id(struct string_list_item *item)
 {
-       return git_path("rr-cache/%s/%s", hex, file);
+       free(item->util);
 }
 
-static int has_rerere_resolution(const char *hex)
+static const char *rerere_id_hex(const struct rerere_id *id)
+{
+       return id->hex;
+}
+
+const char *rerere_path(const struct rerere_id *id, const char *file)
+{
+       if (!file)
+               return git_path("rr-cache/%s", rerere_id_hex(id));
+
+       return git_path("rr-cache/%s/%s", rerere_id_hex(id), file);
+}
+
+static int has_rerere_resolution(const struct rerere_id *id)
 {
        struct stat st;
-       return !stat(rerere_path(hex, "postimage"), &st);
+
+       return !stat(rerere_path(id, "postimage"), &st);
+}
+
+static struct rerere_id *new_rerere_id_hex(char *hex)
+{
+       struct rerere_id *id = xmalloc(sizeof(*id));
+       strcpy(id->hex, hex);
+       return id;
+}
+
+static struct rerere_id *new_rerere_id(unsigned char *sha1)
+{
+       return new_rerere_id_hex(sha1_to_hex(sha1));
 }
 
+/*
+ * $GIT_DIR/MERGE_RR file is a collection of records, each of which is
+ * "conflict ID", a HT and pathname, terminated with a NUL, and is
+ * used to keep track of the set of paths that "rerere" may need to
+ * work on (i.e. what is left by the previous invocation of "git
+ * rerere" during the current conflict resolution session).
+ */
 static void read_rr(struct string_list *rr)
 {
-       unsigned char sha1[20];
-       char buf[PATH_MAX];
+       struct strbuf buf = STRBUF_INIT;
        FILE *in = fopen(git_path_merge_rr(), "r");
+
        if (!in)
                return;
-       while (fread(buf, 40, 1, in) == 1) {
-               int i;
-               char *name;
-               if (get_sha1_hex(buf, sha1))
+       while (!strbuf_getwholeline(&buf, in, '\0')) {
+               char *path;
+               unsigned char sha1[20];
+               struct rerere_id *id;
+
+               /* There has to be the hash, tab, path and then NUL */
+               if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
                        die("corrupt MERGE_RR");
-               buf[40] = '\0';
-               name = xstrdup(buf);
-               if (fgetc(in) != '\t')
+
+               if (buf.buf[40] != '\t')
                        die("corrupt MERGE_RR");
-               for (i = 0; i < sizeof(buf); i++) {
-                       int c = fgetc(in);
-                       if (c < 0)
-                               die("corrupt MERGE_RR");
-                       buf[i] = c;
-                       if (c == 0)
-                                break;
-               }
-               if (i == sizeof(buf))
-                       die("filename too long");
-               string_list_insert(rr, buf)->util = name;
+               buf.buf[40] = '\0';
+               path = buf.buf + 41;
+               id = new_rerere_id_hex(buf.buf);
+               string_list_insert(rr, path)->util = id;
        }
+       strbuf_release(&buf);
        fclose(in);
 }
 
@@ -68,22 +97,42 @@ static int write_rr(struct string_list *rr, int out_fd)
 {
        int i;
        for (i = 0; i < rr->nr; i++) {
-               const char *path;
-               int length;
-               if (!rr->items[i].util)
+               struct strbuf buf = STRBUF_INIT;
+               struct rerere_id *id;
+
+               assert(rr->items[i].util != RERERE_RESOLVED);
+
+               id = rr->items[i].util;
+               if (!id)
                        continue;
-               path = rr->items[i].string;
-               length = strlen(path) + 1;
-               if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
-                   write_str_in_full(out_fd, "\t") != 1 ||
-                   write_in_full(out_fd, path, length) != length)
+               strbuf_addf(&buf, "%s\t%s%c",
+                           rerere_id_hex(id),
+                           rr->items[i].string, 0);
+               if (write_in_full(out_fd, buf.buf, buf.len) != buf.len)
                        die("unable to write rerere record");
+
+               strbuf_release(&buf);
        }
        if (commit_lock_file(&write_lock) != 0)
                die("unable to write rerere record");
        return 0;
 }
 
+/*
+ * "rerere" interacts with conflicted file contents using this I/O
+ * abstraction.  It reads a conflicted contents from one place via
+ * "getline()" method, and optionally can write it out after
+ * normalizing the conflicted hunks to the "output".  Subclasses of
+ * rerere_io embed this structure at the beginning of their own
+ * rerere_io object.
+ */
+struct rerere_io {
+       int (*getline)(struct strbuf *, struct rerere_io *);
+       FILE *output;
+       int wrerror;
+       /* some more stuff */
+};
+
 static void ferr_write(const void *p, size_t count, FILE *fp, int *err)
 {
        if (!count || *err)
@@ -97,31 +146,34 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
        ferr_write(s, strlen(s), fp, err);
 }
 
-struct rerere_io {
-       int (*getline)(struct strbuf *, struct rerere_io *);
-       FILE *output;
-       int wrerror;
-       /* some more stuff */
-};
-
 static void rerere_io_putstr(const char *str, struct rerere_io *io)
 {
        if (io->output)
                ferr_puts(str, io->output, &io->wrerror);
 }
 
+/*
+ * Write a conflict marker to io->output (if defined).
+ */
 static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
 {
        char buf[64];
 
        while (size) {
-               if (size < sizeof(buf) - 2) {
+               if (size <= sizeof(buf) - 2) {
                        memset(buf, ch, size);
                        buf[size] = '\n';
                        buf[size + 1] = '\0';
                        size = 0;
                } else {
                        int sz = sizeof(buf) - 1;
+
+                       /*
+                        * Make sure we will not write everything out
+                        * in this round by leaving at least 1 byte
+                        * for the next round, giving the next round
+                        * a chance to add the terminating LF.  Yuck.
+                        */
                        if (size <= sz)
                                sz -= (sz - size) + 1;
                        memset(buf, ch, sz);
@@ -138,19 +190,42 @@ static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
                ferr_write(mem, sz, io->output, &io->wrerror);
 }
 
+/*
+ * Subclass of rerere_io that reads from an on-disk file
+ */
 struct rerere_io_file {
        struct rerere_io io;
        FILE *input;
 };
 
+/*
+ * ... and its getline() method implementation
+ */
 static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
 {
        struct rerere_io_file *io = (struct rerere_io_file *)io_;
        return strbuf_getwholeline(sb, io->input, '\n');
 }
 
-static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+/*
+ * Require the exact number of conflict marker letters, no more, no
+ * less, followed by SP or any whitespace
+ * (including LF).
+ */
+static int is_cmarker(char *buf, int marker_char, int marker_size)
 {
+       int want_sp;
+
+       /*
+        * The beginning of our version and the end of their version
+        * always are labeled like "<<<<< ours" or ">>>>> theirs",
+        * hence we set want_sp for them.  Note that the version from
+        * the common ancestor in diff3-style output is not always
+        * labelled (e.g. "||||| common" is often seen but "|||||"
+        * alone is also valid), so we do not set want_sp.
+        */
+       want_sp = (marker_char == '<') || (marker_char == '>');
+
        while (marker_size--)
                if (*buf++ != marker_char)
                        return 0;
@@ -159,6 +234,21 @@ static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
        return isspace(*buf);
 }
 
+/*
+ * Read contents a file with conflicts, normalize the conflicts
+ * by (1) discarding the common ancestor version in diff3-style,
+ * (2) reordering our side and their side so that whichever sorts
+ * alphabetically earlier comes before the other one, while
+ * computing the "conflict ID", which is just an SHA-1 hash of
+ * one side of the conflict, NUL, the other side of the conflict,
+ * and NUL concatenated together.
+ *
+ * Return the number of conflict hunks found.
+ *
+ * NEEDSWORK: the logic and theory of operation behind this conflict
+ * normalization may deserve to be documented somewhere, perhaps in
+ * Documentation/technical/rerere.txt.
+ */
 static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
 {
        git_SHA_CTX ctx;
@@ -173,19 +263,19 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
                git_SHA1_Init(&ctx);
 
        while (!io->getline(&buf, io)) {
-               if (is_cmarker(buf.buf, '<', marker_size, 1)) {
+               if (is_cmarker(buf.buf, '<', marker_size)) {
                        if (hunk != RR_CONTEXT)
                                goto bad;
                        hunk = RR_SIDE_1;
-               } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
+               } else if (is_cmarker(buf.buf, '|', marker_size)) {
                        if (hunk != RR_SIDE_1)
                                goto bad;
                        hunk = RR_ORIGINAL;
-               } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
+               } else if (is_cmarker(buf.buf, '=', marker_size)) {
                        if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
                                goto bad;
                        hunk = RR_SIDE_2;
-               } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
+               } else if (is_cmarker(buf.buf, '>', marker_size)) {
                        if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
@@ -229,6 +319,10 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
        return hunk_no;
 }
 
+/*
+ * Scan the path for conflicts, do the "handle_path()" thing above, and
+ * return the number of conflict hunks found.
+ */
 static int handle_file(const char *path, unsigned char *sha1, const char *output)
 {
        int hunk_no = 0;
@@ -270,11 +364,18 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output
        return hunk_no;
 }
 
+/*
+ * Subclass of rerere_io that reads from an in-core buffer that is a
+ * strbuf
+ */
 struct rerere_io_mem {
        struct rerere_io io;
        struct strbuf input;
 };
 
+/*
+ * ... and its getline() method implementation
+ */
 static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
 {
        struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
@@ -313,24 +414,23 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
                return -1;
        pos = -pos - 1;
 
-       for (i = 0; i < 3; i++) {
+       while (pos < active_nr) {
                enum object_type type;
                unsigned long size;
-               int j;
 
-               if (active_nr <= pos)
-                       break;
                ce = active_cache[pos++];
                if (ce_namelen(ce) != len || memcmp(ce->name, path, len))
-                       continue;
-               j = ce_stage(ce) - 1;
-               mmfile[j].ptr = read_sha1_file(ce->sha1, &type, &size);
-               mmfile[j].size = size;
+                       break;
+               i = ce_stage(ce) - 1;
+               if (!mmfile[i].ptr) {
+                       mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+                       mmfile[i].size = size;
+               }
        }
-       for (i = 0; i < 3; i++) {
+       for (i = 0; i < 3; i++)
                if (!mmfile[i].ptr && !mmfile[i].size)
                        mmfile[i].ptr = xstrdup("");
-       }
+
        /*
         * NEEDSWORK: handle conflicts from merges with
         * merge.renormalize set, too
@@ -350,6 +450,10 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        strbuf_init(&io.input, 0);
        strbuf_attach(&io.input, result.ptr, result.size, result.size);
 
+       /*
+        * Grab the conflict ID and optionally write the original
+        * contents with conflict markers out.
+        */
        hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
        strbuf_release(&io.input);
        if (io.io.output)
@@ -357,6 +461,14 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        return hunk_no;
 }
 
+/*
+ * Look at a cache entry at "i" and see if it is not conflicting,
+ * conflicting and we are willing to handle, or conflicting and
+ * we are unable to handle, and return the determination in *type.
+ * Return the cache index to be looked at next, by skipping the
+ * stages we have already looked at in this invocation of this
+ * function.
+ */
 static int check_one_conflict(int i, int *type)
 {
        const struct cache_entry *e = active_cache[i];
@@ -367,10 +479,8 @@ static int check_one_conflict(int i, int *type)
        }
 
        *type = PUNTED;
-       if (ce_stage(e) == 1) {
-               if (active_nr <= ++i)
-                       return i + 1;
-       }
+       while (ce_stage(active_cache[i]) == 1)
+               i++;
 
        /* Only handle regular files with both stages #2 and #3 */
        if (i + 1 < active_nr) {
@@ -390,6 +500,17 @@ static int check_one_conflict(int i, int *type)
        return i;
 }
 
+/*
+ * Scan the index and find paths that have conflicts that rerere can
+ * handle, i.e. the ones that has both stages #2 and #3.
+ *
+ * NEEDSWORK: we do not record or replay a previous "resolve by
+ * deletion" for a delete-modify conflict, as that is inherently risky
+ * without knowing what modification is being discarded.  The only
+ * safe case, i.e. both side doing the deletion and modification that
+ * are identical to the previous round, might want to be handled,
+ * though.
+ */
 static int find_conflict(struct string_list *conflict)
 {
        int i;
@@ -406,6 +527,21 @@ static int find_conflict(struct string_list *conflict)
        return 0;
 }
 
+/*
+ * The merge_rr list is meant to hold outstanding conflicted paths
+ * that rerere could handle.  Abuse the list by adding other types of
+ * entries to allow the caller to show "rerere remaining".
+ *
+ * - Conflicted paths that rerere does not handle are added
+ * - Conflicted paths that have been resolved are marked as such
+ *   by storing RERERE_RESOLVED to .util field (where conflict ID
+ *   is expected to be stored).
+ *
+ * Do *not* write MERGE_RR file out after calling this function.
+ *
+ * NEEDSWORK: we may want to fix the caller that implements "rerere
+ * remaining" to do this without abusing merge_rr.
+ */
 int rerere_remaining(struct string_list *merge_rr)
 {
        int i;
@@ -424,7 +560,7 @@ int rerere_remaining(struct string_list *merge_rr)
                        struct string_list_item *it;
                        it = string_list_lookup(merge_rr, (const char *)e->name);
                        if (it != NULL) {
-                               free(it->util);
+                               free_rerere_id(it);
                                it->util = RERERE_RESOLVED;
                        }
                }
@@ -432,39 +568,66 @@ int rerere_remaining(struct string_list *merge_rr)
        return 0;
 }
 
-static int merge(const char *name, const char *path)
+/*
+ * Find the conflict identified by "id"; the change between its
+ * "preimage" (i.e. a previous contents with conflict markers) and its
+ * "postimage" (i.e. the corresponding contents with conflicts
+ * resolved) may apply cleanly to the contents stored in "path", i.e.
+ * the conflict this time around.
+ *
+ * Returns 0 for successful replay of recorded resolution, or non-zero
+ * for failure.
+ */
+static int merge(const struct rerere_id *id, const char *path)
 {
+       FILE *f;
        int ret;
        mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};
        mmbuffer_t result = {NULL, 0};
 
-       if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
-               return 1;
+       /*
+        * Normalize the conflicts in path and write it out to
+        * "thisimage" temporary file.
+        */
+       if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) {
+               ret = 1;
+               goto out;
+       }
 
-       if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
-                       read_mmfile(&base, rerere_path(name, "preimage")) ||
-                       read_mmfile(&other, rerere_path(name, "postimage"))) {
+       if (read_mmfile(&cur, rerere_path(id, "thisimage")) ||
+           read_mmfile(&base, rerere_path(id, "preimage")) ||
+           read_mmfile(&other, rerere_path(id, "postimage"))) {
                ret = 1;
                goto out;
        }
+
+       /*
+        * A three-way merge. Note that this honors user-customizable
+        * low-level merge driver settings.
+        */
        ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL);
-       if (!ret) {
-               FILE *f;
-
-               if (utime(rerere_path(name, "postimage"), NULL) < 0)
-                       warning("failed utime() on %s: %s",
-                                       rerere_path(name, "postimage"),
-                                       strerror(errno));
-               f = fopen(path, "w");
-               if (!f)
-                       return error("Could not open %s: %s", path,
-                                    strerror(errno));
-               if (fwrite(result.ptr, result.size, 1, f) != 1)
-                       error("Could not write %s: %s", path, strerror(errno));
-               if (fclose(f))
-                       return error("Writing %s failed: %s", path,
-                                    strerror(errno));
-       }
+       if (ret)
+               goto out;
+
+       /*
+        * A successful replay of recorded resolution.
+        * Mark that "postimage" was used to help gc.
+        */
+       if (utime(rerere_path(id, "postimage"), NULL) < 0)
+               warning("failed utime() on %s: %s",
+                       rerere_path(id, "postimage"),
+                       strerror(errno));
+
+       /* Update "path" with the resolution */
+       f = fopen(path, "w");
+       if (!f)
+               return error("Could not open %s: %s", path,
+                            strerror(errno));
+       if (fwrite(result.ptr, result.size, 1, f) != 1)
+               error("Could not write %s: %s", path, strerror(errno));
+       if (fclose(f))
+               return error("Writing %s failed: %s", path,
+                            strerror(errno));
 
 out:
        free(cur.ptr);
@@ -487,6 +650,8 @@ static void update_paths(struct string_list *update)
                struct string_list_item *item = &update->items[i];
                if (add_file_to_cache(item->string, 0))
                        exit(128);
+               fprintf(stderr, "Staged '%s' using previous resolution.\n",
+                       item->string);
        }
 
        if (active_cache_changed) {
@@ -496,6 +661,41 @@ static void update_paths(struct string_list *update)
                rollback_lock_file(&index_lock);
 }
 
+/*
+ * The path indicated by rr_item may still have conflict for which we
+ * have a recorded resolution, in which case replay it and optionally
+ * update it.  Or it may have been resolved by the user and we may
+ * only have the preimage for that conflict, in which case the result
+ * needs to be recorded as a resolution in a postimage file.
+ */
+static void do_rerere_one_path(struct string_list_item *rr_item,
+                              struct string_list *update)
+{
+       const char *path = rr_item->string;
+       const struct rerere_id *id = rr_item->util;
+
+       /* Is there a recorded resolution we could attempt to apply? */
+       if (has_rerere_resolution(id)) {
+               if (merge(id, path))
+                       return; /* failed to replay */
+
+               if (rerere_autoupdate)
+                       string_list_insert(update, path);
+               else
+                       fprintf(stderr,
+                               "Resolved '%s' using previous resolution.\n",
+                               path);
+       } else if (!handle_file(path, NULL, NULL)) {
+               /* The user has resolved it. */
+               copy_file(rerere_path(id, "postimage"), path, 0666);
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+       } else {
+               return;
+       }
+       free_rerere_id(rr_item);
+       rr_item->util = NULL;
+}
+
 static int do_plain_rerere(struct string_list *rr, int fd)
 {
        struct string_list conflict = STRING_LIST_INIT_DUP;
@@ -505,65 +705,55 @@ static int do_plain_rerere(struct string_list *rr, int fd)
        find_conflict(&conflict);
 
        /*
-        * MERGE_RR records paths with conflicts immediately after merge
-        * failed.  Some of the conflicted paths might have been hand resolved
-        * in the working tree since then, but the initial run would catch all
-        * and register their preimages.
+        * MERGE_RR records paths with conflicts immediately after
+        * merge failed.  Some of the conflicted paths might have been
+        * hand resolved in the working tree since then, but the
+        * initial run would catch all and register their preimages.
         */
-
        for (i = 0; i < conflict.nr; i++) {
+               struct rerere_id *id;
+               unsigned char sha1[20];
                const char *path = conflict.items[i].string;
-               if (!string_list_has_string(rr, path)) {
-                       unsigned char sha1[20];
-                       char *hex;
-                       int ret;
-                       ret = handle_file(path, sha1, NULL);
-                       if (ret < 1)
-                               continue;
-                       hex = xstrdup(sha1_to_hex(sha1));
-                       string_list_insert(rr, path)->util = hex;
-                       if (mkdir_in_gitdir(git_path("rr-cache/%s", hex)))
-                               continue;
-                       handle_file(path, NULL, rerere_path(hex, "preimage"));
-                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
-               }
-       }
+               int ret;
 
-       /*
-        * Now some of the paths that had conflicts earlier might have been
-        * hand resolved.  Others may be similar to a conflict already that
-        * was resolved before.
-        */
+               if (string_list_has_string(rr, path))
+                       continue;
 
-       for (i = 0; i < rr->nr; i++) {
-               int ret;
-               const char *path = rr->items[i].string;
-               const char *name = (const char *)rr->items[i].util;
-
-               if (has_rerere_resolution(name)) {
-                       if (!merge(name, path)) {
-                               const char *msg;
-                               if (rerere_autoupdate) {
-                                       string_list_insert(&update, path);
-                                       msg = "Staged '%s' using previous resolution.\n";
-                               } else
-                                       msg = "Resolved '%s' using previous resolution.\n";
-                               fprintf(stderr, msg, path);
-                               goto mark_resolved;
-                       }
-               }
+               /*
+                * Ask handle_file() to scan and assign a
+                * conflict ID.  No need to write anything out
+                * yet.
+                */
+               ret = handle_file(path, sha1, NULL);
+               if (ret < 1)
+                       continue;
 
-               /* Let's see if we have resolved it. */
-               ret = handle_file(path, NULL, NULL);
-               if (ret)
+               id = new_rerere_id(sha1);
+               string_list_insert(rr, path)->util = id;
+
+               /*
+                * If the directory does not exist, create
+                * it.  mkdir_in_gitdir() will fail with
+                * EEXIST if there already is one.
+                *
+                * NEEDSWORK: make sure "gc" does not remove
+                * preimage without removing the directory.
+                */
+               if (mkdir_in_gitdir(rerere_path(id, NULL)))
                        continue;
 
-               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rerere_path(name, "postimage"), path, 0666);
-       mark_resolved:
-               rr->items[i].util = NULL;
+               /*
+                * We are the first to encounter this
+                * conflict.  Ask handle_file() to write the
+                * normalized contents to the "preimage" file.
+                */
+               handle_file(path, NULL, rerere_path(id, "preimage"));
+               fprintf(stderr, "Recorded preimage for '%s'\n", path);
        }
 
+       for (i = 0; i < rr->nr; i++)
+               do_rerere_one_path(&rr->items[i], &update);
+
        if (update.nr)
                update_paths(&update);
 
@@ -614,6 +804,11 @@ int setup_rerere(struct string_list *merge_rr, int flags)
        return fd;
 }
 
+/*
+ * The main entry point that is called internally from codepaths that
+ * perform mergy operations, possibly leaving conflicted index entries
+ * and working tree files.
+ */
 int rerere(int flags)
 {
        struct string_list merge_rr = STRING_LIST_INIT_DUP;
@@ -628,25 +823,42 @@ int rerere(int flags)
 static int rerere_forget_one_path(const char *path, struct string_list *rr)
 {
        const char *filename;
-       char *hex;
+       struct rerere_id *id;
        unsigned char sha1[20];
        int ret;
+       struct string_list_item *item;
 
+       /*
+        * Recreate the original conflict from the stages in the
+        * index and compute the conflict ID
+        */
        ret = handle_cache(path, sha1, NULL);
        if (ret < 1)
                return error("Could not parse conflict hunks in '%s'", path);
-       hex = xstrdup(sha1_to_hex(sha1));
-       filename = rerere_path(hex, "postimage");
+
+       /* Nuke the recorded resolution for the conflict */
+       id = new_rerere_id(sha1);
+       filename = rerere_path(id, "postimage");
        if (unlink(filename))
                return (errno == ENOENT
                        ? error("no remembered resolution for %s", path)
                        : error("cannot unlink %s: %s", filename, strerror(errno)));
 
-       handle_cache(path, sha1, rerere_path(hex, "preimage"));
+       /*
+        * Update the preimage so that the user can resolve the
+        * conflict in the working tree, run us again to record
+        * the postimage.
+        */
+       handle_cache(path, sha1, rerere_path(id, "preimage"));
        fprintf(stderr, "Updated preimage for '%s'\n", path);
 
-
-       string_list_insert(rr, path)->util = hex;
+       /*
+        * And remember that we can record resolution for this
+        * conflict when the user is done.
+        */
+       item = string_list_insert(rr, path);
+       free_rerere_id(item);
+       item->util = id;
        fprintf(stderr, "Forgot resolution for %s\n", path);
        return 0;
 }
@@ -664,6 +876,11 @@ int rerere_forget(struct pathspec *pathspec)
        if (fd < 0)
                return 0;
 
+       /*
+        * The paths may have been resolved (incorrectly);
+        * recover the original conflicted state and then
+        * find the conflicted paths.
+        */
        unmerge_cache(pathspec);
        find_conflict(&conflict);
        for (i = 0; i < conflict.nr; i++) {
@@ -676,24 +893,51 @@ int rerere_forget(struct pathspec *pathspec)
        return write_rr(&merge_rr, fd);
 }
 
-static time_t rerere_created_at(const char *name)
+/*
+ * Garbage collection support
+ */
+
+/*
+ * Note that this is not reentrant but is used only one-at-a-time
+ * so it does not matter right now.
+ */
+static struct rerere_id *dirname_to_id(const char *name)
+{
+       static struct rerere_id id;
+       strcpy(id.hex, name);
+       return &id;
+}
+
+static time_t rerere_created_at(const char *dir_name)
 {
        struct stat st;
-       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+       struct rerere_id *id = dirname_to_id(dir_name);
+
+       return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static time_t rerere_last_used_at(const char *name)
+static time_t rerere_last_used_at(const char *dir_name)
 {
        struct stat st;
-       return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+       struct rerere_id *id = dirname_to_id(dir_name);
+
+       return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static void unlink_rr_item(const char *name)
+/*
+ * Remove the recorded resolution for a given conflict ID
+ */
+static void unlink_rr_item(struct rerere_id *id)
 {
-       unlink(rerere_path(name, "thisimage"));
-       unlink(rerere_path(name, "preimage"));
-       unlink(rerere_path(name, "postimage"));
-       rmdir(git_path("rr-cache/%s", name));
+       unlink(rerere_path(id, "thisimage"));
+       unlink(rerere_path(id, "preimage"));
+       unlink(rerere_path(id, "postimage"));
+       /*
+        * NEEDSWORK: what if this rmdir() fails?  Wouldn't we then
+        * assume that we already have preimage recorded in
+        * do_plain_rerere()?
+        */
+       rmdir(rerere_path(id, NULL));
 }
 
 void rerere_gc(struct string_list *rr)
@@ -715,6 +959,7 @@ void rerere_gc(struct string_list *rr)
        dir = opendir(git_path("rr-cache"));
        if (!dir)
                die_errno("unable to open rr-cache directory");
+       /* Collect stale conflict IDs ... */
        while ((e = readdir(dir))) {
                if (is_dot_or_dotdot(e->d_name))
                        continue;
@@ -732,12 +977,20 @@ void rerere_gc(struct string_list *rr)
                        string_list_append(&to_remove, e->d_name);
        }
        closedir(dir);
+       /* ... and then remove them one-by-one */
        for (i = 0; i < to_remove.nr; i++)
-               unlink_rr_item(to_remove.items[i].string);
+               unlink_rr_item(dirname_to_id(to_remove.items[i].string));
        string_list_clear(&to_remove, 0);
        rollback_lock_file(&write_lock);
 }
 
+/*
+ * During a conflict resolution, after "rerere" recorded the
+ * preimages, abandon them if the user did not resolve them or
+ * record their resolutions.  And drop $GIT_DIR/MERGE_RR.
+ *
+ * NEEDSWORK: shouldn't we be calling this from "reset --hard"?
+ */
 void rerere_clear(struct string_list *merge_rr)
 {
        int i;
@@ -746,9 +999,9 @@ void rerere_clear(struct string_list *merge_rr)
                return;
 
        for (i = 0; i < merge_rr->nr; i++) {
-               const char *name = (const char *)merge_rr->items[i].util;
-               if (!has_rerere_resolution(name))
-                       unlink_rr_item(name);
+               struct rerere_id *id = merge_rr->items[i].util;
+               if (!has_rerere_resolution(id))
+                       unlink_rr_item(id);
        }
        unlink_or_warn(git_path_merge_rr());
        rollback_lock_file(&write_lock);
index 407d59996d97ae307d5b4a124f78ef79666e1feb..1222e91921882b41ac9c1648960eca9305b27d8d 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -16,9 +16,19 @@ struct pathspec;
  */
 extern void *RERERE_RESOLVED;
 
+struct rerere_id {
+       char hex[41];
+};
+
 extern int setup_rerere(struct string_list *, int);
 extern int rerere(int);
-extern const char *rerere_path(const char *hex, const char *file);
+/*
+ * Given the conflict ID and the name of a "file" used for replaying
+ * the recorded resolution (e.g. "preimage", "postimage"), return the
+ * path to that filesystem entity.  With "file" specified with NULL,
+ * return the path to the directory that houses these files.
+ */
+extern const char *rerere_path(const struct rerere_id *, const char *file);
 extern int rerere_forget(struct pathspec *);
 extern int rerere_remaining(struct string_list *);
 extern void rerere_clear(struct string_list *);
index 3277cf797ed41e5834b3b94fa8d2e9e9d5b4a317..c8029f239405e463bba5b033a7321faf16ca6c0d 100644 (file)
@@ -595,7 +595,7 @@ static NORETURN void die_async(const char *err, va_list params)
 {
        vreportf("fatal: ", err, params);
 
-       if (!pthread_equal(main_thread, pthread_self())) {
+       if (in_async()) {
                struct async *async = pthread_getspecific(async_key);
                if (async->proc_in >= 0)
                        close(async->proc_in);
@@ -614,6 +614,13 @@ static int async_die_is_recursing(void)
        return ret != NULL;
 }
 
+int in_async(void)
+{
+       if (!main_thread_set)
+               return 0; /* no asyncs started yet */
+       return !pthread_equal(main_thread, pthread_self());
+}
+
 #else
 
 static struct {
@@ -653,6 +660,12 @@ int git_atexit(void (*handler)(void))
 }
 #define atexit git_atexit
 
+static int process_is_async;
+int in_async(void)
+{
+       return process_is_async;
+}
+
 #endif
 
 int start_async(struct async *async)
@@ -712,6 +725,7 @@ int start_async(struct async *async)
                if (need_out)
                        close(fdout[0]);
                git_atexit_clear();
+               process_is_async = 1;
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
 
index 5b4425a3cbe1aea2bae40c4e060e45ee2d7a29a5..629fab7ae0b2af5c0ada5977a11f862de4cdd273 100644 (file)
@@ -118,5 +118,6 @@ struct async {
 
 int start_async(struct async *async);
 int finish_async(struct async *async);
+int in_async(void);
 
 #endif
index f5c01758ca38b58fc10e30cfac81be5becb0316b..b1673b3e8f38d9961b8b2783e1f5c47a865638fb 100644 (file)
@@ -111,6 +111,10 @@ test_expect_success 'blame 2 authors + 2 merged-in authors' '
        check_count A 2 B 1 B1 2 B2 1
 '
 
+test_expect_success 'blame --first-parent blames merge for branch1' '
+       check_count --first-parent A 2 B 1 "A U Thor" 2 B2 1
+'
+
 test_expect_success 'blame ancestor' '
        check_count -h master A 2 B 2
 '
index 93605f42f27cef9ef3ffe381c84aea9f6f4be426..627ef854d5c804c0248a728862fdf9d4ea344568 100755 (executable)
@@ -266,15 +266,21 @@ test_expect_success 'setup common repository' 'git --git-dir=bar init'
 test_git_path GIT_COMMON_DIR=bar index                    .git/index
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo      bar/logs/refs/bisectfoo
 test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
 test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
 test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
 test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts
 test_git_path GIT_COMMON_DIR=bar info/sparse-checkout     .git/info/sparse-checkout
+test_git_path GIT_COMMON_DIR=bar info//sparse-checkout    .git/info//sparse-checkout
 test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
 test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
 test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
 test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/bisect/foo          .git/refs/bisect/foo
 test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
 test_git_path GIT_COMMON_DIR=bar config                   bar/config
 test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
index 97406fa4b137afdd99e64172d956718005844120..af1b20dd5c6763e58eccd3f93722987f5f9ce233 100755 (executable)
@@ -1130,4 +1130,23 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches
 )
 '
 
+test_expect_success 'handle per-worktree refs in refs/bisect' '
+       git commit --allow-empty -m "initial commit" &&
+       git worktree add -b branch worktree &&
+       (
+               cd worktree &&
+               git commit --allow-empty -m "test commit"  &&
+               git for-each-ref >for-each-ref.out &&
+               ! grep refs/bisect for-each-ref.out &&
+               git update-ref refs/bisect/something HEAD &&
+               git rev-parse refs/bisect/something >../worktree-head &&
+               git for-each-ref | grep refs/bisect/something
+       ) &&
+       test_path_is_missing .git/refs/bisect &&
+       test_must_fail git rev-parse refs/bisect/something &&
+       git update-ref refs/bisect/something HEAD &&
+       git rev-parse refs/bisect/something >main-head &&
+       ! test_cmp main-head worktree-head
+'
+
 test_done
index 7b5b6d452e3762c9d0e311602bf39317200c21a7..db244d2f8820cf63fd77e91460631009b20e4029 100755 (executable)
@@ -160,6 +160,13 @@ test_expect_success 'pack ref directly below refs/' '
        test_path_is_missing .git/refs/top
 '
 
+test_expect_success 'do not pack ref in refs/bisect' '
+       git update-ref refs/bisect/local HEAD &&
+       git pack-refs --all --prune &&
+       ! grep refs/bisect/local .git/packed-refs >/dev/null &&
+       test_path_is_file .git/refs/bisect/local
+'
+
 test_expect_success 'disable reflogs' '
        git config core.logallrefupdates false &&
        rm -rf .git/logs
index d783f03d3fc581eed08d8d2593e20ced3cbae200..944154b2e0ad7da5ddacdde5c22847babf1909c9 100755 (executable)
@@ -37,6 +37,16 @@ testrebase() {
        type=$1
        dotest=$2
 
+       test_expect_success "rebase$type: dirty worktree, --no-autostash" '
+               test_config rebase.autostash true &&
+               git reset --hard &&
+               git checkout -b rebased-feature-branch feature-branch &&
+               test_when_finished git branch -D rebased-feature-branch &&
+               test_when_finished git checkout feature-branch &&
+               echo dirty >>file3 &&
+               test_must_fail git rebase$type --no-autostash unrelated-onto-branch
+       '
+
        test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" '
                test_config rebase.autostash true &&
                git reset --hard &&
index 7a8499ce665c74961fdbf6713c0b70c37f95f5e3..dfaf9d9f68939f0c39a512c478265b4aa227c973 100755 (executable)
@@ -919,6 +919,19 @@ test_expect_success 'new remote' '
        cmp expect actual
 '
 
+get_url_test () {
+       cat >expect &&
+       git remote get-url "$@" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'get-url on new remote' '
+       echo foo | get_url_test someremote &&
+       echo foo | get_url_test --all someremote &&
+       echo foo | get_url_test --push someremote &&
+       echo foo | get_url_test --push --all someremote
+'
+
 test_expect_success 'remote set-url bar' '
        git remote set-url someremote bar &&
        echo bar >expect &&
@@ -961,6 +974,13 @@ test_expect_success 'remote set-url --push zot' '
        cmp expect actual
 '
 
+test_expect_success 'get-url with different urls' '
+       echo baz | get_url_test someremote &&
+       echo baz | get_url_test --all someremote &&
+       echo zot | get_url_test --push someremote &&
+       echo zot | get_url_test --push --all someremote
+'
+
 test_expect_success 'remote set-url --push qux zot' '
        git remote set-url --push someremote qux zot &&
        echo qux >expect &&
@@ -995,6 +1015,14 @@ test_expect_success 'remote set-url --push --add aaa' '
        cmp expect actual
 '
 
+test_expect_success 'get-url on multi push remote' '
+       echo foo | get_url_test --push someremote &&
+       get_url_test --push --all someremote <<-\EOF
+       foo
+       aaa
+       EOF
+'
+
 test_expect_success 'remote set-url --push bar aaa' '
        git remote set-url --push someremote bar aaa &&
        echo foo >expect &&
@@ -1039,6 +1067,14 @@ test_expect_success 'remote set-url --add bbb' '
        cmp expect actual
 '
 
+test_expect_success 'get-url on multi fetch remote' '
+       echo baz | get_url_test someremote &&
+       get_url_test --all someremote <<-\EOF
+       baz
+       bbb
+       EOF
+'
+
 test_expect_success 'remote set-url --delete .*' '
        test_must_fail git remote set-url --delete someremote .\* &&
        echo "YYY" >expect &&
@@ -1108,6 +1144,7 @@ test_extra_arg rename origin newname
 test_extra_arg remove origin
 test_extra_arg set-head origin master
 # set-branches takes any number of args
+test_extra_arg get-url origin newurl
 test_extra_arg set-url origin newurl oldurl
 # show takes any number of args
 # prune takes any number of args
diff --git a/t/t5507-remote-environment.sh b/t/t5507-remote-environment.sh
new file mode 100755 (executable)
index 0000000..e614929
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='check environment showed to remote side of transports'
+. ./test-lib.sh
+
+test_expect_success 'set up "remote" push situation' '
+       test_commit one &&
+       git config push.default current &&
+       git init remote
+'
+
+test_expect_success 'set up fake ssh' '
+       GIT_SSH_COMMAND="f() {
+               cd \"\$TRASH_DIRECTORY\" &&
+               eval \"\$2\"
+       }; f" &&
+       export GIT_SSH_COMMAND &&
+       export TRASH_DIRECTORY
+'
+
+# due to receive.denyCurrentBranch=true
+test_expect_success 'confirm default push fails' '
+       test_must_fail git push remote
+'
+
+test_expect_success 'config does not travel over same-machine push' '
+       test_must_fail git -c receive.denyCurrentBranch=false push remote
+'
+
+test_expect_success 'config does not travel over ssh push' '
+       test_must_fail git -c receive.denyCurrentBranch=false push host:remote
+'
+
+test_done
index c9d3ed14c3a3238208b44ce815e37123298dea77..362b1581e092826400d78515cdc8d5cb0f7cfc29 100755 (executable)
@@ -242,13 +242,6 @@ clean_mark () {
        sort >$(basename "$1")
 }
 
-cmp_marks () {
-       test_when_finished "rm -rf git.marks testgit.marks" &&
-       clean_mark ".git/testgit/$1/git.marks" &&
-       clean_mark ".git/testgit/$1/testgit.marks" &&
-       test_cmp git.marks testgit.marks
-}
-
 test_expect_success 'proper failure checks for fetching' '
        (cd local &&
        test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error &&
@@ -258,12 +251,15 @@ test_expect_success 'proper failure checks for fetching' '
 '
 
 test_expect_success 'proper failure checks for pushing' '
+       test_when_finished "rm -rf local/git.marks local/testgit.marks" &&
        (cd local &&
        git checkout -b crash master &&
        echo crash >>file &&
        git commit -a -m crash &&
        test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git push --all &&
-       cmp_marks origin
+       clean_mark ".git/testgit/origin/git.marks" &&
+       clean_mark ".git/testgit/origin/testgit.marks" &&
+       test_cmp git.marks testgit.marks
        )
 '
 
index 9e2c20374732d790edefc4cd87aa28223fbb24c4..e74662ba5c638de5acb4188e2cd0c5ff2456ec13 100755 (executable)
@@ -759,4 +759,139 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' '
        git bisect reset
 '
 
+test_expect_success 'bisect starts with only one new' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect new $HASH4 &&
+       git bisect next
+'
+
+test_expect_success 'bisect does not start with only one old' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect old $HASH1 &&
+       test_must_fail git bisect next
+'
+
+test_expect_success 'bisect start with one new and old' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect old $HASH1 &&
+       git bisect new $HASH4 &&
+       git bisect new &&
+       git bisect new >bisect_result &&
+       grep "$HASH2 is the first new commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect replay with old and new' '
+       git bisect replay log_to_replay.txt >bisect_result &&
+       grep "$HASH2 is the first new commit" bisect_result &&
+       git bisect reset
+'
+
+test_expect_success 'bisect cannot mix old/new and good/bad' '
+       git bisect start &&
+       git bisect bad $HASH4 &&
+       test_must_fail git bisect old $HASH1
+'
+
+test_expect_success 'bisect terms needs 0 or 1 argument' '
+       git bisect reset &&
+       test_must_fail git bisect terms only-one &&
+       test_must_fail git bisect terms 1 2 &&
+       test_must_fail git bisect terms 2>actual &&
+       echo "no terms defined" >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect terms shows good/bad after start' '
+       git bisect reset &&
+       git bisect start HEAD $HASH1 &&
+       git bisect terms --term-good >actual &&
+       echo good >expected &&
+       test_cmp expected actual &&
+       git bisect terms --term-bad >actual &&
+       echo bad >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect start with one term1 and term2' '
+       git bisect reset &&
+       git bisect start --term-old term2 --term-new term1 &&
+       git bisect term2 $HASH1 &&
+       git bisect term1 $HASH4 &&
+       git bisect term1 &&
+       git bisect term1 >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect replay with term1 and term2' '
+       git bisect replay log_to_replay.txt >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect reset
+'
+
+test_expect_success 'bisect start term1 term2' '
+       git bisect reset &&
+       git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 &&
+       git bisect term1 &&
+       git bisect term1 >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect cannot mix terms' '
+       git bisect reset &&
+       git bisect start --term-good term1 --term-bad term2 $HASH4 $HASH1 &&
+       test_must_fail git bisect a &&
+       test_must_fail git bisect b &&
+       test_must_fail git bisect bad &&
+       test_must_fail git bisect good &&
+       test_must_fail git bisect new &&
+       test_must_fail git bisect old
+'
+
+test_expect_success 'bisect terms rejects invalid terms' '
+       git bisect reset &&
+       test_must_fail git bisect start --term-good invalid..term &&
+       test_must_fail git bisect terms --term-bad invalid..term &&
+       test_must_fail git bisect terms --term-good bad &&
+       test_must_fail git bisect terms --term-good old &&
+       test_must_fail git bisect terms --term-good skip &&
+       test_must_fail git bisect terms --term-good reset &&
+       test_path_is_missing .git/BISECT_TERMS
+'
+
+test_expect_success 'bisect start --term-* does store terms' '
+       git bisect reset &&
+       git bisect start --term-bad=one --term-good=two &&
+       git bisect terms >actual &&
+       cat <<-EOF >expected &&
+       Your current terms are two for the old state
+       and one for the new state.
+       EOF
+       test_cmp expected actual &&
+       git bisect terms --term-bad >actual &&
+       echo one >expected &&
+       test_cmp expected actual &&
+       git bisect terms --term-good >actual &&
+       echo two >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect start takes options and revs in any order' '
+       git bisect reset &&
+       git bisect start --term-good one $HASH4 \
+               --term-good two --term-bad bad-term \
+               $HASH1 --term-good three -- &&
+       (git bisect terms --term-bad && git bisect terms --term-good) >actual &&
+       printf "%s\n%s\n" bad-term three >expected &&
+       test_cmp expected actual
+'
+
 test_done
index 7c9bec76302259beebeea7521b9c74acfe6de264..03873b09d1a4480549e1b8ab92fc3780fb874d89 100755 (executable)
@@ -8,8 +8,8 @@ test_description='for-each-ref test'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
 
-# Mon Jul 3 15:18:43 2006 +0000
-datestamp=1151939923
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
 setdate_and_increment () {
     GIT_COMMITTER_DATE="$datestamp +0200"
     datestamp=$(expr "$datestamp" + 1)
@@ -61,21 +61,21 @@ test_atom head object ''
 test_atom head type ''
 test_atom head '*objectname' ''
 test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151939924 +0200'
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
 test_atom head authorname 'A U Thor'
 test_atom head authoremail '<author@example.com>'
-test_atom head authordate 'Mon Jul 3 17:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151939923 +0200'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head committername 'C O Mitter'
 test_atom head committeremail '<committer@example.com>'
-test_atom head committerdate 'Mon Jul 3 17:18:43 2006 +0200'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head tag ''
 test_atom head tagger ''
 test_atom head taggername ''
 test_atom head taggeremail ''
 test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
-test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head subject 'Initial'
 test_atom head contents:subject 'Initial'
 test_atom head body ''
@@ -96,7 +96,7 @@ test_atom tag parent ''
 test_atom tag numparent ''
 test_atom tag object $(git rev-parse refs/tags/testtag^0)
 test_atom tag type 'commit'
-test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
+test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463'
 test_atom tag '*objecttype' 'commit'
 test_atom tag author ''
 test_atom tag authorname ''
@@ -107,18 +107,18 @@ test_atom tag committername ''
 test_atom tag committeremail ''
 test_atom tag committerdate ''
 test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151939925 +0200'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag taggername 'C O Mitter'
 test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
-test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151939927'
-test_atom tag contents:subject 'Tagging at 1151939927'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
 test_atom tag body ''
 test_atom tag contents:body ''
 test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151939927
+test_atom tag contents 'Tagging at 1151968727
 '
 test_atom tag HEAD ' '
 
@@ -146,95 +146,123 @@ test_expect_success 'Check invalid format specifiers are errors' '
        test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200'
-'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200'
-EOF
+test_date () {
+       f=$1 &&
+       committer_date=$2 &&
+       author_date=$3 &&
+       tagger_date=$4 &&
+       cat >expected <<-EOF &&
+       'refs/heads/master' '$committer_date' '$author_date'
+       'refs/tags/testtag' '$tagger_date'
+       EOF
+       (
+               git for-each-ref --shell \
+                       --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+                       refs/heads &&
+               git for-each-ref --shell \
+                       --format="%(refname) %(taggerdate${f:+:$f})" \
+                       refs/tags
+       ) >actual &&
+       test_cmp expected actual
+}
 
 test_expect_success 'Check unformatted date fields output' '
-       (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date "" \
+               "Tue Jul 4 01:18:43 2006 +0200" \
+               "Tue Jul 4 01:18:44 2006 +0200" \
+               "Tue Jul 4 01:18:45 2006 +0200"
 '
 
 test_expect_success 'Check format "default" formatted date fields output' '
-       f=default &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date default \
+               "Tue Jul 4 01:18:43 2006 +0200" \
+               "Tue Jul 4 01:18:44 2006 +0200" \
+               "Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+       test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
 '
 
 # Don't know how to do relative check because I can't know when this script
 # is going to be run and can't fake the current time to git, and hence can't
 # provide expected output.  Instead, I'll just make sure that "relative"
 # doesn't exit in error
-#
-#cat >expected <<\EOF
-#
-#EOF
-#
 test_expect_success 'Check format "relative" date fields output' '
        f=relative &&
        (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
        git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
 '
 
-cat >expected <<\EOF
-'refs/heads/master' '2006-07-03' '2006-07-03'
-'refs/tags/testtag' '2006-07-03'
-EOF
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+       test_date relative-local \
+               "$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
+               "$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
+               "$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
+'
 
 test_expect_success 'Check format "short" date fields output' '
-       f=short &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date short 2006-07-04 2006-07-04 2006-07-04
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006'
-'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006'
-EOF
+test_expect_success 'Check format "short-local" date fields output' '
+       test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
 
 test_expect_success 'Check format "local" date fields output' '
-       f=local &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date local \
+               "Mon Jul 3 23:18:43 2006" \
+               "Mon Jul 3 23:18:44 2006" \
+               "Mon Jul 3 23:18:45 2006"
 '
 
-cat >expected <<\EOF
-'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200'
-'refs/tags/testtag' '2006-07-03 17:18:45 +0200'
-EOF
-
 test_expect_success 'Check format "iso8601" date fields output' '
-       f=iso8601 &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date iso8601 \
+               "2006-07-04 01:18:43 +0200" \
+               "2006-07-04 01:18:44 +0200" \
+               "2006-07-04 01:18:45 +0200"
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200'
-'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200'
-EOF
+test_expect_success 'Check format "iso8601-local" date fields output' '
+       test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
 
 test_expect_success 'Check format "rfc2822" date fields output' '
-       f=rfc2822 &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date rfc2822 \
+               "Tue, 4 Jul 2006 01:18:43 +0200" \
+               "Tue, 4 Jul 2006 01:18:44 +0200" \
+               "Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+       test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+       test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+       test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
 '
 
 test_expect_success 'Check format of strftime date fields' '
-       echo "my date is 2006-07-03" >expected &&
+       echo "my date is 2006-07-04" >expected &&
        git for-each-ref \
          --format="%(authordate:format:my date is %Y-%m-%d)" \
          refs/heads >actual &&
        test_cmp expected actual
 '
 
+test_expect_success 'Check format of strftime-local date fields' '
+       echo "my date is 2006-07-03" >expected &&
+       git for-each-ref \
+         --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+         refs/heads >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'exercise strftime with odd fields' '
        echo >expected &&
        git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
@@ -546,8 +574,8 @@ body contents
 $sig"
 
 cat >expected <<EOF
-$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 $(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
+$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 EOF
 
 test_expect_success 'Verify sort with multiple keys' '
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
new file mode 100755 (executable)
index 0000000..fe4796c
--- /dev/null
@@ -0,0 +1,258 @@
+#!/bin/sh
+
+test_description='test for-each-refs usage of ref-filter APIs'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
+
+if ! test_have_prereq GPG
+then
+       skip_all="skipping for-each-ref tests, GPG not available"
+       test_done
+fi
+
+test_expect_success 'setup some history and refs' '
+       test_commit one &&
+       test_commit two &&
+       test_commit three &&
+       git checkout -b side &&
+       test_commit four &&
+       git tag -s -m "A signed tag message" signed-tag &&
+       git tag -s -m "Annonated doubly" double-tag signed-tag &&
+       git checkout master &&
+       git update-ref refs/odd/spot master
+'
+
+test_expect_success 'filtering with --points-at' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/three
+       EOF
+       git for-each-ref --format="%(refname)" --points-at=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check signed tags with --points-at' '
+       sed -e "s/Z$//" >expect <<-\EOF &&
+       refs/heads/side Z
+       refs/tags/four Z
+       refs/tags/signed-tag four
+       EOF
+       git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/one
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --no-merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/side
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       EOF
+       git for-each-ref --format="%(refname)" --no-merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --contains' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/heads/side
+       refs/odd/spot
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --contains=two >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(color) must fail' '
+       test_must_fail git for-each-ref --format="%(color)%(refname)"
+'
+
+test_expect_success 'left alignment is default' '
+       cat >expect <<-\EOF &&
+       refname is refs/heads/master  |refs/heads/master
+       refname is refs/heads/side    |refs/heads/side
+       refname is refs/odd/spot      |refs/odd/spot
+       refname is refs/tags/double-tag|refs/tags/double-tag
+       refname is refs/tags/four     |refs/tags/four
+       refname is refs/tags/one      |refs/tags/one
+       refname is refs/tags/signed-tag|refs/tags/signed-tag
+       refname is refs/tags/three    |refs/tags/three
+       refname is refs/tags/two      |refs/tags/two
+       EOF
+       git for-each-ref --format="%(align:30)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'middle alignment' '
+       cat >expect <<-\EOF &&
+       | refname is refs/heads/master |refs/heads/master
+       |  refname is refs/heads/side  |refs/heads/side
+       |   refname is refs/odd/spot   |refs/odd/spot
+       |refname is refs/tags/double-tag|refs/tags/double-tag
+       |  refname is refs/tags/four   |refs/tags/four
+       |   refname is refs/tags/one   |refs/tags/one
+       |refname is refs/tags/signed-tag|refs/tags/signed-tag
+       |  refname is refs/tags/three  |refs/tags/three
+       |   refname is refs/tags/two   |refs/tags/two
+       EOF
+       git for-each-ref --format="|%(align:middle,30)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'right alignment' '
+       cat >expect <<-\EOF &&
+       |  refname is refs/heads/master|refs/heads/master
+       |    refname is refs/heads/side|refs/heads/side
+       |      refname is refs/odd/spot|refs/odd/spot
+       |refname is refs/tags/double-tag|refs/tags/double-tag
+       |     refname is refs/tags/four|refs/tags/four
+       |      refname is refs/tags/one|refs/tags/one
+       |refname is refs/tags/signed-tag|refs/tags/signed-tag
+       |    refname is refs/tags/three|refs/tags/three
+       |      refname is refs/tags/two|refs/tags/two
+       EOF
+       git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+# Individual atoms inside %(align:...) and %(end) must not be quoted.
+
+test_expect_success 'alignment with format quote' "
+       cat >expect <<-\EOF &&
+       |'      '\''master| A U Thor'\''      '|
+       |'       '\''side| A U Thor'\''       '|
+       |'     '\''odd/spot| A U Thor'\''     '|
+       |'        '\''double-tag| '\''        '|
+       |'       '\''four| A U Thor'\''       '|
+       |'       '\''one| A U Thor'\''        '|
+       |'        '\''signed-tag| '\''        '|
+       |'      '\''three| A U Thor'\''       '|
+       |'       '\''two| A U Thor'\''        '|
+       EOF
+       git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual &&
+       test_cmp expect actual
+"
+
+test_expect_success 'nested alignment with quote formatting' "
+       cat >expect <<-\EOF &&
+       |'         master               '|
+       |'           side               '|
+       |'       odd/spot               '|
+       |'     double-tag               '|
+       |'           four               '|
+       |'            one               '|
+       |'     signed-tag               '|
+       |'          three               '|
+       |'            two               '|
+       EOF
+       git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&
+       test_cmp expect actual
+"
+
+test_expect_success 'check `%(contents:lines=1)`' '
+       cat >expect <<-\EOF &&
+       master |three
+       side |four
+       odd/spot |three
+       double-tag |Annonated doubly
+       four |four
+       one |one
+       signed-tag |A signed tag message
+       three |three
+       two |two
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=1)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check `%(contents:lines=0)`' '
+       cat >expect <<-\EOF &&
+       master |
+       side |
+       odd/spot |
+       double-tag |
+       four |
+       one |
+       signed-tag |
+       three |
+       two |
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check `%(contents:lines=99999)`' '
+       cat >expect <<-\EOF &&
+       master |three
+       side |four
+       odd/spot |three
+       double-tag |Annonated doubly
+       four |four
+       one |one
+       signed-tag |A signed tag message
+       three |three
+       two |two
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=99999)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '`%(contents:lines=-1)` should fail' '
+       test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)"
+'
+
+test_expect_success 'setup for version sort' '
+       test_commit foo1.3 &&
+       test_commit foo1.6 &&
+       test_commit foo1.10
+'
+
+test_expect_success 'version sort' '
+       git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.3
+       foo1.6
+       foo1.10
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'version sort (shortened)' '
+       git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.3
+       foo1.6
+       foo1.10
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'reverse version sort' '
+       git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.10
+       foo1.6
+       foo1.3
+       EOF
+       test_cmp expect actual
+'
+
+test_done
index d31788cc6ce6a5698c34ffe6ee8a59fccacab90c..3dd2f51e49d7e6824382ac415cf64842fa3b757f 100755 (executable)
@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '
 
 test_expect_success 'invalid sort parameter in configuratoin' '
        git config tag.sort "v:notvalid" &&
-       git tag -l "foo*" >actual &&
-       cat >expect <<-\EOF &&
-       foo1.10
-       foo1.3
-       foo1.6
-       EOF
-       test_cmp expect actual
+       test_must_fail git tag -l "foo*"
 '
 
 test_expect_success 'version sort with prerelease reordering' '
@@ -1525,4 +1519,43 @@ EOF"
        test_cmp expect actual
 '
 
+test_expect_success '--format should list tags as per format given' '
+       cat >expect <<-\EOF &&
+       refname : refs/tags/foo1.10
+       refname : refs/tags/foo1.3
+       refname : refs/tags/foo1.6
+       refname : refs/tags/foo1.6-rc1
+       refname : refs/tags/foo1.6-rc2
+       EOF
+       git tag -l --format="refname : %(refname)" "foo*" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup --merged test tags' '
+       git tag mergetest-1 HEAD~2 &&
+       git tag mergetest-2 HEAD~1 &&
+       git tag mergetest-3 HEAD
+'
+
+test_expect_success '--merged cannot be used in non-list mode' '
+       test_must_fail git tag --merged=mergetest-2 foo
+'
+
+test_expect_success '--merged shows merged tags' '
+       cat >expect <<-\EOF &&
+       mergetest-1
+       mergetest-2
+       EOF
+       git tag -l --merged=mergetest-2 mergetest-* >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--no-merged show unmerged tags' '
+       cat >expect <<-\EOF &&
+       mergetest-3
+       EOF
+       git tag -l --no-merged=mergetest-2 mergetest-* >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 7eeb207b32b48041037488f42e645b8b12742690..6f12b235b3a9330497e814d5e8f564aec94d0c44 100755 (executable)
@@ -174,9 +174,9 @@ test_expect_success 'mergetool skips autoresolved' '
 '
 
 test_expect_success 'mergetool merges all from subdir' '
+       test_config rerere.enabled false &&
        (
                cd subdir &&
-               test_config rerere.enabled false &&
                test_must_fail git merge master &&
                ( yes "r" | git mergetool ../submod ) &&
                ( yes "d" "d" | git mergetool --no-prompt ) &&
index ea35a0241c201ac12c29603a21421199dd097600..48c6e2bc830e74ab8d242e49c08f38609b5ef585 100755 (executable)
@@ -492,12 +492,12 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
 
 test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
        git submodule add ./. submod/ule &&
+       test_config -C submod/ule diff.tool checktrees &&
+       test_config -C submod/ule difftool.checktrees.cmd '\''
+               test -d "$LOCAL" && test -d "$REMOTE" && echo good
+               '\'' &&
        (
                cd submod/ule &&
-               test_config diff.tool checktrees &&
-               test_config difftool.checktrees.cmd '\''
-                       test -d "$LOCAL" && test -d "$REMOTE" && echo good
-               '\'' &&
                echo good >expect &&
                git difftool --tool=checktrees --dir-diff HEAD~ >actual &&
                test_cmp expect actual
index 095238fffe2757a5bf1c5c2f877791117fbf09a3..decb66ba30871573eb9c252e56599d47095f0fff 100755 (executable)
@@ -214,6 +214,51 @@ test_expect_success 'use git config to enable import/export of tags' '
        )
 '
 
+p4_head_revision() {
+       p4 changes -m 1 "$@" | awk '{print $2}'
+}
+
+# Importing a label that references a P4 commit that
+# has not been seen. The presence of a label on a commit
+# we haven't seen should not cause git-p4 to fail. It should
+# merely skip that label, and still import other labels.
+test_expect_success 'importing labels with missing revisions' '
+       test_when_finished cleanup_git &&
+       (
+               rm -fr "$cli" "$git" &&
+               mkdir "$cli" &&
+               P4CLIENT=missing-revision &&
+               client_view "//depot/missing-revision/... //missing-revision/..." &&
+               cd "$cli" &&
+               >f1 && p4 add f1 && p4 submit -d "start" &&
+
+               p4 tag -l TAG_S0 ... &&
+
+               >f2 && p4 add f2 && p4 submit -d "second" &&
+
+               startrev=$(p4_head_revision //depot/missing-revision/...) &&
+
+               >f3 && p4 add f3 && p4 submit -d "third" &&
+
+               p4 edit f2 && date >f2 && p4 submit -d "change" f2 &&
+
+               endrev=$(p4_head_revision //depot/missing-revision/...) &&
+
+               p4 tag -l TAG_S1 ... &&
+
+               # we should skip TAG_S0 since it is before our startpoint,
+               # but pick up TAG_S1.
+
+               git p4 clone --dest="$git" --import-labels -v \
+                       //depot/missing-revision/...@$startrev,$endrev &&
+               (
+                       cd "$git" &&
+                       git rev-parse TAG_S1 &&
+                       ! git rev-parse TAG_S0
+               )
+       )
+'
+
 
 test_expect_success 'kill p4d' '
        kill_p4d
index e8d3c0fdbc76d93ea18f6da1c869fc963e7ca81d..6dffb8bcde83b82fa40b05dd8ee815f4e6a3cdd3 100644 (file)
@@ -201,7 +201,14 @@ test_chmod () {
 
 # Unset a configuration variable, but don't fail if it doesn't exist.
 test_unconfig () {
-       git config --unset-all "$@"
+       config_dir=
+       if test "$1" = -C
+       then
+               shift
+               config_dir=$1
+               shift
+       fi
+       git ${config_dir:+-C "$config_dir"} config --unset-all "$@"
        config_status=$?
        case "$config_status" in
        5) # ok, nothing to unset
@@ -213,8 +220,15 @@ test_unconfig () {
 
 # Set git config, automatically unsetting it after the test is over.
 test_config () {
-       test_when_finished "test_unconfig '$1'" &&
-       git config "$@"
+       config_dir=
+       if test "$1" = -C
+       then
+               shift
+               config_dir=$1
+               shift
+       fi
+       test_when_finished "test_unconfig ${config_dir:+-C '$config_dir'} '$1'" &&
+       git ${config_dir:+-C "$config_dir"} config "$@"
 }
 
 test_config_global () {
@@ -722,6 +736,11 @@ test_seq () {
 # what went wrong.
 
 test_when_finished () {
+       # We cannot detect when we are in a subshell in general, but by
+       # doing so on Bash is better than nothing (the test will
+       # silently pass on other shells).
+       test "${BASH_SUBSHELL-0}" = 0 ||
+       error "bug in test script: test_when_finished does nothing in a subshell"
        test_cleanup="{ $*
                } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
 }
diff --git a/utf8.c b/utf8.c
index 28e6d76a425db4c16a8cc32e6f17c7aa72c2b1f0..00e10c86ad7ea7bcc2d53a90a2f608dd66d47462 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -644,3 +644,24 @@ int skip_utf8_bom(char **text, size_t len)
        *text += strlen(utf8_bom);
        return 1;
 }
+
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+                      const char *s)
+{
+       int slen = strlen(s);
+       int display_len = utf8_strnwidth(s, slen, 0);
+       int utf8_compensation = slen - display_len;
+
+       if (display_len >= width) {
+               strbuf_addstr(buf, s);
+               return;
+       }
+
+       if (position == ALIGN_LEFT)
+               strbuf_addf(buf, "%-*s", width + utf8_compensation, s);
+       else if (position == ALIGN_MIDDLE) {
+               int left = (width - display_len) / 2;
+               strbuf_addf(buf, "%*s%-*s", left, "", width - left + utf8_compensation, s);
+       } else if (position == ALIGN_RIGHT)
+               strbuf_addf(buf, "%*s", width + utf8_compensation, s);
+}
diff --git a/utf8.h b/utf8.h
index 5a9e94bee62fd2ed62dcbc82aee12bfc6d75e254..7930b44f19c701cc671d9639289782abb1812034 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -55,4 +55,19 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
  */
 int is_hfs_dotgit(const char *path);
 
+typedef enum {
+       ALIGN_LEFT,
+       ALIGN_MIDDLE,
+       ALIGN_RIGHT
+} align_type;
+
+/*
+ * Align the string given and store it into a strbuf as per the
+ * 'position' and 'width'. If the given string length is larger than
+ * 'width' than then the input string is not truncated and no
+ * alignment is done.
+ */
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+                      const char *s);
+
 #endif