Merge branch 'jt/avoid-ls-refs-with-http'
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 Sep 2019 18:50:10 +0000 (11:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 Sep 2019 18:50:10 +0000 (11:50 -0700)
The http transport lacked some optimization the native transports
learned to avoid unnecessary ref advertisement, which has been
corrected.

* jt/avoid-ls-refs-with-http:
transport: teach all vtables to allow fetch first
transport-helper: skip ls-refs if unnecessary

143 files changed:
Documentation/RelNotes/2.24.0.txt
Documentation/config.txt
Documentation/config/core.txt
Documentation/config/diff.txt
Documentation/config/feature.txt [new file with mode: 0644]
Documentation/config/fetch.txt
Documentation/config/gc.txt
Documentation/config/index.txt
Documentation/config/pack.txt
Documentation/config/remote.txt
Documentation/fetch-options.txt
Documentation/git-merge.txt
Documentation/git-rebase.txt
Documentation/gitattributes.txt
Documentation/gitcli.txt
Documentation/githooks.txt
Documentation/gitrepository-layout.txt
Documentation/merge-options.txt
Documentation/rev-list-options.txt
Documentation/technical/partial-clone.txt
Makefile
apply.c
archive-tar.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/fetch.c
builtin/gc.c
builtin/index-pack.c
builtin/merge.c
builtin/pack-objects.c
builtin/pull.c
builtin/rebase.c
builtin/repack.c
builtin/rev-list.c
builtin/update-index.c
cache-tree.c
cache.h
commit-graph.c
common-main.c
config.c
connected.c
contrib/completion/git-completion.bash
convert.c
convert.h
credential-store.c
diff.c
environment.c
fetch-negotiator.c
fetch-negotiator.h
fetch-object.c [deleted file]
fetch-object.h [deleted file]
fetch-pack.c
git-compat-util.h
git-gui/git-gui.sh
git-gui/lib/checkout_op.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/index.tcl
gitk-git/gitk
gitk-git/po/zh_cn.po [new file with mode: 0644]
http.c
http.h
line-log.c
list-objects-filter-options.c
list-objects-filter-options.h
list-objects-filter.c
list-objects-filter.h
list-objects.c
ll-merge.c
ll-merge.h
packfile.c
packfile.h
parse-options.c
parse-options.h
path.c
path.h
promisor-remote.c [new file with mode: 0644]
promisor-remote.h [new file with mode: 0644]
read-cache.c
ref-filter.c
repo-settings.c [new file with mode: 0644]
repository.h
revision.c
sequencer.c
sequencer.h
setup.c
sha1-file.c
sha1-name.c
strbuf.c
strbuf.h
t/lib-rebase.sh
t/t0021-conversion.sh
t/t0040-parse-options.sh
t/t0410-partial-clone.sh
t/t1309-early-config.sh
t/t1600-index.sh
t/t3400-rebase.sh
t/t3418-rebase-continue.sh
t/t3422-rebase-incompatible-options.sh
t/t3427-rebase-subtree.sh
t/t3430-rebase-merges.sh
t/t4018-diff-funcname.sh
t/t4018/dts-labels [new file with mode: 0644]
t/t4018/dts-node-unitless [new file with mode: 0644]
t/t4018/dts-nodes [new file with mode: 0644]
t/t4018/dts-nodes-comment1 [new file with mode: 0644]
t/t4018/dts-nodes-comment2 [new file with mode: 0644]
t/t4018/dts-reference [new file with mode: 0644]
t/t4018/dts-root [new file with mode: 0644]
t/t4034-diff-words.sh
t/t4034/dts/expect [new file with mode: 0644]
t/t4034/dts/post [new file with mode: 0644]
t/t4034/dts/pre [new file with mode: 0644]
t/t4067-diff-partial-clone.sh
t/t4150-am.sh
t/t4202-log.sh
t/t4211-line-log.sh
t/t5004-archive-corner-cases.sh
t/t5307-pack-missing-commit.sh
t/t5324-split-commit-graph.sh
t/t5552-skipping-fetch-negotiator.sh
t/t5553-set-upstream.sh [new file with mode: 0755]
t/t5601-clone.sh
t/t5616-partial-clone.sh
t/t6000-rev-list-misc.sh
t/t6011-rev-list-with-bad-commit.sh
t/t6112-rev-list-filters-objects.sh
t/t6300-for-each-ref.sh
t/t6501-freshen-objects.sh
t/t7503-pre-commit-and-pre-merge-commit-hooks.sh [new file with mode: 0755]
t/t7503-pre-commit-hook.sh [deleted file]
t/t9902-completion.sh
templates/hooks--pre-merge-commit.sample [new file with mode: 0755]
trace.c
transport-helper.c
transport.c
unpack-trees.c
upload-pack.c
url.c
url.h
userdiff.c
wrapper.c
index a95a8b0084f8d3ec04e168b9e994216da494b5b3..e123d2daa650bd2cdc474700889e5625a7ed7831 100644 (file)
@@ -11,7 +11,22 @@ Backward compatibility note
 
 UI, Workflows & Features
 
- * (no entry yet so far)
+ * The command line parser learned "--end-of-options" notation; the
+   standard convention for scripters to have hardcoded set of options
+   first on the command line, and force the command to treat end-user
+   input as non-options, has been to use "--" as the delimiter, but
+   that would not work for commands that use "--" as a delimiter
+   between revs and pathspec.
+
+ * A mechanism to affect the default setting for a (related) group of
+   configuration variables is introduced.
+
+ * "git fetch" learned "--set-upstream" option to help those who first
+   clone from their private fork they intend to push to, add the true
+   upstream via "git remote add" and then "git fetch" from it.
+
+ * Device-tree files learned their own userdiff patterns.
+   (merge 3c81760bc6 sb/userdiff-dts later to maint).
 
 
 Performance, Internal Implementation, Development Support etc.
@@ -22,6 +37,8 @@ Performance, Internal Implementation, Development Support etc.
  * The first line of verbose output from each test piece now carries
    the test name and number to help scanning with eyeballs.
 
+ * Further clean-up of the initialization code.
+
 
 Fixes since v2.23
 -----------------
@@ -48,5 +65,43 @@ Fixes since v2.23
  * Compilation fix.
    (merge 70597e8386 rs/nedalloc-fixlets later to maint).
 
+ * "git gui" learned to call the clean-up procedure before exiting.
+   (merge 0d88f3d2c5 py/git-gui-do-quit later to maint).
+
+ * We promoted the "indent heuristics" that decides where to split
+   diff hunks from experimental to the default a few years ago, but
+   some stale documentation still marked it as experimental, which has
+   been corrected.
+   (merge 64e5e1fba1 sg/diff-indent-heuristic-non-experimental later to maint).
+
+ * Fix a mismerge that happened in 2.22 timeframe.
+   (merge acb7da05ac en/checkout-mismerge-fix later to maint).
+
+ * "git archive" recorded incorrect length in extended pax header in
+   some corner cases, which has been corrected.
+   (merge 71d41ff651 rs/pax-extended-header-length-fix later to maint).
+
+ * On-demand object fetching in lazy clone incorrectly tried to fetch
+   commits from submodule projects, while still working in the
+   superproject, which has been corrected.
+   (merge a63694f523 jt/diff-lazy-fetch-submodule-fix later to maint).
+
+ * Prepare get_short_oid() codepath to be thread-safe.
+   (merge 7cfcb16b0e rs/sort-oid-array-thread-safe later to maint).
+
+ * "for-each-ref" and friends that show refs did not protect themselves
+   against ancient tags that did not record tagger names when asked to
+   show "%(taggername)", which have been corrected.
+   (merge 8b3f33ef11 mp/for-each-ref-missing-name-or-email later to maint).
+
+ * The "git am" based backend of "git rebase" ignored the result of
+   updating ".gitattributes" done in one step when replaying
+   subsequent steps.
+   (merge 2c65d90f75 bc/reread-attributes-during-rebase later to maint).
+
  * Other code cleanup, docfix, build fix, etc.
    (merge d1387d3895 en/fast-import-merge-doc later to maint).
+   (merge 1c24a54ea4 bm/repository-layout-typofix later to maint).
+   (merge 415b770b88 ds/midx-expire-repack later to maint).
+   (merge 19800bdc3f nd/diff-parseopt later to maint).
+   (merge 58166c2e9d tg/t0021-racefix later to maint).
index e3f5bc3396d0c7502f16eed989220c8e2010bcc1..77f3b1486b78ab81e1248e1a6cfb651c9e17fd85 100644 (file)
@@ -345,6 +345,8 @@ include::config/difftool.txt[]
 
 include::config/fastimport.txt[]
 
+include::config/feature.txt[]
+
 include::config/fetch.txt[]
 
 include::config/format.txt[]
index 75538d27e7e06b2041f556dc5e3fcd4aef1abf20..852d2ba37a1204e0e210e9abf671d7f0c9c850d8 100644 (file)
@@ -86,7 +86,9 @@ core.untrackedCache::
        it will automatically be removed, if set to `false`. Before
        setting it to `true`, you should check that mtime is working
        properly on your system.
-       See linkgit:git-update-index[1]. `keep` by default.
+       See linkgit:git-update-index[1]. `keep` by default, unless
+       `feature.manyFiles` is enabled which sets this setting to
+       `true` by default.
 
 core.checkStat::
        When missing or is set to `default`, many fields in the stat
@@ -577,7 +579,7 @@ the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
 
 core.commitGraph::
        If true, then git will read the commit-graph file (if it exists)
-       to parse the graph structure of commits. Defaults to false. See
+       to parse the graph structure of commits. Defaults to true. See
        linkgit:git-commit-graph[1] for more information.
 
 core.useReplaceRefs::
index 5afb5a2cbc69b763263e3e265343884c0ff64dda..ff09f1cf737c062898ac40555402de097d360eb9 100644 (file)
@@ -189,7 +189,7 @@ diff.guitool::
 include::../mergetools-diff.txt[]
 
 diff.indentHeuristic::
-       Set this option to `true` to enable experimental heuristics
+       Set this option to `false` to disable the default heuristics
        that shift diff hunk boundaries to make patches easier to read.
 
 diff.algorithm::
diff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt
new file mode 100644 (file)
index 0000000..545522f
--- /dev/null
@@ -0,0 +1,29 @@
+feature.*::
+       The config settings that start with `feature.` modify the defaults of
+       a group of other config settings. These groups are created by the Git
+       developer community as recommended defaults and are subject to change.
+       In particular, new config options may be added with different defaults.
+
+feature.experimental::
+       Enable config options that are new to Git, and are being considered for
+       future defaults. Config settings included here may be added or removed
+       with each release, including minor version updates. These settings may
+       have unintended interactions since they are so new. Please enable this
+       setting if you are interested in providing feedback on experimental
+       features. The new default values are:
++
+* `pack.useSparse=true` uses a new algorithm when constructing a pack-file
+which can improve `git push` performance in repos with many files.
++
+* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
+skipping more commits at a time, reducing the number of round trips.
+
+feature.manyFiles::
+       Enable config options that optimize for repos with many files in the
+       working directory. With many files, commands such as `git status` and
+       `git checkout` may be slow and these new defaults improve performance:
++
+* `index.version=4` enables path-prefix compression in the index.
++
+* `core.untrackedCache=true` enables the untracked cache. This setting assumes
+that mtime is working on your machine.
index ba890b5884fc3496e6529cb8e1f41ad7dd9748f4..d402110638b4e465075bf1a59885563d15f190e1 100644 (file)
@@ -59,7 +59,8 @@ fetch.negotiationAlgorithm::
        effort to converge faster, but may result in a larger-than-necessary
        packfile; The default is "default" which instructs Git to use the default algorithm
        that never skips commits (unless the server has acknowledged it or one
-       of its descendants).
+       of its descendants). If `feature.experimental` is enabled, then this
+       setting defaults to "skipping".
        Unknown values will cause 'git fetch' to error out.
 +
 See also the `--negotiation-tip` option for linkgit:git-fetch[1].
index 02b92b18b5c2cf6f9d509483a4bb995f3677d519..00ea0a678ee214f86cc91b27389572698d35bece 100644 (file)
@@ -63,7 +63,7 @@ gc.writeCommitGraph::
        If true, then gc will rewrite the commit-graph file when
        linkgit:git-gc[1] is run. When using `git gc --auto`
        the commit-graph will be updated if housekeeping is
-       required. Default is false. See linkgit:git-commit-graph[1]
+       required. Default is true. See linkgit:git-commit-graph[1]
        for details.
 
 gc.logExpiry::
index f18150304106891ad388a7620594937b43e7615e..7cb50b37e98dba813886adb4578435495be8e78c 100644 (file)
@@ -24,3 +24,4 @@ index.threads::
 index.version::
        Specify the version with which new index files should be
        initialized.  This does not affect existing repositories.
+       If `feature.manyFiles` is enabled, then the default is 4.
index 9cdcfa7324784299f431d94b5237cc136aa585d1..1d66f0c992c38237dfa7def97d9b62c9803c7fc8 100644 (file)
@@ -112,7 +112,8 @@ pack.useSparse::
        objects. This can have significant performance benefits when
        computing a pack to send a small change. However, it is possible
        that extra objects are added to the pack-file if the included
-       commits contain certain types of direct renames.
+       commits contain certain types of direct renames. Default is `false`
+       unless `feature.experimental` is enabled.
 
 pack.writeBitmaps (deprecated)::
        This is a deprecated synonym for `repack.writeBitmaps`.
index 6c4cad83a2c9f41206e6c1032573d82fa7f3090c..a8e6437a903592934198fb212ede8241d6acebca 100644 (file)
@@ -76,3 +76,11 @@ remote.<name>.pruneTags::
 +
 See also `remote.<name>.prune` and the PRUNING section of
 linkgit:git-fetch[1].
+
+remote.<name>.promisor::
+       When set to true, this remote will be used to fetch promisor
+       objects.
+
+remote.<name>.partialclonefilter::
+       The filter that will be applied when fetching from this
+       promisor remote.
index 3c9b4f9e09515d99d32a3d6dfa6603ef3a6f23b6..99df1f3d4e3773bf78c4c3c128f91b33fc084d2f 100644 (file)
@@ -169,6 +169,13 @@ ifndef::git-pull[]
        Disable recursive fetching of submodules (this has the same effect as
        using the `--recurse-submodules=no` option).
 
+--set-upstream::
+       If the remote is fetched successfully, pull and add upstream
+       (tracking) reference, used by argument-less
+       linkgit:git-pull[1] and other commands. For more information,
+       see `branch.<name>.merge` and `branch.<name>.remote` in
+       linkgit:git-config[1].
+
 --submodule-prefix=<path>::
        Prepend <path> to paths printed in informative messages
        such as "Fetching submodule foo".  This option is used
index 01fd52dc7063802226bf4f7205a2a1aab697bc67..092529c619e29cfa1c0959358d4671ff1cba1144 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
-       [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
+       [--no-verify] [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
        [--[no-]allow-unrelated-histories]
        [--[no-]rerere-autoupdate] [-m <msg>] [-F <file>] [<commit>...]
 'git merge' (--continue | --abort | --quit)
index 6156609cf7149ccf5c1f79df2d9807cdbcc609ba..3136c19fb4ce578ee37c4e3b9b86ab2e47294dc8 100644 (file)
@@ -543,8 +543,6 @@ In addition, the following pairs of options are incompatible:
  * --preserve-merges and --interactive
  * --preserve-merges and --signoff
  * --preserve-merges and --rebase-merges
- * --rebase-merges and --strategy
- * --rebase-merges and --strategy-option
 
 BEHAVIORAL DIFFERENCES
 -----------------------
index fb1d188d440cc2fd3579844045d01a77423a58c7..c5a528c667b62abe81e6454b061834200011657e 100644 (file)
@@ -810,6 +810,8 @@ patterns are available:
 
 - `css` suitable for cascading style sheets.
 
+- `dts` suitable for devicetree (DTS) files.
+
 - `fortran` suitable for source code in the Fortran language.
 
 - `fountain` suitable for Fountain documents.
index 1ed3ca33b7a94ad8187d7fa490cc212711fd578e..4b32876b6e912b528ee0e00399719576a12b1990 100644 (file)
@@ -37,6 +37,12 @@ arguments.  Here are the rules:
    file called HEAD in your work tree, `git diff HEAD` is ambiguous, and
    you have to say either `git diff HEAD --` or `git diff -- HEAD` to
    disambiguate.
+
+ * Because `--` disambiguates revisions and paths in some commands, it
+   cannot be used for those commands to separate options and revisions.
+   You can use `--end-of-options` for this (it also works for commands
+   that do not distinguish between revisions in paths, in which case it
+   is simply an alias for `--`).
 +
 When writing a script that is expected to handle random user-input, it is
 a good practice to make it explicit which arguments are which by placing
index 82cd573776cec696774c467979c6da10f23554c3..57d6e2b98da7a093eff4ac0987824e34f6858677 100644 (file)
@@ -103,6 +103,28 @@ The default 'pre-commit' hook, when enabled--and with the
 `hooks.allownonascii` config option unset or set to false--prevents
 the use of non-ASCII filenames.
 
+pre-merge-commit
+~~~~~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-merge[1], and can be bypassed
+with the `--no-verify` option.  It takes no parameters, and is
+invoked after the merge has been carried out successfully and before
+obtaining the proposed commit log message to
+make a commit.  Exiting with a non-zero status from this script
+causes the `git merge` command to abort before creating a commit.
+
+The default 'pre-merge-commit' hook, when enabled, runs the
+'pre-commit' hook, if the latter is enabled.
+
+This hook is invoked with the environment variable
+`GIT_EDITOR=:` if the command will not bring up an editor
+to modify the commit message.
+
+If the merge cannot be carried out automatically, the conflicts
+need to be resolved and the result committed separately (see
+linkgit:git-merge[1]). At that point, this hook will not be executed,
+but the 'pre-commit' hook will, if it is enabled.
+
 prepare-commit-msg
 ~~~~~~~~~~~~~~~~~~
 
index 216b11ee88f4e9c5b10278e037f3b1c51173bc6d..d6388f10bbeb45caf9ccb1cf945161b70be625dc 100644 (file)
@@ -59,7 +59,7 @@ objects/[0-9a-f][0-9a-f]::
        here are often called 'unpacked' (or 'loose') objects.
 
 objects/pack::
-       Packs (files that store many object in compressed form,
+       Packs (files that store many objects in compressed form,
        along with index files to allow them to be randomly
        accessed) are found in this directory.
 
index 79a00d2a4abd6f7191f2639185e199fa85f6289b..d6a9f4b96faaf2814b41a2ff79a49c9d4d899795 100644 (file)
@@ -105,6 +105,10 @@ option can be used to override --squash.
 +
 With --squash, --commit is not allowed, and will fail.
 
+--no-verify::
+       This option bypasses the pre-merge and commit-msg hooks.
+       See also linkgit:githooks[5].
+
 -s <strategy>::
 --strategy=<strategy>::
        Use the given merge strategy; can be supplied more than
index bb1251c0364dc71880f6e63db7c6116ed859b90f..90ff9e2bea2e2fd0caf4744ed4e5c5e59f4c1243 100644 (file)
@@ -756,6 +756,22 @@ explicitly-given commit or tree.
 Note that the form '--filter=sparse:path=<path>' that wants to read
 from an arbitrary path on the filesystem has been dropped for security
 reasons.
++
+Multiple '--filter=' flags can be specified to combine filters. Only
+objects which are accepted by every filter are included.
++
+The form '--filter=combine:<filter1>+<filter2>+...<filterN>' can also be
+used to combined several filters, but this is harder than just repeating
+the '--filter' flag and is usually not necessary. Filters are joined by
+'{plus}' and individual filters are %-encoded (i.e. URL-encoded).
+Besides the '{plus}' and '%' characters, the following characters are
+reserved and also must be encoded: `~!@#$^&*()[]{}\;",<>?`+&#39;&#96;+
+as well as all characters with ASCII code &lt;= `0x20`, which includes
+space and newline.
++
+Other arbitrary characters can also be encoded. For instance,
+'combine:tree:3+blob:none' and 'combine:tree%3A3+blob%3Anone' are
+equivalent.
 
 --no-filter::
        Turn off any previous `--filter=` argument.
index 896c7b3878869de8d3564b42abcef9d4ea5dedec..210373e258890d7204c6dcf4a1befbd31e21139b 100644 (file)
@@ -30,12 +30,20 @@ advance* during clone and fetch operations and thereby reduce download
 times and disk usage.  Missing objects can later be "demand fetched"
 if/when needed.
 
+A remote that can later provide the missing objects is called a
+promisor remote, as it promises to send the objects when
+requested. Initialy Git supported only one promisor remote, the origin
+remote from which the user cloned and that was configured in the
+"extensions.partialClone" config option. Later support for more than
+one promisor remote has been implemented.
+
 Use of partial clone requires that the user be online and the origin
-remote be available for on-demand fetching of missing objects.  This may
-or may not be problematic for the user.  For example, if the user can
-stay within the pre-selected subset of the source tree, they may not
-encounter any missing objects.  Alternatively, the user could try to
-pre-fetch various objects if they know that they are going offline.
+remote or other promisor remotes be available for on-demand fetching
+of missing objects.  This may or may not be problematic for the user.
+For example, if the user can stay within the pre-selected subset of
+the source tree, they may not encounter any missing objects.
+Alternatively, the user could try to pre-fetch various objects if they
+know that they are going offline.
 
 
 Non-Goals
@@ -100,18 +108,18 @@ or commits that reference missing trees.
 Handling Missing Objects
 ------------------------
 
-- An object may be missing due to a partial clone or fetch, or missing due
-  to repository corruption.  To differentiate these cases, the local
-  repository specially indicates such filtered packfiles obtained from the
-  promisor remote as "promisor packfiles".
+- An object may be missing due to a partial clone or fetch, or missing
+  due to repository corruption.  To differentiate these cases, the
+  local repository specially indicates such filtered packfiles
+  obtained from promisor remotes as "promisor packfiles".
 +
 These promisor packfiles consist of a "<name>.promisor" file with
 arbitrary contents (like the "<name>.keep" files), in addition to
 their "<name>.pack" and "<name>.idx" files.
 
 - The local repository considers a "promisor object" to be an object that
-  it knows (to the best of its ability) that the promisor remote has promised
-  that it has, either because the local repository has that object in one of
+  it knows (to the best of its ability) that promisor remotes have promised
+  that they have, either because the local repository has that object in one of
   its promisor packfiles, or because another promisor object refers to it.
 +
 When Git encounters a missing object, Git can see if it is a promisor object
@@ -123,12 +131,12 @@ expensive-to-modify list of missing objects.[a]
 - Since almost all Git code currently expects any referenced object to be
   present locally and because we do not want to force every command to do
   a dry-run first, a fallback mechanism is added to allow Git to attempt
-  to dynamically fetch missing objects from the promisor remote.
+  to dynamically fetch missing objects from promisor remotes.
 +
 When the normal object lookup fails to find an object, Git invokes
-fetch-object to try to get the object from the server and then retry
-the object lookup.  This allows objects to be "faulted in" without
-complicated prediction algorithms.
+promisor_remote_get_direct() to try to get the object from a promisor
+remote and then retry the object lookup.  This allows objects to be
+"faulted in" without complicated prediction algorithms.
 +
 For efficiency reasons, no check as to whether the missing object is
 actually a promisor object is performed.
@@ -157,8 +165,7 @@ and prefetch those objects in bulk.
 +
 We are not happy with this global variable and would like to remove it,
 but that requires significant refactoring of the object code to pass an
-additional flag.  We hope that concurrent efforts to add an ODB API can
-encompass this.
+additional flag.
 
 
 Fetching Missing Objects
@@ -182,21 +189,63 @@ has been updated to not use any object flags when the corresponding argument
   though they are not necessary.
 
 
+Using many promisor remotes
+---------------------------
+
+Many promisor remotes can be configured and used.
+
+This allows for example a user to have multiple geographically-close
+cache servers for fetching missing blobs while continuing to do
+filtered `git-fetch` commands from the central server.
+
+When fetching objects, promisor remotes are tried one after the other
+until all the objects have been fetched.
+
+Remotes that are considered "promisor" remotes are those specified by
+the following configuration variables:
+
+- `extensions.partialClone = <name>`
+
+- `remote.<name>.promisor = true`
+
+- `remote.<name>.partialCloneFilter = ...`
+
+Only one promisor remote can be configured using the
+`extensions.partialClone` config variable. This promisor remote will
+be the last one tried when fetching objects.
+
+We decided to make it the last one we try, because it is likely that
+someone using many promisor remotes is doing so because the other
+promisor remotes are better for some reason (maybe they are closer or
+faster for some kind of objects) than the origin, and the origin is
+likely to be the remote specified by extensions.partialClone.
+
+This justification is not very strong, but one choice had to be made,
+and anyway the long term plan should be to make the order somehow
+fully configurable.
+
+For now though the other promisor remotes will be tried in the order
+they appear in the config file.
+
 Current Limitations
 -------------------
 
-- The remote used for a partial clone (or the first partial fetch
-  following a regular clone) is marked as the "promisor remote".
+- It is not possible to specify the order in which the promisor
+  remotes are tried in other ways than the order in which they appear
+  in the config file.
 +
-We are currently limited to a single promisor remote and only that
-remote may be used for subsequent partial fetches.
+It is also not possible to specify an order to be used when fetching
+from one remote and a different order when fetching from another
+remote.
+
+- It is not possible to push only specific objects to a promisor
+  remote.
 +
-We accept this limitation because we believe initial users of this
-feature will be using it on repositories with a strong single central
-server.
+It is not possible to push at the same time to multiple promisor
+remote in a specific order.
 
-- Dynamic object fetching will only ask the promisor remote for missing
-  objects.  We assume that the promisor remote has a complete view of the
+- Dynamic object fetching will only ask promisor remotes for missing
+  objects.  We assume that promisor remotes have a complete view of the
   repository and can satisfy all such requests.
 
 - Repack essentially treats promisor and non-promisor packfiles as 2
@@ -218,15 +267,17 @@ server.
 Future Work
 -----------
 
-- Allow more than one promisor remote and define a strategy for fetching
-  missing objects from specific promisor remotes or of iterating over the
-  set of promisor remotes until a missing object is found.
+- Improve the way to specify the order in which promisor remotes are
+  tried.
 +
-A user might want to have multiple geographically-close cache servers
-for fetching missing blobs while continuing to do filtered `git-fetch`
-commands from the central server, for example.
+For example this could allow to specify explicitly something like:
+"When fetching from this remote, I want to use these promisor remotes
+in this order, though, when pushing or fetching to that remote, I want
+to use those promisor remotes in that order."
+
+- Allow pushing to promisor remotes.
 +
-Or the user might want to work in a triangular work flow with multiple
+The user might want to work in a triangular work flow with multiple
 promisor remotes that each have an incomplete view of the repository.
 
 - Allow repack to work on promisor packfiles (while keeping them distinct
index f9255344ae5009a192ab83a332be749632d94531..f879697ea3b23f6ad6308afb33ae0f08ec262591 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -884,7 +884,6 @@ LIB_OBJS += ewah/ewah_io.o
 LIB_OBJS += ewah/ewah_rlw.o
 LIB_OBJS += exec-cmd.o
 LIB_OBJS += fetch-negotiator.o
-LIB_OBJS += fetch-object.o
 LIB_OBJS += fetch-pack.o
 LIB_OBJS += fsck.o
 LIB_OBJS += fsmonitor.o
@@ -948,6 +947,7 @@ LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
 LIB_OBJS += progress.o
+LIB_OBJS += promisor-remote.o
 LIB_OBJS += prompt.o
 LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
@@ -965,6 +965,7 @@ LIB_OBJS += refspec.o
 LIB_OBJS += ref-filter.o
 LIB_OBJS += remote.o
 LIB_OBJS += replace-object.o
+LIB_OBJS += repo-settings.o
 LIB_OBJS += repository.o
 LIB_OBJS += rerere.o
 LIB_OBJS += resolve-undo.o
diff --git a/apply.c b/apply.c
index cde95369bb3f3a9c763108fd91e1e48e0e455e29..57a61f2881bed383862044413210b42abca18434 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -4643,6 +4643,7 @@ static int apply_patch(struct apply_state *state,
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
        int res = 0;
+       int flush_attributes = 0;
 
        state->patch_input_file = filename;
        if (read_patch_file(&buf, fd) < 0)
@@ -4670,6 +4671,14 @@ static int apply_patch(struct apply_state *state,
                        patch_stats(state, patch);
                        *listp = patch;
                        listp = &patch->next;
+
+                       if ((patch->new_name &&
+                            ends_with_path_components(patch->new_name,
+                                                      GITATTRIBUTES_FILE)) ||
+                           (patch->old_name &&
+                            ends_with_path_components(patch->old_name,
+                                                      GITATTRIBUTES_FILE)))
+                               flush_attributes = 1;
                }
                else {
                        if (state->apply_verbosity > verbosity_normal)
@@ -4746,6 +4755,8 @@ static int apply_patch(struct apply_state *state,
        if (state->summary && state->apply_verbosity > verbosity_silent)
                summary_patch_list(list);
 
+       if (flush_attributes)
+               reset_parsed_attributes();
 end:
        free_patch_list(list);
        strbuf_release(&buf);
index 3e53aac1e6523571ce0b9cb02d151c1f00652603..e16d3f756ddd61d38477e73b71aa01e912ba2b13 100644 (file)
@@ -142,19 +142,25 @@ static int stream_blocked(const struct object_id *oid)
  * string and appends it to a struct strbuf.
  */
 static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
-                                    const char *value, unsigned int valuelen)
+                                    const char *value, size_t valuelen)
 {
-       int len, tmp;
+       size_t orig_len = sb->len;
+       size_t len, tmp;
 
        /* "%u %s=%s\n" */
        len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
-       for (tmp = len; tmp > 9; tmp /= 10)
+       for (tmp = 1; len / 10 >= tmp; tmp *= 10)
                len++;
 
        strbuf_grow(sb, len);
-       strbuf_addf(sb, "%u %s=", len, keyword);
+       strbuf_addf(sb, "%"PRIuMAX" %s=", (uintmax_t)len, keyword);
        strbuf_add(sb, value, valuelen);
        strbuf_addch(sb, '\n');
+
+       if (len != sb->len - orig_len)
+               BUG("pax extended header length miscalculated as %"PRIuMAX
+                   ", should be %"PRIuMAX,
+                   (uintmax_t)len, (uintmax_t)(sb->len - orig_len));
 }
 
 /*
index 995d47c85aad24a645786ed6480bd659c755997e..d6a1aa74cd41fe17fdb9d92f6b55fb7d67b31726 100644 (file)
@@ -15,6 +15,7 @@
 #include "sha1-array.h"
 #include "packfile.h"
 #include "object-store.h"
+#include "promisor-remote.h"
 
 struct batch_options {
        int enabled;
@@ -524,8 +525,8 @@ static int batch_objects(struct batch_options *opt)
        if (opt->all_objects) {
                struct object_cb_data cb;
 
-               if (repository_format_partial_clone)
-                       warning("This repository has extensions.partialClone set. Some objects may not be loaded.");
+               if (has_promisor_remote())
+                       warning("This repository uses promisor remotes. Some objects may not be loaded.");
 
                cb.opt = opt;
                cb.expand = &data;
index 8d3ad7cd9e735fdece09793ec11995e7b91b5c6f..0a3679eb776ec962d943f3d1902c9c6e7f1feea7 100644 (file)
@@ -731,13 +731,6 @@ static int merge_working_tree(const struct checkout_opts *opts,
                                      "the following files:\n%s"), sb.buf);
                        strbuf_release(&sb);
 
-                       if (repo_index_has_changes(the_repository,
-                                                  get_commit_tree(old_branch_info->commit),
-                                                  &sb))
-                               warning(_("staged changes in the following files may be lost: %s"),
-                                       sb.buf);
-                       strbuf_release(&sb);
-
                        /* Do more real merge */
 
                        /*
index f665b28ccccfacaf5dfe84b7f94081e1afacdd49..2048b6760aa9f0a426524e5ea1bd7a3a4c4c5536 100644 (file)
@@ -1160,13 +1160,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                transport->server_options = &server_options;
 
        if (filter_options.choice) {
-               struct strbuf expanded_filter_spec = STRBUF_INIT;
-               expand_list_objects_filter_spec(&filter_options,
-                                               &expanded_filter_spec);
+               const char *spec =
+                       expand_list_objects_filter_spec(&filter_options);
                transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
-                                    expanded_filter_spec.buf);
+                                    spec);
                transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
-               strbuf_release(&expanded_filter_spec);
        }
 
        if (transport->smart_options && !deepen && !filter_options.choice)
index 717dd14e896117c56101f7f56df0261576c323d0..9b27ae968199a75e62a617b649b56788e501e076 100644 (file)
@@ -23,6 +23,8 @@
 #include "packfile.h"
 #include "list-objects-filter-options.h"
 #include "commit-reach.h"
+#include "branch.h"
+#include "promisor-remote.h"
 
 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 
@@ -50,7 +52,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
 static int prune_tags = -1; /* unspecified */
 #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 
-static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
+static int all, append, dry_run, force, keep, multiple, update_head_ok;
+static int verbosity, deepen_relative, set_upstream;
 static int progress = -1;
 static int enable_auto_gc = 1;
 static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@ -123,6 +126,8 @@ static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
        OPT_BOOL(0, "all", &all,
                 N_("fetch from all remotes")),
+       OPT_BOOL(0, "set-upstream", &set_upstream,
+                N_("set upstream for git pull/fetch")),
        OPT_BOOL('a', "append", &append,
                 N_("append to .git/FETCH_HEAD instead of overwriting")),
        OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
@@ -1238,13 +1243,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
        if (update_shallow)
                set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
        if (filter_options.choice) {
-               struct strbuf expanded_filter_spec = STRBUF_INIT;
-               expand_list_objects_filter_spec(&filter_options,
-                                               &expanded_filter_spec);
-               set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
-                          expanded_filter_spec.buf);
+               const char *spec =
+                       expand_list_objects_filter_spec(&filter_options);
+               set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec);
                set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
-               strbuf_release(&expanded_filter_spec);
        }
        if (negotiation_tip.nr) {
                if (transport->smart_options)
@@ -1367,6 +1369,51 @@ static int do_fetch(struct transport *transport,
                retcode = 1;
                goto cleanup;
        }
+
+       if (set_upstream) {
+               struct branch *branch = branch_get("HEAD");
+               struct ref *rm;
+               struct ref *source_ref = NULL;
+
+               /*
+                * We're setting the upstream configuration for the
+                * current branch. The relevent upstream is the
+                * fetched branch that is meant to be merged with the
+                * current one, i.e. the one fetched to FETCH_HEAD.
+                *
+                * When there are several such branches, consider the
+                * request ambiguous and err on the safe side by doing
+                * nothing and just emit a warning.
+                */
+               for (rm = ref_map; rm; rm = rm->next) {
+                       if (!rm->peer_ref) {
+                               if (source_ref) {
+                                       warning(_("multiple branch detected, incompatible with --set-upstream"));
+                                       goto skip;
+                               } else {
+                                       source_ref = rm;
+                               }
+                       }
+               }
+               if (source_ref) {
+                       if (!strcmp(source_ref->name, "HEAD") ||
+                           starts_with(source_ref->name, "refs/heads/"))
+                               install_branch_config(0,
+                                                     branch->name,
+                                                     transport->remote->name,
+                                                     source_ref->name);
+                       else if (starts_with(source_ref->name, "refs/remotes/"))
+                               warning(_("not setting upstream for a remote remote-tracking branch"));
+                       else if (starts_with(source_ref->name, "refs/tags/"))
+                               warning(_("not setting upstream for a remote tag"));
+                       else
+                               warning(_("unknown branch type"));
+               } else {
+                       warning(_("no source branch found.\n"
+                               "you need to specify exactly one branch with the --set-upstream option."));
+               }
+       }
+ skip:
        free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
@@ -1510,37 +1557,27 @@ static inline void fetch_one_setup_partial(struct remote *remote)
         * If no prior partial clone/fetch and the current fetch DID NOT
         * request a partial-fetch, do a normal fetch.
         */
-       if (!repository_format_partial_clone && !filter_options.choice)
+       if (!has_promisor_remote() && !filter_options.choice)
                return;
 
        /*
-        * If this is the FIRST partial-fetch request, we enable partial
-        * on this repo and remember the given filter-spec as the default
-        * for subsequent fetches to this remote.
+        * If this is a partial-fetch request, we enable partial on
+        * this repo if not already enabled and remember the given
+        * filter-spec as the default for subsequent fetches to this
+        * remote.
         */
-       if (!repository_format_partial_clone && filter_options.choice) {
+       if (filter_options.choice) {
                partial_clone_register(remote->name, &filter_options);
                return;
        }
 
-       /*
-        * We are currently limited to only ONE promisor remote and only
-        * allow partial-fetches from the promisor remote.
-        */
-       if (strcmp(remote->name, repository_format_partial_clone)) {
-               if (filter_options.choice)
-                       die(_("--filter can only be used with the remote "
-                             "configured in extensions.partialClone"));
-               return;
-       }
-
        /*
         * Do a partial-fetch from the promisor remote using either the
         * explicitly given filter-spec or inherit the filter-spec from
         * the config.
         */
        if (!filter_options.choice)
-               partial_clone_get_default_filter_spec(&filter_options);
+               partial_clone_get_default_filter_spec(&filter_options, remote->name);
        return;
 }
 
@@ -1661,7 +1698,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        if (depth || deepen_since || deepen_not.nr)
                deepen = 1;
 
-       if (filter_options.choice && !repository_format_partial_clone)
+       if (filter_options.choice && !has_promisor_remote())
                die("--filter can only be used when extensions.partialClone is set");
 
        if (all) {
@@ -1695,7 +1732,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        }
 
        if (remote) {
-               if (filter_options.choice || repository_format_partial_clone)
+               if (filter_options.choice || has_promisor_remote())
                        fetch_one_setup_partial(remote);
                result = fetch_one(remote, argc, argv, prune_tags_ok);
        } else {
index 305fb0f45af3bdfc32e15bcc18acdcaa6af205cb..fadb45489f34a760f4c8f6c96c3cc5a5c5c115bd 100644 (file)
@@ -27,6 +27,7 @@
 #include "pack-objects.h"
 #include "blob.h"
 #include "tree.h"
+#include "promisor-remote.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -41,7 +42,6 @@ static int aggressive_depth = 50;
 static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
-static int gc_write_commit_graph;
 static int detach_auto = 1;
 static timestamp_t gc_log_expire_time;
 static const char *gc_log_expire = "1.day.ago";
@@ -148,7 +148,6 @@ static void gc_config(void)
        git_config_get_int("gc.aggressivedepth", &aggressive_depth);
        git_config_get_int("gc.auto", &gc_auto_threshold);
        git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
-       git_config_get_bool("gc.writecommitgraph", &gc_write_commit_graph);
        git_config_get_bool("gc.autodetach", &detach_auto);
        git_config_get_expiry("gc.pruneexpire", &prune_expire);
        git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
@@ -661,7 +660,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        argv_array_push(&prune, prune_expire);
                        if (quiet)
                                argv_array_push(&prune, "--no-progress");
-                       if (repository_format_partial_clone)
+                       if (has_promisor_remote())
                                argv_array_push(&prune,
                                                "--exclude-promisor-objects");
                        if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
@@ -685,11 +684,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                clean_pack_garbage();
        }
 
-       if (gc_write_commit_graph &&
-           write_commit_graph_reachable(get_object_directory(),
-                                        !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
-                                        NULL))
-               return 1;
+       prepare_repo_settings(the_repository);
+       if (the_repository->settings.gc_write_commit_graph == 1)
+               write_commit_graph_reachable(get_object_directory(),
+                                            !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
+                                            NULL);
 
        if (auto_gc && too_many_loose_objects())
                warning(_("There are too many unreachable loose objects; "
index 0d55f73b0b443b60dccc31096675d40691b3eb56..a23454da6ef9b3a219a48971ef9045772d328155 100644 (file)
@@ -14,7 +14,7 @@
 #include "thread-utils.h"
 #include "packfile.h"
 #include "object-store.h"
-#include "fetch-object.h"
+#include "promisor-remote.h"
 
 static const char index_pack_usage[] =
 "git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
@@ -1352,7 +1352,7 @@ static void fix_unresolved_deltas(struct hashfile *f)
                sorted_by_pos[i] = &ref_deltas[i];
        QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare);
 
-       if (repository_format_partial_clone) {
+       if (has_promisor_remote()) {
                /*
                 * Prefetch the delta bases.
                 */
@@ -1366,8 +1366,8 @@ static void fix_unresolved_deltas(struct hashfile *f)
                        oid_array_append(&to_fetch, &d->oid);
                }
                if (to_fetch.nr)
-                       fetch_objects(repository_format_partial_clone,
-                                     to_fetch.oid, to_fetch.nr);
+                       promisor_remote_get_direct(the_repository,
+                                                  to_fetch.oid, to_fetch.nr);
                oid_array_clear(&to_fetch);
        }
 
index e2ccbc44e204173b09f5ad4b704a31a9e8643bb6..c9746e37b86fa15963ae862ad1aaea5ece92562b 100644 (file)
@@ -81,7 +81,7 @@ static int show_progress = -1;
 static int default_to_upstream = 1;
 static int signoff;
 static const char *sign_commit;
-static int verify_msg = 1;
+static int no_verify;
 
 static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -287,7 +287,7 @@ static struct option builtin_merge_options[] = {
          N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
        OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")),
-       OPT_BOOL(0, "verify", &verify_msg, N_("verify commit-msg hook")),
+       OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")),
        OPT_END()
 };
 
@@ -816,6 +816,18 @@ static void write_merge_heads(struct commit_list *);
 static void prepare_to_commit(struct commit_list *remoteheads)
 {
        struct strbuf msg = STRBUF_INIT;
+       const char *index_file = get_index_file();
+
+       if (!no_verify && run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL))
+               abort_commit(remoteheads, NULL);
+       /*
+        * Re-read the index as pre-merge-commit hook could have updated it,
+        * and write it out as a tree.  We must do this before we invoke
+        * the editor and after we invoke run_status above.
+        */
+       if (find_hook("pre-merge-commit"))
+               discard_cache();
+       read_cache_from(index_file);
        strbuf_addbuf(&msg, &merge_msg);
        if (squash)
                BUG("the control must not reach here under --squash");
@@ -842,7 +854,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
                        abort_commit(remoteheads, NULL);
        }
 
-       if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(),
+       if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(),
                                          "commit-msg",
                                          git_path_merge_msg(the_repository), NULL))
                abort_commit(remoteheads, NULL);
index 76ce9069467e06545652a70040b79316f0a87fb9..c8f51bc65c3621d945ad4a11d9c08929a5250862 100644 (file)
@@ -2342,15 +2342,6 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
        free(array);
 }
 
-static void try_to_free_from_threads(size_t size)
-{
-       packing_data_lock(&to_pack);
-       release_pack_memory(size);
-       packing_data_unlock(&to_pack);
-}
-
-static try_to_free_t old_try_to_free_routine;
-
 /*
  * The main object list is split into smaller lists, each is handed to
  * one worker.
@@ -2391,12 +2382,10 @@ static void init_threaded_search(void)
        pthread_mutex_init(&cache_mutex, NULL);
        pthread_mutex_init(&progress_mutex, NULL);
        pthread_cond_init(&progress_cond, NULL);
-       old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads);
 }
 
 static void cleanup_threaded_search(void)
 {
-       set_try_to_free_routine(old_try_to_free_routine);
        pthread_cond_destroy(&progress_cond);
        pthread_mutex_destroy(&cache_mutex);
        pthread_mutex_destroy(&progress_mutex);
@@ -2715,10 +2704,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                use_bitmap_index_default = git_config_bool(k, v);
                return 0;
        }
-       if (!strcmp(k, "pack.usesparse")) {
-               sparse = git_config_bool(k, v);
-               return 0;
-       }
        if (!strcmp(k, "pack.threads")) {
                delta_search_threads = git_config_int(k, v);
                if (delta_search_threads < 0)
@@ -3343,6 +3328,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        read_replace_refs = 0;
 
        sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0);
+       prepare_repo_settings(the_repository);
+       if (!sparse && the_repository->settings.pack_use_sparse != -1)
+               sparse = the_repository->settings.pack_use_sparse;
+
        reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
 
index f1eaf6e6edb154417e1b38b4f0cfd9d93ef89130..d25ff13a60f2d08efdc160c359364ab94a5b19ae 100644 (file)
@@ -129,6 +129,7 @@ static char *opt_refmap;
 static char *opt_ipv4;
 static char *opt_ipv6;
 static int opt_show_forced_updates = -1;
+static char *set_upstream;
 
 static struct option pull_options[] = {
        /* Shared options */
@@ -243,6 +244,9 @@ static struct option pull_options[] = {
                PARSE_OPT_NOARG),
        OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
                 N_("check for forced-updates on all updated branches")),
+       OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
+               N_("set upstream for git pull/fetch"),
+               PARSE_OPT_NOARG),
 
        OPT_END()
 };
@@ -556,6 +560,8 @@ static int run_fetch(const char *repo, const char **refspecs)
                argv_array_push(&args, "--show-forced-updates");
        else if (opt_show_forced_updates == 0)
                argv_array_push(&args, "--no-show-forced-updates");
+       if (set_upstream)
+               argv_array_push(&args, set_upstream);
 
        if (repo) {
                argv_array_push(&args, repo);
index 670096c065f5f5fa8cb57c642cd759111b010988..e8319d594639dcd85bb47b5f1ece1a4fdd5c1a4c 100644 (file)
@@ -62,7 +62,7 @@ struct rebase_options {
        const char *onto_name;
        const char *revisions;
        const char *switch_to;
-       int root;
+       int root, root_with_onto;
        struct object_id *squash_onto;
        struct commit *restrict_revision;
        int dont_finish_rebase;
@@ -374,6 +374,7 @@ static int run_rebase_interactive(struct rebase_options *opts,
        flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
        flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
        flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
+       flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
        flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
 
        switch (command) {
@@ -1833,15 +1834,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                              "'--reschedule-failed-exec'"));
        }
 
-       if (options.rebase_merges) {
-               if (strategy_options.nr)
-                       die(_("cannot combine '--rebase-merges' with "
-                             "'--strategy-option'"));
-               if (options.strategy)
-                       die(_("cannot combine '--rebase-merges' with "
-                             "'--strategy'"));
-       }
-
        if (!options.root) {
                if (argc < 1) {
                        struct branch *branch;
@@ -1872,7 +1864,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        options.squash_onto = &squash_onto;
                        options.onto_name = squash_onto_name =
                                xstrdup(oid_to_hex(&squash_onto));
-               }
+               } else
+                       options.root_with_onto = 1;
+
                options.upstream_name = NULL;
                options.upstream = NULL;
                if (argc > 1)
index 632c0c0a79422a229d52c83665331501e8c54e29..3b3dd1437299a53a9031572820c12f3269922695 100644 (file)
@@ -11,6 +11,7 @@
 #include "midx.h"
 #include "packfile.h"
 #include "object-store.h"
+#include "promisor-remote.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
@@ -361,7 +362,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        argv_array_push(&cmd.args, "--all");
        argv_array_push(&cmd.args, "--reflog");
        argv_array_push(&cmd.args, "--indexed-objects");
-       if (repository_format_partial_clone)
+       if (has_promisor_remote())
                argv_array_push(&cmd.args, "--exclude-promisor-objects");
        if (write_bitmaps > 0)
                argv_array_push(&cmd.args, "--write-bitmap-index");
index 301ccb970bb36340bc6b4a136a3029811f252aa2..b8dc2e1fba6ce4c7763a55924f34d86796d982a2 100644 (file)
@@ -473,8 +473,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                                die(_("object filtering requires --objects"));
                        if (filter_options.choice == LOFC_SPARSE_OID &&
                            !filter_options.sparse_oid_value)
-                               die(_("invalid sparse value '%s'"),
-                                   filter_options.filter_spec);
+                               die(
+                                       _("invalid sparse value '%s'"),
+                                       list_objects_filter_spec(
+                                               &filter_options));
                        continue;
                }
                if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
index dff2f4b837208deebeb34f4af687c18df7dbfe76..49302d98c55d19651cacef7b36197de65d805e50 100644 (file)
@@ -966,6 +966,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        struct parse_opt_ctx_t ctx;
        strbuf_getline_fn getline_fn;
        int parseopt_state = PARSE_OPT_UNKNOWN;
+       struct repository *r = the_repository;
        struct option options[] = {
                OPT_BIT('q', NULL, &refresh_args.flags,
                        N_("continue refresh even when index needs update"),
@@ -1180,11 +1181,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                remove_split_index(&the_index);
        }
 
+       prepare_repo_settings(r);
        switch (untracked_cache) {
        case UC_UNSPECIFIED:
                break;
        case UC_DISABLE:
-               if (git_config_get_untracked_cache() == 1)
+               if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)
                        warning(_("core.untrackedCache is set to true; "
                                  "remove or change it, if you really want to "
                                  "disable the untracked cache"));
@@ -1196,7 +1198,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                return !test_if_untracked_cache_is_supported();
        case UC_ENABLE:
        case UC_FORCE:
-               if (git_config_get_untracked_cache() == 0)
+               if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)
                        warning(_("core.untrackedCache is set to false; "
                                  "remove or change it, if you really want to "
                                  "enable the untracked cache"));
index c22161f987152ea18715b00815e987847786c5ff..0e5724fad752e9157dbcf1caba0438b42386ee3d 100644 (file)
@@ -5,6 +5,7 @@
 #include "cache-tree.h"
 #include "object-store.h"
 #include "replace-object.h"
+#include "promisor-remote.h"
 
 #ifndef DEBUG_CACHE_TREE
 #define DEBUG_CACHE_TREE 0
@@ -357,7 +358,7 @@ static int update_one(struct cache_tree *it,
                }
 
                ce_missing_ok = mode == S_IFGITLINK || missing_ok ||
-                       (repository_format_partial_clone &&
+                       (has_promisor_remote() &&
                         ce_skip_worktree(ce));
                if (is_null_oid(oid) ||
                    (!ce_missing_ok && !has_object_file(oid))) {
diff --git a/cache.h b/cache.h
index b1da1ab08faad3da19657a9a5dcf5f2592c2127c..5624e6c02d5e72ab2975bb3219329e4580f96f55 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -636,6 +636,9 @@ int daemonize(void);
  * at least 'nr' entries; the number of entries currently allocated
  * is 'alloc', using the standard growing factor alloc_nr() macro.
  *
+ * Consider using ALLOC_GROW_BY instead of ALLOC_GROW as it has some
+ * added niceties.
+ *
  * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
  */
 #define ALLOC_GROW(x, nr, alloc) \
@@ -649,6 +652,25 @@ int daemonize(void);
                } \
        } while (0)
 
+/*
+ * Similar to ALLOC_GROW but handles updating of the nr value and
+ * zeroing the bytes of the newly-grown array elements.
+ *
+ * DO NOT USE any expression with side-effect for any of the
+ * arguments.
+ */
+#define ALLOC_GROW_BY(x, nr, increase, alloc) \
+       do { \
+               if (increase) { \
+                       size_t new_nr = nr + (increase); \
+                       if (new_nr < nr) \
+                               BUG("negative growth in ALLOC_GROW_BY"); \
+                       ALLOC_GROW(x, new_nr, alloc); \
+                       memset((x) + nr, 0, sizeof(*(x)) * (increase)); \
+                       nr = new_nr; \
+               } \
+       } while (0)
+
 /* Initialize and use the cache information */
 struct lock_file;
 void preload_index(struct index_state *index,
@@ -937,8 +959,6 @@ extern int grafts_replace_parents;
 #define GIT_REPO_VERSION 0
 #define GIT_REPO_VERSION_READ 1
 extern int repository_format_precious_objects;
-extern char *repository_format_partial_clone;
-extern const char *core_partial_clone_filter_default;
 extern int repository_format_worktree_config;
 
 /*
index f2888c203b677a8a0091f7f63129efe282899172..9b02d2c42657b4cef247fee6aa4240dead0ae776 100644 (file)
@@ -467,7 +467,6 @@ static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)
 static int prepare_commit_graph(struct repository *r)
 {
        struct object_directory *odb;
-       int config_value;
 
        if (git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD, 0))
                die("dying as requested by the '%s' variable on commit-graph load!",
@@ -477,9 +476,10 @@ static int prepare_commit_graph(struct repository *r)
                return !!r->objects->commit_graph;
        r->objects->commit_graph_attempted = 1;
 
+       prepare_repo_settings(r);
+
        if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
-           (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
-           !config_value))
+           r->settings.core_commit_graph != 1)
                /*
                 * This repository is not configured to use commit graphs, so
                 * do not load one. (But report commit_graph_attempted anyway
index 582a7b18869fb80c94b3a0057e4cb9ced4c6f1c2..71e21dd20a3b141bed0d37512cdc9196321dc315 100644 (file)
@@ -39,16 +39,16 @@ int main(int argc, const char **argv)
 
        git_resolve_executable_dir(argv[0]);
 
-       trace2_initialize();
-       trace2_cmd_start(argv);
-       trace2_collect_process_info(TRACE2_PROCESS_INFO_STARTUP);
-
        git_setup_gettext();
 
        initialize_the_repository();
 
        attr_start();
 
+       trace2_initialize();
+       trace2_cmd_start(argv);
+       trace2_collect_process_info(TRACE2_PROCESS_INFO_STARTUP);
+
        result = cmd_main(argc, argv);
 
        trace2_cmd_exit(result);
index 3900e4947be92b916ed9b531eb455e8f856105dc..743e4570ee38cb6fffe7be7ed06ecdb590481016 100644 (file)
--- a/config.c
+++ b/config.c
@@ -275,7 +275,7 @@ static int include_by_branch(const char *cond, size_t cond_len)
        int flags;
        int ret;
        struct strbuf pattern = STRBUF_INIT;
-       const char *refname = !the_repository || !the_repository->gitdir ?
+       const char *refname = !the_repository->gitdir ?
                NULL : resolve_ref_unsafe("HEAD", 0, NULL, &flags);
        const char *shortname;
 
@@ -1379,11 +1379,6 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (!strcmp(var, "core.partialclonefilter")) {
-               return git_config_string(&core_partial_clone_filter_default,
-                                        var, value);
-       }
-
        if (!strcmp(var, "core.usereplacerefs")) {
                read_replace_refs = git_config_bool(var, value);
                return 0;
@@ -2288,30 +2283,6 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestam
        return -1; /* thing exists but cannot be parsed */
 }
 
-int git_config_get_untracked_cache(void)
-{
-       int val = -1;
-       const char *v;
-
-       /* Hack for test programs like test-dump-untracked-cache */
-       if (ignore_untracked_cache_config)
-               return -1;
-
-       if (!git_config_get_maybe_bool("core.untrackedcache", &val))
-               return val;
-
-       if (!git_config_get_value("core.untrackedcache", &v)) {
-               if (!strcasecmp(v, "keep"))
-                       return -1;
-
-               error(_("unknown core.untrackedCache value '%s'; "
-                       "using 'keep' default value"), v);
-               return -1;
-       }
-
-       return -1; /* default value */
-}
-
 int git_config_get_split_index(void)
 {
        int val;
index cd9b324afa5a33be7eced6a420061905d52c211f..971db009b327e63cf71c82b496d70604cecf6415 100644 (file)
@@ -5,6 +5,7 @@
 #include "connected.h"
 #include "transport.h"
 #include "packfile.h"
+#include "promisor-remote.h"
 
 /*
  * If we feed all the commits we want to verify to this command
@@ -73,7 +74,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
        argv_array_push(&rev_list.args,"rev-list");
        argv_array_push(&rev_list.args, "--objects");
        argv_array_push(&rev_list.args, "--stdin");
-       if (repository_format_partial_clone)
+       if (has_promisor_remote())
                argv_array_push(&rev_list.args, "--exclude-promisor-objects");
        if (!opt->is_deepening_fetch) {
                argv_array_push(&rev_list.args, "--not");
index e087c4bf0085add8e968e128db6b667acbc80320..ce7ff0a96d345fd6fb5c957d4d404f40598eb9c2 100644 (file)
@@ -340,7 +340,7 @@ __gitcomp ()
                        c="$c${4-}"
                        if [[ $c == "$cur_"* ]]; then
                                case $c in
-                               --*=*|*.) ;;
+                               --*=|*.) ;;
                                *) c="$c " ;;
                                esac
                                COMPREPLY[i++]="${2-}$c"
@@ -360,7 +360,7 @@ __gitcomp ()
                        c="$c${4-}"
                        if [[ $c == "$cur_"* ]]; then
                                case $c in
-                               --*=*|*.) ;;
+                               *=|*.) ;;
                                *) c="$c " ;;
                                esac
                                COMPREPLY[i++]="${2-}$c"
@@ -524,7 +524,7 @@ __git_index_files ()
                        # Even when a directory name itself does not contain
                        # any special characters, it will still be quoted if
                        # any of its (stripped) trailing path components do.
-                       # Because of this we may have seen the same direcory
+                       # Because of this we may have seen the same directory
                        # both quoted and unquoted.
                        if (p in paths)
                                # We have seen the same directory unquoted,
@@ -1399,7 +1399,18 @@ _git_clean ()
 
 _git_clone ()
 {
+       case "$prev" in
+       -c|--config)
+               __git_complete_config_variable_name_and_value
+               return
+               ;;
+       esac
        case "$cur" in
+       --config=*)
+               __git_complete_config_variable_name_and_value \
+                       --cur="${cur##--config=}"
+               return
+               ;;
        --*)
                __gitcomp_builtin clone
                return
@@ -2225,181 +2236,282 @@ __git_config_vars=
 __git_compute_config_vars ()
 {
        test -n "$__git_config_vars" ||
-       __git_config_vars="$(git help --config-for-completion | sort | uniq)"
+       __git_config_vars="$(git help --config-for-completion | sort -u)"
 }
 
-_git_config ()
+# Completes possible values of various configuration variables.
+#
+# Usage: __git_complete_config_variable_value [<option>]...
+# --varname=<word>: The name of the configuration variable whose value is
+#                   to be completed.  Defaults to the previous word on the
+#                   command line.
+# --cur=<word>: The current value to be completed.  Defaults to the current
+#               word to be completed.
+__git_complete_config_variable_value ()
 {
-       local varname
+       local varname="$prev" cur_="$cur"
+
+       while test $# != 0; do
+               case "$1" in
+               --varname=*)    varname="${1##--varname=}" ;;
+               --cur=*)        cur_="${1##--cur=}" ;;
+               *)              return 1 ;;
+               esac
+               shift
+       done
 
        if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
-               varname="${prev,,}"
+               varname="${varname,,}"
        else
-               varname="$(echo "$prev" |tr A-Z a-z)"
+               varname="$(echo "$varname" |tr A-Z a-z)"
        fi
 
        case "$varname" in
        branch.*.remote|branch.*.pushremote)
-               __gitcomp_nl "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)" "" "$cur_"
                return
                ;;
        branch.*.merge)
-               __git_complete_refs
+               __git_complete_refs --cur="$cur_"
                return
                ;;
        branch.*.rebase)
-               __gitcomp "false true merges preserve interactive"
+               __gitcomp "false true merges preserve interactive" "" "$cur_"
                return
                ;;
        remote.pushdefault)
-               __gitcomp_nl "$(__git_remotes)"
+               __gitcomp_nl "$(__git_remotes)" "" "$cur_"
                return
                ;;
        remote.*.fetch)
-               local remote="${prev#remote.}"
+               local remote="${varname#remote.}"
                remote="${remote%.fetch}"
-               if [ -z "$cur" ]; then
+               if [ -z "$cur_" ]; then
                        __gitcomp_nl "refs/heads/" "" "" ""
                        return
                fi
-               __gitcomp_nl "$(__git_refs_remotes "$remote")"
+               __gitcomp_nl "$(__git_refs_remotes "$remote")" "" "$cur_"
                return
                ;;
        remote.*.push)
-               local remote="${prev#remote.}"
+               local remote="${varname#remote.}"
                remote="${remote%.push}"
                __gitcomp_nl "$(__git for-each-ref \
-                       --format='%(refname):%(refname)' refs/heads)"
+                       --format='%(refname):%(refname)' refs/heads)" "" "$cur_"
                return
                ;;
        pull.twohead|pull.octopus)
                __git_compute_merge_strategies
-               __gitcomp "$__git_merge_strategies"
-               return
-               ;;
-       color.branch|color.diff|color.interactive|\
-       color.showbranch|color.status|color.ui)
-               __gitcomp "always never auto"
+               __gitcomp "$__git_merge_strategies" "" "$cur_"
                return
                ;;
        color.pager)
-               __gitcomp "false true"
+               __gitcomp "false true" "" "$cur_"
                return
                ;;
        color.*.*)
                __gitcomp "
                        normal black red green yellow blue magenta cyan white
                        bold dim ul blink reverse
-                       "
+                       " "" "$cur_"
+               return
+               ;;
+       color.*)
+               __gitcomp "false true always never auto" "" "$cur_"
                return
                ;;
        diff.submodule)
-               __gitcomp "$__git_diff_submodule_formats"
+               __gitcomp "$__git_diff_submodule_formats" "" "$cur_"
                return
                ;;
        help.format)
-               __gitcomp "man info web html"
+               __gitcomp "man info web html" "" "$cur_"
                return
                ;;
        log.date)
-               __gitcomp "$__git_log_date_formats"
+               __gitcomp "$__git_log_date_formats" "" "$cur_"
                return
                ;;
        sendemail.aliasfiletype)
-               __gitcomp "mutt mailrc pine elm gnus"
+               __gitcomp "mutt mailrc pine elm gnus" "" "$cur_"
                return
                ;;
        sendemail.confirm)
-               __gitcomp "$__git_send_email_confirm_options"
+               __gitcomp "$__git_send_email_confirm_options" "" "$cur_"
                return
                ;;
        sendemail.suppresscc)
-               __gitcomp "$__git_send_email_suppresscc_options"
+               __gitcomp "$__git_send_email_suppresscc_options" "" "$cur_"
                return
                ;;
        sendemail.transferencoding)
-               __gitcomp "7bit 8bit quoted-printable base64"
-               return
-               ;;
-       --get|--get-all|--unset|--unset-all)
-               __gitcomp_nl "$(__git_config_get_set_variables)"
+               __gitcomp "7bit 8bit quoted-printable base64" "" "$cur_"
                return
                ;;
        *.*)
                return
                ;;
        esac
-       case "$cur" in
-       --*)
-               __gitcomp_builtin config
-               return
-               ;;
+}
+
+# Completes configuration sections, subsections, variable names.
+#
+# Usage: __git_complete_config_variable_name [<option>]...
+# --cur=<word>: The current configuration section/variable name to be
+#               completed.  Defaults to the current word to be completed.
+# --sfx=<suffix>: A suffix to be appended to each fully completed
+#                 configuration variable name (but not to sections or
+#                 subsections) instead of the default space.
+__git_complete_config_variable_name ()
+{
+       local cur_="$cur" sfx
+
+       while test $# != 0; do
+               case "$1" in
+               --cur=*)        cur_="${1##--cur=}" ;;
+               --sfx=*)        sfx="${1##--sfx=}" ;;
+               *)              return 1 ;;
+               esac
+               shift
+       done
+
+       case "$cur_" in
        branch.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
-               __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
+               __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" "$sfx"
                return
                ;;
        branch.*)
-               local pfx="${cur%.*}." cur_="${cur#*.}"
+               local pfx="${cur%.*}."
+               cur_="${cur#*.}"
                __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
-               __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_"
+               __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx"
                return
                ;;
        guitool.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
                __gitcomp "
                        argPrompt cmd confirm needsFile noConsole noRescan
                        prompt revPrompt revUnmerged title
-                       " "$pfx" "$cur_"
+                       " "$pfx" "$cur_" "$sfx"
                return
                ;;
        difftool.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
-               __gitcomp "cmd path" "$pfx" "$cur_"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
+               __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
                return
                ;;
        man.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
-               __gitcomp "cmd path" "$pfx" "$cur_"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
+               __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
                return
                ;;
        mergetool.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
-               __gitcomp "cmd path trustExitCode" "$pfx" "$cur_"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
+               __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" "$sfx"
                return
                ;;
        pager.*)
-               local pfx="${cur%.*}." cur_="${cur#*.}"
+               local pfx="${cur_%.*}."
+               cur_="${cur_#*.}"
                __git_compute_all_commands
-               __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
+               __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx"
                return
                ;;
        remote.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
                __gitcomp "
                        url proxy fetch push mirror skipDefaultUpdate
                        receivepack uploadpack tagOpt pushurl
-                       " "$pfx" "$cur_"
+                       " "$pfx" "$cur_" "$sfx"
                return
                ;;
        remote.*)
-               local pfx="${cur%.*}." cur_="${cur#*.}"
+               local pfx="${cur_%.*}."
+               cur_="${cur_#*.}"
                __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
-               __gitcomp_nl_append "pushDefault" "$pfx" "$cur_"
+               __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx"
                return
                ;;
        url.*.*)
-               local pfx="${cur%.*}." cur_="${cur##*.}"
-               __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_"
+               local pfx="${cur_%.*}."
+               cur_="${cur_##*.}"
+               __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" "$sfx"
                return
                ;;
        *.*)
                __git_compute_config_vars
-               __gitcomp "$__git_config_vars"
+               __gitcomp "$__git_config_vars" "" "$cur_" "$sfx"
                ;;
        *)
                __git_compute_config_vars
-               __gitcomp "$(echo "$__git_config_vars" | sed 's/\.[^ ]*/./g')"
+               __gitcomp "$(echo "$__git_config_vars" |
+                               awk -F . '{
+                                       sections[$1] = 1
+                               }
+                               END {
+                                       for (s in sections)
+                                               print s "."
+                               }
+                               ')" "" "$cur_"
+               ;;
+       esac
+}
+
+# Completes '='-separated configuration sections/variable names and values
+# for 'git -c section.name=value'.
+#
+# Usage: __git_complete_config_variable_name_and_value [<option>]...
+# --cur=<word>: The current configuration section/variable name/value to be
+#               completed. Defaults to the current word to be completed.
+__git_complete_config_variable_name_and_value ()
+{
+       local cur_="$cur"
+
+       while test $# != 0; do
+               case "$1" in
+               --cur=*)        cur_="${1##--cur=}" ;;
+               *)              return 1 ;;
+               esac
+               shift
+       done
+
+       case "$cur_" in
+       *=*)
+               __git_complete_config_variable_value \
+                       --varname="${cur_%%=*}" --cur="${cur_#*=}"
+               ;;
+       *)
+               __git_complete_config_variable_name --cur="$cur_" --sfx='='
+               ;;
+       esac
+}
+
+_git_config ()
+{
+       case "$prev" in
+       --get|--get-all|--unset|--unset-all)
+               __gitcomp_nl "$(__git_config_get_set_variables)"
+               return
+               ;;
+       *.*)
+               __git_complete_config_variable_value
+               return
+               ;;
+       esac
+       case "$cur" in
+       --*)
+               __gitcomp_builtin config
+               ;;
+       *)
+               __git_complete_config_variable_name
+               ;;
        esac
 }
 
@@ -2956,7 +3068,11 @@ __git_main ()
                        # Bash filename completion
                        return
                        ;;
-               -c|--namespace)
+               -c)
+                       __git_complete_config_variable_name_and_value
+                       return
+                       ;;
+               --namespace)
                        # we don't support completing these options' arguments
                        return
                        ;;
index 94ff8376492257782a1af5b3d2851ee8724c2edd..deb6f71b2d164fc87dab3b741da34dde98c51374 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -8,6 +8,7 @@
 #include "pkt-line.h"
 #include "sub-process.h"
 #include "utf8.h"
+#include "ll-merge.h"
 
 /*
  * convert.c - convert a file when checking it out and checking it in.
@@ -1293,10 +1294,11 @@ struct conv_attrs {
        const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
 };
 
+static struct attr_check *check;
+
 static void convert_attrs(const struct index_state *istate,
                          struct conv_attrs *ca, const char *path)
 {
-       static struct attr_check *check;
        struct attr_check_item *ccheck = NULL;
 
        if (!check) {
@@ -1339,6 +1341,23 @@ static void convert_attrs(const struct index_state *istate,
                ca->crlf_action = CRLF_AUTO_INPUT;
 }
 
+void reset_parsed_attributes(void)
+{
+       struct convert_driver *drv, *next;
+
+       attr_check_free(check);
+       check = NULL;
+       reset_merge_attributes();
+
+       for (drv = user_convert; drv; drv = next) {
+               next = drv->next;
+               free((void *)drv->name);
+               free(drv);
+       }
+       user_convert = NULL;
+       user_convert_tail = NULL;
+}
+
 int would_convert_to_git_filter_fd(const struct index_state *istate, const char *path)
 {
        struct conv_attrs ca;
index 831559f10d4442b35a43c642b0fd6c203d996f3c..3710969d43d6a16c6eab2e8302f962aa466ccd9f 100644 (file)
--- a/convert.h
+++ b/convert.h
@@ -94,6 +94,12 @@ void convert_to_git_filter_fd(const struct index_state *istate,
 int would_convert_to_git_filter_fd(const struct index_state *istate,
                                   const char *path);
 
+/*
+ * Reset the internal list of attributes used by convert_to_git and
+ * convert_to_working_tree.
+ */
+void reset_parsed_attributes(void);
+
 /*****************************************************************
  *
  * Streaming conversion support
index ac295420dd0d03d1b31922f9b7c16a83e987e6a3..c010497cb21db3c2bc921caf1b34be3e60ff5570 100644 (file)
@@ -72,15 +72,16 @@ static void store_credential_file(const char *fn, struct credential *c)
        struct strbuf buf = STRBUF_INIT;
 
        strbuf_addf(&buf, "%s://", c->protocol);
-       strbuf_addstr_urlencode(&buf, c->username, 1);
+       strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved);
        strbuf_addch(&buf, ':');
-       strbuf_addstr_urlencode(&buf, c->password, 1);
+       strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved);
        strbuf_addch(&buf, '@');
        if (c->host)
-               strbuf_addstr_urlencode(&buf, c->host, 1);
+               strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved);
        if (c->path) {
                strbuf_addch(&buf, '/');
-               strbuf_addstr_urlencode(&buf, c->path, 0);
+               strbuf_addstr_urlencode(&buf, c->path,
+                                       is_rfc3986_reserved_or_unreserved);
        }
 
        rewrite_credential_file(fn, c, &buf);
diff --git a/diff.c b/diff.c
index efe42b341ae15ebceb1e216518ab3a542be361dc..6db6927369e5c18ae3cb7ea27a0ebce37fd2b357 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -25,7 +25,7 @@
 #include "packfile.h"
 #include "parse-options.h"
 #include "help.h"
-#include "fetch-object.h"
+#include "promisor-remote.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -6512,6 +6512,7 @@ static void add_if_missing(struct repository *r,
                           const struct diff_filespec *filespec)
 {
        if (filespec && filespec->oid_valid &&
+           !S_ISGITLINK(filespec->mode) &&
            oid_object_info_extended(r, &filespec->oid, NULL,
                                     OBJECT_INFO_FOR_PREFETCH))
                oid_array_append(to_fetch, &filespec->oid);
@@ -6519,8 +6520,7 @@ static void add_if_missing(struct repository *r,
 
 void diffcore_std(struct diff_options *options)
 {
-       if (options->repo == the_repository &&
-           repository_format_partial_clone) {
+       if (options->repo == the_repository && has_promisor_remote()) {
                /*
                 * Prefetch the diff pairs that are about to be flushed.
                 */
@@ -6537,8 +6537,8 @@ void diffcore_std(struct diff_options *options)
                        /*
                         * NEEDSWORK: Consider deduplicating the OIDs sent.
                         */
-                       fetch_objects(repository_format_partial_clone,
-                                     to_fetch.oid, to_fetch.nr);
+                       promisor_remote_get_direct(options->repo,
+                                                  to_fetch.oid, to_fetch.nr);
                oid_array_clear(&to_fetch);
        }
 
index 89af47cb8504903b90460d5faa91be9f61bd2fc3..efa072680a2bca0d317b66554a325b73c227c866 100644 (file)
@@ -31,8 +31,6 @@ int warn_ambiguous_refs = 1;
 int warn_on_object_refname_ambiguity = 1;
 int ref_paranoia = -1;
 int repository_format_precious_objects;
-char *repository_format_partial_clone;
-const char *core_partial_clone_filter_default;
 int repository_format_worktree_config;
 const char *git_commit_encoding;
 const char *git_log_output_encoding;
index d6d685cba012d6765b3998fde14b5c40fff82a96..0a1357dc9d55b34992ff5a6f65e3bdf77375465e 100644 (file)
@@ -2,19 +2,20 @@
 #include "fetch-negotiator.h"
 #include "negotiator/default.h"
 #include "negotiator/skipping.h"
+#include "repository.h"
 
-void fetch_negotiator_init(struct fetch_negotiator *negotiator,
-                          const char *algorithm)
+void fetch_negotiator_init(struct repository *r,
+                          struct fetch_negotiator *negotiator)
 {
-       if (algorithm) {
-               if (!strcmp(algorithm, "skipping")) {
-                       skipping_negotiator_init(negotiator);
-                       return;
-               } else if (!strcmp(algorithm, "default")) {
-                       /* Fall through to default initialization */
-               } else {
-                       die("unknown fetch negotiation algorithm '%s'", algorithm);
-               }
+       prepare_repo_settings(r);
+       switch(r->settings.fetch_negotiation_algorithm) {
+       case FETCH_NEGOTIATION_SKIPPING:
+               skipping_negotiator_init(negotiator);
+               return;
+
+       case FETCH_NEGOTIATION_DEFAULT:
+       default:
+               default_negotiator_init(negotiator);
+               return;
        }
-       default_negotiator_init(negotiator);
 }
index 9e3967ce6626be459ad7d8a0590240b06e7056e3..ea78868504bdcff0121c40cd40b6857df1680cfc 100644 (file)
@@ -2,6 +2,7 @@
 #define FETCH_NEGOTIATOR_H
 
 struct commit;
+struct repository;
 
 /*
  * An object that supplies the information needed to negotiate the contents of
@@ -52,7 +53,7 @@ struct fetch_negotiator {
        void *data;
 };
 
-void fetch_negotiator_init(struct fetch_negotiator *negotiator,
-                          const char *algorithm);
+void fetch_negotiator_init(struct repository *r,
+                          struct fetch_negotiator *negotiator);
 
 #endif
diff --git a/fetch-object.c b/fetch-object.c
deleted file mode 100644 (file)
index 4266548..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#include "cache.h"
-#include "packfile.h"
-#include "pkt-line.h"
-#include "strbuf.h"
-#include "transport.h"
-#include "fetch-object.h"
-
-static void fetch_refs(const char *remote_name, struct ref *ref)
-{
-       struct remote *remote;
-       struct transport *transport;
-       int original_fetch_if_missing = fetch_if_missing;
-
-       fetch_if_missing = 0;
-       remote = remote_get(remote_name);
-       if (!remote->url[0])
-               die(_("Remote with no URL"));
-       transport = transport_get(remote, remote->url[0]);
-
-       transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
-       transport_set_option(transport, TRANS_OPT_NO_DEPENDENTS, "1");
-       transport_fetch_refs(transport, ref);
-       fetch_if_missing = original_fetch_if_missing;
-}
-
-void fetch_objects(const char *remote_name, const struct object_id *oids,
-                  int oid_nr)
-{
-       struct ref *ref = NULL;
-       int i;
-
-       for (i = 0; i < oid_nr; i++) {
-               struct ref *new_ref = alloc_ref(oid_to_hex(&oids[i]));
-               oidcpy(&new_ref->old_oid, &oids[i]);
-               new_ref->exact_oid = 1;
-               new_ref->next = ref;
-               ref = new_ref;
-       }
-       fetch_refs(remote_name, ref);
-}
diff --git a/fetch-object.h b/fetch-object.h
deleted file mode 100644 (file)
index d6444ca..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef FETCH_OBJECT_H
-#define FETCH_OBJECT_H
-
-struct object_id;
-
-void fetch_objects(const char *remote_name, const struct object_id *oids,
-                  int oid_nr);
-
-#endif
index 65be043f2afafdb37b4ce4d6fe8fbd3ac0eb2eaf..6ccc6294ea7b7fe2da9d2306acdcf40031a4383f 100644 (file)
@@ -36,7 +36,6 @@ static int agent_supported;
 static int server_supports_filtering;
 static struct lock_file shallow_lock;
 static const char *alternate_shallow_file;
-static char *negotiation_algorithm;
 static struct strbuf fsck_msg_types = STRBUF_INIT;
 
 /* Remember to update object flag allocation in object.h */
@@ -339,12 +338,9 @@ static int find_common(struct fetch_negotiator *negotiator,
                }
        }
        if (server_supports_filtering && args->filter_options.choice) {
-               struct strbuf expanded_filter_spec = STRBUF_INIT;
-               expand_list_objects_filter_spec(&args->filter_options,
-                                               &expanded_filter_spec);
-               packet_buf_write(&req_buf, "filter %s",
-                                expanded_filter_spec.buf);
-               strbuf_release(&expanded_filter_spec);
+               const char *spec =
+                       expand_list_objects_filter_spec(&args->filter_options);
+               packet_buf_write(&req_buf, "filter %s", spec);
        }
        packet_buf_flush(&req_buf);
        state_len = req_buf.len;
@@ -892,12 +888,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
                                 struct shallow_info *si,
                                 char **pack_lockfile)
 {
+       struct repository *r = the_repository;
        struct ref *ref = copy_ref_list(orig_ref);
        struct object_id oid;
        const char *agent_feature;
        int agent_len;
        struct fetch_negotiator negotiator;
-       fetch_negotiator_init(&negotiator, negotiation_algorithm);
+       fetch_negotiator_init(r, &negotiator);
 
        sort_ref_list(&ref, ref_compare_name);
        QSORT(sought, nr_sought, cmp_ref_by_name);
@@ -911,7 +908,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
 
        if (server_supports("shallow"))
                print_verbose(args, _("Server supports %s"), "shallow");
-       else if (args->depth > 0 || is_repository_shallow(the_repository))
+       else if (args->depth > 0 || is_repository_shallow(r))
                die(_("Server does not support shallow clients"));
        if (args->depth > 0 || args->deepen_since || args->deepen_not)
                args->deepen = 1;
@@ -1112,7 +1109,7 @@ static int add_haves(struct fetch_negotiator *negotiator,
 }
 
 static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
-                             const struct fetch_pack_args *args,
+                             struct fetch_pack_args *args,
                              const struct ref *wants, struct oidset *common,
                              int *haves_to_send, int *in_vain,
                              int sideband_all)
@@ -1153,13 +1150,10 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
        /* Add filter */
        if (server_supports_feature("fetch", "filter", 0) &&
            args->filter_options.choice) {
-               struct strbuf expanded_filter_spec = STRBUF_INIT;
+               const char *spec =
+                       expand_list_objects_filter_spec(&args->filter_options);
                print_verbose(args, _("Server supports filter"));
-               expand_list_objects_filter_spec(&args->filter_options,
-                                               &expanded_filter_spec);
-               packet_buf_write(&req_buf, "filter %s",
-                                expanded_filter_spec.buf);
-               strbuf_release(&expanded_filter_spec);
+               packet_buf_write(&req_buf, "filter %s", spec);
        } else if (args->filter_options.choice) {
                warning("filtering not recognized by server, ignoring");
        }
@@ -1379,6 +1373,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                                    struct shallow_info *si,
                                    char **pack_lockfile)
 {
+       struct repository *r = the_repository;
        struct ref *ref = copy_ref_list(orig_ref);
        enum fetch_state state = FETCH_CHECK_LOCAL;
        struct oidset common = OIDSET_INIT;
@@ -1386,7 +1381,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
        int in_vain = 0;
        int haves_to_send = INITIAL_FLUSH;
        struct fetch_negotiator negotiator;
-       fetch_negotiator_init(&negotiator, negotiation_algorithm);
+       fetch_negotiator_init(r, &negotiator);
        packet_reader_init(&reader, fd[0], NULL, 0,
                           PACKET_READ_CHOMP_NEWLINE |
                           PACKET_READ_DIE_ON_ERR_PACKET);
@@ -1505,8 +1500,6 @@ static void fetch_pack_config(void)
        git_config_get_bool("repack.usedeltabaseoffset", &prefer_ofs_delta);
        git_config_get_bool("fetch.fsckobjects", &fetch_fsck_objects);
        git_config_get_bool("transfer.fsckobjects", &transfer_fsck_objects);
-       git_config_get_string("fetch.negotiationalgorithm",
-                             &negotiation_algorithm);
 
        git_config(fetch_pack_config_cb, NULL);
 }
index 83be89de0aac7c96799635e3b2858a465f440acf..f0d13e4e28470a544516a52146aae80c079467de 100644 (file)
@@ -818,9 +818,6 @@ const char *inet_ntop(int af, const void *src, char *dst, size_t size);
 int git_atexit(void (*handler)(void));
 #endif
 
-typedef void (*try_to_free_t)(size_t);
-try_to_free_t set_try_to_free_routine(try_to_free_t);
-
 static inline size_t st_add(size_t a, size_t b)
 {
        if (unsigned_add_overflows(a, b))
index 6de74ce639cec90fcf7ba1797825526df675547a..fd476b69993c68ddd58e274ae97e0f91c0086951 100755 (executable)
@@ -1340,6 +1340,7 @@ set HEAD {}
 set PARENT {}
 set MERGE_HEAD [list]
 set commit_type {}
+set commit_type_is_amend 0
 set empty_tree {}
 set current_branch {}
 set is_detached 0
@@ -1347,8 +1348,9 @@ set current_diff_path {}
 set is_3way_diff 0
 set is_submodule_diff 0
 set is_conflict_diff 0
-set selected_commit_type new
 set diff_empty_count 0
+set last_revert {}
+set last_revert_enc {}
 
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
@@ -1434,7 +1436,7 @@ proc PARENT {} {
 }
 
 proc force_amend {} {
-       global selected_commit_type
+       global commit_type_is_amend
        global HEAD PARENT MERGE_HEAD commit_type
 
        repository_state newType newHEAD newMERGE_HEAD
@@ -1443,7 +1445,7 @@ proc force_amend {} {
        set MERGE_HEAD $newMERGE_HEAD
        set commit_type $newType
 
-       set selected_commit_type amend
+       set commit_type_is_amend 1
        do_select_commit_type
 }
 
@@ -2494,7 +2496,7 @@ proc force_first_diff {after} {
 
 proc toggle_or_diff {mode w args} {
        global file_states file_lists current_diff_path ui_index ui_workdir
-       global last_clicked selected_paths
+       global last_clicked selected_paths file_lists_last_clicked
 
        if {$mode eq "click"} {
                foreach {x y} $args break
@@ -2551,6 +2553,8 @@ proc toggle_or_diff {mode w args} {
        $ui_index tag remove in_sel 0.0 end
        $ui_workdir tag remove in_sel 0.0 end
 
+       set file_lists_last_clicked($w) $path
+
        # Determine the state of the file
        if {[info exists file_states($path)]} {
                set state [lindex $file_states($path) 0]
@@ -2664,6 +2668,32 @@ proc show_less_context {} {
        }
 }
 
+proc focus_widget {widget} {
+       global file_lists last_clicked selected_paths
+       global file_lists_last_clicked
+
+       if {[llength $file_lists($widget)] > 0} {
+               set path $file_lists_last_clicked($widget)
+               set index [lsearch -sorted -exact $file_lists($widget) $path]
+               if {$index < 0} {
+                       set index 0
+                       set path [lindex $file_lists($widget) $index]
+               }
+
+               focus $widget
+               set last_clicked [list $widget [expr $index + 1]]
+               array unset selected_paths
+               set selected_paths($path) 1
+               show_diff $path $widget
+       }
+}
+
+proc toggle_commit_type {} {
+       global commit_type_is_amend
+       set commit_type_is_amend [expr !$commit_type_is_amend]
+       do_select_commit_type
+}
+
 ######################################################################
 ##
 ## ui construction
@@ -2852,19 +2882,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
        menu .mbar.commit
 
        if {![is_enabled nocommit]} {
-               .mbar.commit add radiobutton \
-                       -label [mc "New Commit"] \
-                       -command do_select_commit_type \
-                       -variable selected_commit_type \
-                       -value new
-               lappend disable_on_lock \
-                       [list .mbar.commit entryconf [.mbar.commit index last] -state]
-
-               .mbar.commit add radiobutton \
+               .mbar.commit add checkbutton \
                        -label [mc "Amend Last Commit"] \
-                       -command do_select_commit_type \
-                       -variable selected_commit_type \
-                       -value amend
+                       -accelerator $M1T-E \
+                       -variable commit_type_is_amend \
+                       -command do_select_commit_type
                lappend disable_on_lock \
                        [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
@@ -3030,8 +3052,23 @@ unset doc_path doc_url
 wm protocol . WM_DELETE_WINDOW do_quit
 bind all <$M1B-Key-q> do_quit
 bind all <$M1B-Key-Q> do_quit
-bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
-bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
+
+set m1b_w_script {
+       set toplvl_win [winfo toplevel %W]
+
+       # If we are destroying the main window, we should call do_quit to take
+       # care of cleanup before exiting the program.
+       if {$toplvl_win eq "."} {
+               do_quit
+       } else {
+               destroy $toplvl_win
+       }
+}
+
+bind all <$M1B-Key-w> $m1b_w_script
+bind all <$M1B-Key-W> $m1b_w_script
+
+unset m1b_w_script
 
 set subcommand_args {}
 proc usage {} {
@@ -3337,18 +3374,10 @@ set ui_comm .vpane.lower.commarea.buffer.frame.t
 set ui_coml .vpane.lower.commarea.buffer.header.l
 
 if {![is_enabled nocommit]} {
-       ${NS}::radiobutton .vpane.lower.commarea.buffer.header.new \
-               -text [mc "New Commit"] \
-               -command do_select_commit_type \
-               -variable selected_commit_type \
-               -value new
-       lappend disable_on_lock \
-               [list .vpane.lower.commarea.buffer.header.new conf -state]
-       ${NS}::radiobutton .vpane.lower.commarea.buffer.header.amend \
+       ${NS}::checkbutton .vpane.lower.commarea.buffer.header.amend \
                -text [mc "Amend Last Commit"] \
-               -command do_select_commit_type \
-               -variable selected_commit_type \
-               -value amend
+               -variable commit_type_is_amend \
+               -command do_select_commit_type
        lappend disable_on_lock \
                [list .vpane.lower.commarea.buffer.header.amend conf -state]
 }
@@ -3373,7 +3402,6 @@ pack $ui_coml -side left -fill x
 
 if {![is_enabled nocommit]} {
        pack .vpane.lower.commarea.buffer.header.amend -side right
-       pack .vpane.lower.commarea.buffer.header.new -side right
 }
 
 textframe .vpane.lower.commarea.buffer.frame
@@ -3387,10 +3415,16 @@ ttext $ui_comm -background white -foreground black \
        -relief sunken \
        -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
        -font font_diff \
+       -xscrollcommand {.vpane.lower.commarea.buffer.frame.sbx set} \
        -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set}
+${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sbx \
+       -orient horizontal \
+       -command [list $ui_comm xview]
 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \
+       -orient vertical \
        -command [list $ui_comm yview]
 
+pack .vpane.lower.commarea.buffer.frame.sbx -side bottom -fill x
 pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y
 pack $ui_comm -side left -fill y
 pack .vpane.lower.commarea.buffer.header -side top -fill x
@@ -3606,15 +3640,31 @@ set ctxm .vpane.lower.diff.body.ctxm
 menu $ctxm -tearoff 0
 $ctxm add command \
        -label [mc "Apply/Reverse Hunk"] \
-       -command {apply_hunk $cursorX $cursorY}
+       -command {apply_or_revert_hunk $cursorX $cursorY 0}
 set ui_diff_applyhunk [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
 $ctxm add command \
        -label [mc "Apply/Reverse Line"] \
-       -command {apply_range_or_line $cursorX $cursorY; do_rescan}
+       -command {apply_or_revert_range_or_line $cursorX $cursorY 0; do_rescan}
 set ui_diff_applyline [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
 $ctxm add separator
+$ctxm add command \
+       -label [mc "Revert Hunk"] \
+       -command {apply_or_revert_hunk $cursorX $cursorY 1}
+set ui_diff_reverthunk [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_reverthunk -state]
+$ctxm add command \
+       -label [mc "Revert Line"] \
+       -command {apply_or_revert_range_or_line $cursorX $cursorY 1; do_rescan}
+set ui_diff_revertline [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_revertline -state]
+$ctxm add command \
+       -label [mc "Undo Last Revert"] \
+       -command {undo_last_revert; do_rescan}
+set ui_diff_undorevert [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_undorevert -state]
+$ctxm add separator
 $ctxm add command \
        -label [mc "Show Less Context"] \
        -command show_less_context
@@ -3693,7 +3743,7 @@ proc has_textconv {path} {
 }
 
 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
-       global current_diff_path file_states
+       global current_diff_path file_states last_revert
        set ::cursorX $x
        set ::cursorY $y
        if {[info exists file_states($current_diff_path)]} {
@@ -3707,19 +3757,28 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
                tk_popup $ctxmsm $X $Y
        } else {
                set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
+               set u [mc "Undo Last Revert"]
                if {$::ui_index eq $::current_diff_side} {
                        set l [mc "Unstage Hunk From Commit"]
+                       set h [mc "Revert Hunk"]
+
                        if {$has_range} {
                                set t [mc "Unstage Lines From Commit"]
+                               set r [mc "Revert Lines"]
                        } else {
                                set t [mc "Unstage Line From Commit"]
+                               set r [mc "Revert Line"]
                        }
                } else {
                        set l [mc "Stage Hunk For Commit"]
+                       set h [mc "Revert Hunk"]
+
                        if {$has_range} {
                                set t [mc "Stage Lines For Commit"]
+                               set r [mc "Revert Lines"]
                        } else {
                                set t [mc "Stage Line For Commit"]
+                               set r [mc "Revert Line"]
                        }
                }
                if {$::is_3way_diff
@@ -3730,11 +3789,35 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
                        || [string match {T?} $state]
                        || [has_textconv $current_diff_path]} {
                        set s disabled
+                       set revert_state disabled
                } else {
                        set s normal
+
+                       # Only allow reverting changes in the working tree. If
+                       # the user wants to revert changes in the index, they
+                       # need to unstage those first.
+                       if {$::ui_workdir eq $::current_diff_side} {
+                               set revert_state normal
+                       } else {
+                               set revert_state disabled
+                       }
+               }
+
+               if {$last_revert eq {}} {
+                       set undo_state disabled
+               } else {
+                       set undo_state normal
                }
+
                $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
                $ctxm entryconf $::ui_diff_applyline -state $s -label $t
+               $ctxm entryconf $::ui_diff_revertline -state $revert_state \
+                       -label $r
+               $ctxm entryconf $::ui_diff_reverthunk -state $revert_state \
+                       -label $h
+               $ctxm entryconf $::ui_diff_undorevert -state $undo_state \
+                       -label $u
+
                tk_popup $ctxm $X $Y
        }
 }
@@ -3861,6 +3944,8 @@ bind .   <$M1B-Key-j> do_revert_selection
 bind .   <$M1B-Key-J> do_revert_selection
 bind .   <$M1B-Key-i> do_add_all
 bind .   <$M1B-Key-I> do_add_all
+bind .   <$M1B-Key-e> toggle_commit_type
+bind .   <$M1B-Key-E> toggle_commit_type
 bind .   <$M1B-Key-minus> {show_less_context;break}
 bind .   <$M1B-Key-KP_Subtract> {show_less_context;break}
 bind .   <$M1B-Key-equal> {show_more_context;break}
@@ -3877,6 +3962,14 @@ foreach i [list $ui_index $ui_workdir] {
 }
 unset i
 
+bind .   <Alt-Key-1> {focus_widget $::ui_workdir}
+bind .   <Alt-Key-2> {focus_widget $::ui_index}
+bind .   <Alt-Key-3> {focus $::ui_diff}
+bind .   <Alt-Key-4> {focus $::ui_comm}
+
+set file_lists_last_clicked($ui_index) {}
+set file_lists_last_clicked($ui_workdir) {}
+
 set file_lists($ui_index) [list]
 set file_lists($ui_workdir) [list]
 
index 9e7412c446f715588b9cfe21654a3447e8680e12..a5228297db2c6bcccaf61d906143ecfffacf9822 100644 (file)
@@ -389,7 +389,7 @@ $err
 }
 
 method _after_readtree {} {
-       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global commit_type HEAD MERGE_HEAD PARENT
        global current_branch is_detached
        global ui_comm
 
@@ -490,12 +490,12 @@ method _update_repo_state {} {
        #    amend mode our file lists are accurate and we can avoid
        #    the rescan.
        #
-       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global commit_type_is_amend commit_type HEAD MERGE_HEAD PARENT
        global ui_comm
 
        unlock_index
        set name [_name $this]
-       set selected_commit_type new
+       set commit_type_is_amend 0
        if {[string match amend* $commit_type]} {
                $ui_comm delete 0.0 end
                $ui_comm edit reset
index 75ea965dacd71289dd6b965a51f40bb3c1298f5b..b516aa29906911a6984f2e9ea539c2e25d23ae66 100644 (file)
@@ -333,7 +333,7 @@ proc commit_writetree {curHEAD msg_p} {
 proc commit_committree {fd_wt curHEAD msg_p} {
        global HEAD PARENT MERGE_HEAD commit_type commit_author
        global current_branch
-       global ui_comm selected_commit_type
+       global ui_comm commit_type_is_amend
        global file_states selected_paths rescan_active
        global repo_config
        global env
@@ -467,8 +467,8 @@ A rescan will be automatically started now.
 
        # -- Update in memory status
        #
-       set selected_commit_type new
        set commit_type normal
+       set commit_type_is_amend 0
        set HEAD $cmt_id
        set PARENT $cmt_id
        set MERGE_HEAD [list]
index 68c4a6c7366f864bcfc500483c83b15860a2d185..958a0fa2191b1dbcf3b5f92de7a1b77ac03fb152 100644 (file)
@@ -55,7 +55,7 @@ proc reshow_diff {{after {}}} {
 
 proc force_diff_encoding {enc} {
        global current_diff_path
-       
+
        if {$current_diff_path ne {}} {
                force_path_encoding $current_diff_path $enc
                reshow_diff
@@ -567,24 +567,31 @@ proc read_diff {fd conflict_size cont_info} {
        }
 }
 
-proc apply_hunk {x y} {
+proc apply_or_revert_hunk {x y revert} {
        global current_diff_path current_diff_header current_diff_side
-       global ui_diff ui_index file_states
+       global ui_diff ui_index file_states last_revert last_revert_enc
 
        if {$current_diff_path eq {} || $current_diff_header eq {}} return
        if {![lock_index apply_hunk]} return
 
-       set apply_cmd {apply --cached --whitespace=nowarn}
+       set apply_cmd {apply --whitespace=nowarn}
        set mi [lindex $file_states($current_diff_path) 0]
        if {$current_diff_side eq $ui_index} {
                set failed_msg [mc "Failed to unstage selected hunk."]
-               lappend apply_cmd --reverse
+               lappend apply_cmd --reverse --cached
                if {[string index $mi 0] ne {M}} {
                        unlock_index
                        return
                }
        } else {
-               set failed_msg [mc "Failed to stage selected hunk."]
+               if {$revert} {
+                       set failed_msg [mc "Failed to revert selected hunk."]
+                       lappend apply_cmd --reverse
+               } else {
+                       set failed_msg [mc "Failed to stage selected hunk."]
+                       lappend apply_cmd --cached
+               }
+
                if {[string index $mi 1] ne {M}} {
                        unlock_index
                        return
@@ -603,29 +610,40 @@ proc apply_hunk {x y} {
                set e_lno end
        }
 
+       set wholepatch "$current_diff_header[$ui_diff get $s_lno $e_lno]"
+
        if {[catch {
                set enc [get_path_encoding $current_diff_path]
                set p [eval git_write $apply_cmd]
                fconfigure $p -translation binary -encoding $enc
-               puts -nonewline $p $current_diff_header
-               puts -nonewline $p [$ui_diff get $s_lno $e_lno]
+               puts -nonewline $p $wholepatch
                close $p} err]} {
                error_popup "$failed_msg\n\n$err"
                unlock_index
                return
        }
 
+       if {$revert} {
+               # Save a copy of this patch for undoing reverts.
+               set last_revert $wholepatch
+               set last_revert_enc $enc
+       }
+
        $ui_diff conf -state normal
        $ui_diff delete $s_lno $e_lno
        $ui_diff conf -state disabled
 
+       # Check if the hunk was the last one in the file.
        if {[$ui_diff get 1.0 end] eq "\n"} {
                set o _
        } else {
                set o ?
        }
 
-       if {$current_diff_side eq $ui_index} {
+       # Update the status flags.
+       if {$revert} {
+               set mi [string index $mi 0]$o
+       } elseif {$current_diff_side eq $ui_index} {
                set mi ${o}M
        } elseif {[string index $mi 0] eq {_}} {
                set mi M$o
@@ -640,9 +658,9 @@ proc apply_hunk {x y} {
        }
 }
 
-proc apply_range_or_line {x y} {
+proc apply_or_revert_range_or_line {x y revert} {
        global current_diff_path current_diff_header current_diff_side
-       global ui_diff ui_index file_states
+       global ui_diff ui_index file_states last_revert
 
        set selected [$ui_diff tag nextrange sel 0.0]
 
@@ -660,19 +678,27 @@ proc apply_range_or_line {x y} {
        if {$current_diff_path eq {} || $current_diff_header eq {}} return
        if {![lock_index apply_hunk]} return
 
-       set apply_cmd {apply --cached --whitespace=nowarn}
+       set apply_cmd {apply --whitespace=nowarn}
        set mi [lindex $file_states($current_diff_path) 0]
        if {$current_diff_side eq $ui_index} {
                set failed_msg [mc "Failed to unstage selected line."]
                set to_context {+}
-               lappend apply_cmd --reverse
+               lappend apply_cmd --reverse --cached
                if {[string index $mi 0] ne {M}} {
                        unlock_index
                        return
                }
        } else {
-               set failed_msg [mc "Failed to stage selected line."]
-               set to_context {-}
+               if {$revert} {
+                       set failed_msg [mc "Failed to revert selected line."]
+                       set to_context {+}
+                       lappend apply_cmd --reverse
+               } else {
+                       set failed_msg [mc "Failed to stage selected line."]
+                       set to_context {-}
+                       lappend apply_cmd --cached
+               }
+
                if {[string index $mi 1] ne {M}} {
                        unlock_index
                        return
@@ -830,7 +856,47 @@ proc apply_range_or_line {x y} {
                puts -nonewline $p $wholepatch
                close $p} err]} {
                error_popup "$failed_msg\n\n$err"
+               unlock_index
+               return
+       }
+
+       if {$revert} {
+               # Save a copy of this patch for undoing reverts.
+               set last_revert $current_diff_header$wholepatch
+               set last_revert_enc $enc
        }
 
        unlock_index
 }
+
+# Undo the last line/hunk reverted. When hunks and lines are reverted, a copy
+# of the diff applied is saved. Re-apply that diff to undo the revert.
+#
+# Right now, we only use a single variable to hold the copy, and not a
+# stack/deque for simplicity, so multiple undos are not possible. Maybe this
+# can be added if the need for something like this is felt in the future.
+proc undo_last_revert {} {
+       global last_revert current_diff_path current_diff_header
+       global last_revert_enc
+
+       if {$last_revert eq {}} return
+       if {![lock_index apply_hunk]} return
+
+       set apply_cmd {apply --whitespace=nowarn}
+       set failed_msg [mc "Failed to undo last revert."]
+
+       if {[catch {
+               set enc $last_revert_enc
+               set p [eval git_write $apply_cmd]
+               fconfigure $p -translation binary -encoding $enc
+               puts -nonewline $p $last_revert
+               close $p} err]} {
+               error_popup "$failed_msg\n\n$err"
+               unlock_index
+               return
+       }
+
+       set last_revert {}
+
+       unlock_index
+}
index b588db11d9fc46b6c4c33274c3c5f969dfe43e5e..e07b7a376230f50a66f2450255121798409667f8 100644 (file)
@@ -466,19 +466,19 @@ proc do_revert_selection {} {
 }
 
 proc do_select_commit_type {} {
-       global commit_type selected_commit_type
+       global commit_type commit_type_is_amend
 
-       if {$selected_commit_type eq {new}
+       if {$commit_type_is_amend == 0
                && [string match amend* $commit_type]} {
                create_new_commit
-       } elseif {$selected_commit_type eq {amend}
+       } elseif {$commit_type_is_amend == 1
                && ![string match amend* $commit_type]} {
                load_last_commit
 
                # The amend request was rejected...
                #
                if {![string match amend* $commit_type]} {
-                       set selected_commit_type new
+                       set commit_type_is_amend 0
                }
        }
 }
index a14d7a16b2dd1162fa8572d776f4adee8a0ce91f..abe4805adedb3c3559bf0048ae49df2e2ac37337 100755 (executable)
@@ -3404,6 +3404,8 @@ set rectmask {
 }
 image create bitmap reficon-H -background black -foreground "#00ff00" \
     -data $rectdata -maskdata $rectmask
+image create bitmap reficon-R -background black -foreground "#ffddaa" \
+    -data $rectdata -maskdata $rectmask
 image create bitmap reficon-o -background black -foreground "#ddddff" \
     -data $rectdata -maskdata $rectmask
 
@@ -7016,6 +7018,7 @@ proc commit_descriptor {p} {
 
 # append some text to the ctext widget, and make any SHA1 ID
 # that we know about be a clickable link.
+# Also look for URLs of the form "http[s]://..." and make them web links.
 proc appendwithlinks {text tags} {
     global ctext linknum curview
 
@@ -7032,6 +7035,18 @@ proc appendwithlinks {text tags} {
        setlink $linkid link$linknum
        incr linknum
     }
+    set wlinks [regexp -indices -all -inline -line \
+                   {https?://[^[:space:]]+} $text]
+    foreach l $wlinks {
+       set s2 [lindex $l 0]
+       set e2 [lindex $l 1]
+       set url [string range $text $s2 $e2]
+       incr e2
+       $ctext tag delete link$linknum
+       $ctext tag add link$linknum "$start + $s2 c" "$start + $e2 c"
+       setwlink $url link$linknum
+       incr linknum
+    }
 }
 
 proc setlink {id lk} {
@@ -7064,6 +7079,18 @@ proc setlink {id lk} {
     }
 }
 
+proc setwlink {url lk} {
+    global ctext
+    global linkfgcolor
+    global web_browser
+
+    if {$web_browser eq {}} return
+    $ctext tag conf $lk -foreground $linkfgcolor -underline 1
+    $ctext tag bind $lk <1> [list browseweb $url]
+    $ctext tag bind $lk <Enter> {linkcursor %W 1}
+    $ctext tag bind $lk <Leave> {linkcursor %W -1}
+}
+
 proc appendshortlink {id {pre {}} {post {}}} {
     global ctext linknum
 
@@ -7098,6 +7125,16 @@ proc linkcursor {w inc} {
     }
 }
 
+proc browseweb {url} {
+    global web_browser
+
+    if {$web_browser eq {}} return
+    # Use eval here in case $web_browser is a command plus some arguments
+    if {[catch {eval exec $web_browser [list $url] &} err]} {
+       error_popup "[mc "Error starting web browser:"] $err"
+    }
+}
+
 proc viewnextline {dir} {
     global canv linespc
 
@@ -8191,11 +8228,11 @@ proc parseblobdiffline {ids line} {
        } else {
            $ctext insert end "$line\n" filesep
        }
-    } elseif {![string compare -length 3 "  >" $line]} {
+    } elseif {$currdiffsubmod != "" && ![string compare -length 3 "  >" $line]} {
        set $currdiffsubmod ""
        set line [encoding convertfrom $diffencoding $line]
        $ctext insert end "$line\n" dresult
-    } elseif {![string compare -length 3 "  <" $line]} {
+    } elseif {$currdiffsubmod != "" && ![string compare -length 3 "  <" $line]} {
        set $currdiffsubmod ""
        set line [encoding convertfrom $diffencoding $line]
        $ctext insert end "$line\n" d0
@@ -10022,6 +10059,7 @@ proc sel_reflist {w x y} {
     set n [lindex $ref 0]
     switch -- [lindex $ref 1] {
        "H" {selbyid $headids($n)}
+       "R" {selbyid $headids($n)}
        "T" {selbyid $tagids($n)}
        "o" {selbyid $otherrefids($n)}
     }
@@ -10051,7 +10089,11 @@ proc refill_reflist {} {
     foreach n [array names headids] {
        if {[string match $reflistfilter $n]} {
            if {[commitinview $headids($n) $curview]} {
-               lappend refs [list $n H]
+               if {[string match "remotes/*" $n]} {
+                   lappend refs [list $n R]
+               } else {
+                   lappend refs [list $n H]
+               }
            } else {
                interestedin $headids($n) {run refill_reflist}
            }
@@ -11488,7 +11530,7 @@ proc create_prefs_page {w} {
 proc prefspage_general {notebook} {
     global NS maxwidth maxgraphpct showneartags showlocalchanges
     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
-    global hideremotes want_ttk have_ttk maxrefs
+    global hideremotes want_ttk have_ttk maxrefs web_browser
 
     set page [create_prefs_page $notebook.general]
 
@@ -11539,6 +11581,13 @@ proc prefspage_general {notebook} {
     pack configure $page.extdifff.l -padx 10
     grid x $page.extdifff $page.extdifft -sticky ew
 
+    ${NS}::entry $page.webbrowser -textvariable web_browser
+    ${NS}::frame $page.webbrowserf
+    ${NS}::label $page.webbrowserf.l -text [mc "Web browser" ]
+    pack $page.webbrowserf.l -side left
+    pack configure $page.webbrowserf.l -padx 10
+    grid x $page.webbrowserf $page.webbrowser -sticky ew
+
     ${NS}::label $page.lgen -text [mc "General options"]
     grid $page.lgen - -sticky w -pady 10
     ${NS}::checkbutton $page.want_ttk -variable want_ttk \
@@ -12310,6 +12359,7 @@ if {[tk windowingsystem] eq "win32"} {
     set bgcolor SystemWindow
     set fgcolor SystemWindowText
     set selectbgcolor SystemHighlight
+    set web_browser "cmd /c start"
 } else {
     set uicolor grey85
     set uifgcolor black
@@ -12317,6 +12367,11 @@ if {[tk windowingsystem] eq "win32"} {
     set bgcolor white
     set fgcolor black
     set selectbgcolor gray85
+    if {[tk windowingsystem] eq "aqua"} {
+       set web_browser "open"
+    } else {
+       set web_browser "xdg-open"
+    }
 }
 set diffcolors {red "#00a000" blue}
 set diffcontext 3
@@ -12390,6 +12445,7 @@ set config_variables {
     filesepbgcolor filesepfgcolor linehoverbgcolor linehoverfgcolor
     linehoveroutlinecolor mainheadcirclecolor workingfilescirclecolor
     indexcirclecolor circlecolors linkfgcolor circleoutlinecolor
+    web_browser
 }
 foreach var $config_variables {
     config_init_trace $var
diff --git a/gitk-git/po/zh_cn.po b/gitk-git/po/zh_cn.po
new file mode 100644 (file)
index 0000000..17b7f89
--- /dev/null
@@ -0,0 +1,1367 @@
+# Translation of gitk to Simplified Chinese.
+#
+# Translators:
+# YanKe <imyanke@163.com>, 2017
+
+msgid ""
+msgstr ""
+"Project-Id-Version: Git Chinese Localization Project\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-02-28 23:11+0800\n"
+"PO-Revision-Date: 2017-03-11 02:27+0800\n"
+"Last-Translator: YanKe <imyanke@163.com>\n"
+"Language-Team: Chinese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+
+#: gitk:140
+msgid "Couldn't get list of unmerged files:"
+msgstr "不能获取未合并文件列表:"
+
+#: gitk:212 gitk:2403
+msgid "Color words"
+msgstr "着色显示差异"
+
+#: gitk:217 gitk:2403 gitk:8249 gitk:8282
+msgid "Markup words"
+msgstr "标记显示差异"
+
+#: gitk:324
+msgid "Error parsing revisions:"
+msgstr "解析版本错误:"
+
+#: gitk:380
+msgid "Error executing --argscmd command:"
+msgstr "运行 --argscmd命令出错"
+
+#: gitk:393
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr "没有选中文件:--指定merge参数但没有未合并的文件。"
+
+#: gitk:396
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr "没有选中文件:--指定merge参数但没有未合并的文件在文件中"
+
+#: gitk:418 gitk:566
+msgid "Error executing git log:"
+msgstr "执行git log命令出错:"
+
+#: gitk:436 gitk:582
+msgid "Reading"
+msgstr "读取中"
+
+#: gitk:496 gitk:4549
+msgid "Reading commits..."
+msgstr "提交记录读取中..."
+
+#: gitk:499 gitk:1641 gitk:4552
+msgid "No commits selected"
+msgstr "未选中任何提交"
+
+#: gitk:1449 gitk:4069 gitk:12583
+msgid "Command line"
+msgstr "命令行"
+
+#: gitk:1515
+msgid "Can't parse git log output:"
+msgstr "不能解析git log输出:"
+
+#: gitk:1744
+msgid "No commit information available"
+msgstr "无可用提交信息"
+
+#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668
+msgid "OK"
+msgstr "确定"
+
+#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791
+#: gitk:11389 gitk:11669
+msgid "Cancel"
+msgstr "取消"
+
+#: gitk:2087
+msgid "&Update"
+msgstr "更新"
+
+#: gitk:2088
+msgid "&Reload"
+msgstr "重新加载"
+
+#: gitk:2089
+msgid "Reread re&ferences"
+msgstr "重新读取引用"
+
+#: gitk:2090
+msgid "&List references"
+msgstr "列出引用(分支以及tag)"
+
+#: gitk:2092
+msgid "Start git &gui"
+msgstr "启动git gui客户端"
+
+#: gitk:2094
+msgid "&Quit"
+msgstr "退出"
+
+#: gitk:2086
+msgid "&File"
+msgstr "文件"
+
+#: gitk:2098
+msgid "&Preferences"
+msgstr "偏好设置"
+
+#: gitk:2097
+msgid "&Edit"
+msgstr "编辑"
+
+#: gitk:2102
+msgid "&New view..."
+msgstr "新视图..."
+
+#: gitk:2103
+msgid "&Edit view..."
+msgstr "编辑视图..."
+
+#: gitk:2104
+msgid "&Delete view"
+msgstr "删除视图"
+
+#: gitk:2106
+msgid "&All files"
+msgstr "所有文件"
+
+#: gitk:2101
+msgid "&View"
+msgstr "视图"
+
+#: gitk:2111 gitk:2121
+msgid "&About gitk"
+msgstr "关于gitk"
+
+#: gitk:2112 gitk:2126
+msgid "&Key bindings"
+msgstr "快捷键"
+
+#: gitk:2110 gitk:2125
+msgid "&Help"
+msgstr "帮助"
+
+#: gitk:2203 gitk:8681
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:2247
+msgid "Row"
+msgstr "行"
+
+#: gitk:2285
+msgid "Find"
+msgstr "查找"
+
+#: gitk:2313
+msgid "commit"
+msgstr "提交"
+
+#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851
+#: gitk:6936
+msgid "containing:"
+msgstr "包含:"
+
+#: gitk:2320 gitk:3550 gitk:3555 gitk:4787
+msgid "touching paths:"
+msgstr "影响路径:"
+
+#: gitk:2321 gitk:4801
+msgid "adding/removing string:"
+msgstr "增加/删除字符串:"
+
+#: gitk:2322 gitk:4803
+msgid "changing lines matching:"
+msgstr "改变行匹配:"
+
+#: gitk:2331 gitk:2333 gitk:4790
+msgid "Exact"
+msgstr "精确匹配"
+
+#: gitk:2333 gitk:4878 gitk:6747
+msgid "IgnCase"
+msgstr "忽略大小写"
+
+#: gitk:2333 gitk:4760 gitk:4876 gitk:6743
+msgid "Regexp"
+msgstr "正则"
+
+#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940
+msgid "All fields"
+msgstr "所有字段"
+
+#: gitk:2336 gitk:4895 gitk:4928 gitk:6810
+msgid "Headline"
+msgstr "标题"
+
+#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413
+msgid "Comments"
+msgstr "提交注释"
+
+#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859
+#: gitk:8874
+msgid "Author"
+msgstr "作者"
+
+#: gitk:2337 gitk:4895 gitk:6810 gitk:7350
+msgid "Committer"
+msgstr "提交者"
+
+#: gitk:2371
+msgid "Search"
+msgstr "搜索"
+
+#: gitk:2379
+msgid "Diff"
+msgstr "差异"
+
+#: gitk:2381
+msgid "Old version"
+msgstr "老版本"
+
+#: gitk:2383
+msgid "New version"
+msgstr "新版本"
+
+#: gitk:2386
+msgid "Lines of context"
+msgstr "Diff上下文显示行数"
+
+#: gitk:2396
+msgid "Ignore space change"
+msgstr "忽略空格修改"
+
+#: gitk:2400 gitk:2402 gitk:7983 gitk:8235
+msgid "Line diff"
+msgstr "按行显示差异"
+
+#: gitk:2467
+msgid "Patch"
+msgstr "补丁"
+
+#: gitk:2469
+msgid "Tree"
+msgstr "树"
+
+#: gitk:2639 gitk:2660
+msgid "Diff this -> selected"
+msgstr "比较从当前提交到选中提交的差异"
+
+#: gitk:2640 gitk:2661
+msgid "Diff selected -> this"
+msgstr "比较从选中提交到当前提交的差异"
+
+#: gitk:2641 gitk:2662
+msgid "Make patch"
+msgstr "制作补丁"
+
+#: gitk:2642 gitk:9283
+msgid "Create tag"
+msgstr "创建tag"
+
+#: gitk:2643
+msgid "Copy commit summary"
+msgstr "复制提交摘要"
+
+#: gitk:2644 gitk:9414
+msgid "Write commit to file"
+msgstr "写入提交到文件"
+
+#: gitk:2645
+msgid "Create new branch"
+msgstr "创建新分支"
+
+#: gitk:2646
+msgid "Cherry-pick this commit"
+msgstr "在此提交运用补丁(cherry-pick)命令"
+
+#: gitk:2647
+msgid "Reset HEAD branch to here"
+msgstr "将分支头(HEAD)重置到此处"
+
+#: gitk:2648
+msgid "Mark this commit"
+msgstr "标记此提交"
+
+#: gitk:2649
+msgid "Return to mark"
+msgstr "返回到标记"
+
+#: gitk:2650
+msgid "Find descendant of this and mark"
+msgstr "查找本次提交的子提交并标记"
+
+#: gitk:2651
+msgid "Compare with marked commit"
+msgstr "和已标记的提交作比较"
+
+#: gitk:2652 gitk:2663
+msgid "Diff this -> marked commit"
+msgstr "比较从当前提交到已标记提交的差异"
+
+#: gitk:2653 gitk:2664
+msgid "Diff marked commit -> this"
+msgstr "比较从已标记提交到当前提交的差异"
+
+#: gitk:2654
+msgid "Revert this commit"
+msgstr "撤销(revert)此提交"
+
+#: gitk:2670
+msgid "Check out this branch"
+msgstr "检出(checkout)此分支"
+
+#: gitk:2671
+msgid "Rename this branch"
+msgstr "重命名(Rename)此分支"
+
+#: gitk:2672
+msgid "Remove this branch"
+msgstr "删除(Remove)此分支"
+
+#: gitk:2673
+msgid "Copy branch name"
+msgstr "复制分支名称"
+
+#: gitk:2680
+msgid "Highlight this too"
+msgstr "高亮此处"
+
+#: gitk:2681
+msgid "Highlight this only"
+msgstr "只高亮此处"
+
+#: gitk:2682
+msgid "External diff"
+msgstr "外部diff"
+
+#: gitk:2683
+msgid "Blame parent commit"
+msgstr "Blame父提交"
+
+#: gitk:2684
+msgid "Copy path"
+msgstr "复制路径"
+
+#: gitk:2691
+msgid "Show origin of this line"
+msgstr "显示此行原始提交"
+
+#: gitk:2692
+msgid "Run git gui blame on this line"
+msgstr "在此行运行git gui客户端的blame"
+
+#: gitk:3036
+msgid "About gitk"
+msgstr "关于gitk"
+
+#: gitk:3038
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr "\nGitk — 一个git的提交查看器\n\n© 2005-2016 Paul Mackerras\n\n在GNU许可证下使用以及分发"
+
+#: gitk:3046 gitk:3113 gitk:10004
+msgid "Close"
+msgstr "关闭"
+
+#: gitk:3067
+msgid "Gitk key bindings"
+msgstr "Gitk快捷键"
+
+#: gitk:3070
+msgid "Gitk key bindings:"
+msgstr "Gitk快捷键:"
+
+#: gitk:3072
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\t退出"
+
+#: gitk:3073
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\t关闭窗口"
+
+#: gitk:3074
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\t移动到第一次提交"
+
+#: gitk:3075
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\t移动到最后一次提交"
+
+#: gitk:3076
+msgid "<Up>, p, k\tMove up one commit"
+msgstr "<Up>, p, k\t移动到上一次提交"
+
+#: gitk:3077
+msgid "<Down>, n, j\tMove down one commit"
+msgstr "<Down>, n, j\t移动到下一次提交"
+
+#: gitk:3078
+msgid "<Left>, z, h\tGo back in history list"
+msgstr "<Left>, z, h\t历史列表的上一项"
+
+#: gitk:3079
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\t历史列表的下一项"
+
+#: gitk:3080
+#, tcl-format
+msgid "<%s-n>\tGo to n-th parent of current commit in history list"
+msgstr "<%s-n>\t在历史列表中前往本次提交的第n个父提交"
+
+#: gitk:3081
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\t上一页提交列表"
+
+#: gitk:3082
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\t下一页提交列表"
+
+#: gitk:3083
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\t滚动到提交列表顶部"
+
+#: gitk:3084
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\t滚动到提交列表底部"
+
+#: gitk:3085
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\t向上滚动一行提交列表"
+
+#: gitk:3086
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\t向下滚动一行提交列表"
+
+#: gitk:3087
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\t向上滚动一页提交列表"
+
+#: gitk:3088
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\t向下滚动一页提交列表"
+
+#: gitk:3089
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\t向后查找(向上的,更晚的提交)"
+
+#: gitk:3090
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\t向前查找(向下的,更早的提交)"
+
+#: gitk:3091
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\t向上滚动diff视图一页"
+
+#: gitk:3092
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\t向上滚动diff视图一页"
+
+#: gitk:3093
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\t向下滚动diff视图一页"
+
+#: gitk:3094
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\t向上滚动diff视图18行"
+
+#: gitk:3095
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\t向下滚动diff视图18行"
+
+#: gitk:3096
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\t查找"
+
+#: gitk:3097
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\t移动到下一次查找命中"
+
+#: gitk:3098
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\t\t移动到下一次查找命中"
+
+#: gitk:3099
+msgid "g\t\tGo to commit"
+msgstr "g\t\t转到提交"
+
+#: gitk:3100
+msgid "/\t\tFocus the search box"
+msgstr "/\t\t选中搜索框"
+
+#: gitk:3101
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\t移动到上一次查找命中"
+
+#: gitk:3102
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\t滚动diff视图到下一个文件"
+
+#: gitk:3103
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\t在diff视图中查找下一此命中"
+
+#: gitk:3104
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\t在diff视图中查找上一次命中"
+
+#: gitk:3105
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\t增大字体大小"
+
+#: gitk:3106
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\t增大字体大小"
+
+#: gitk:3107
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\t减小字体大小"
+
+#: gitk:3108
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\t减小字体大小"
+
+#: gitk:3109
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\t更新"
+
+#: gitk:3574 gitk:3583
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "创建临时目录出错%s:"
+
+#: gitk:3596
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "从%s获取\"%s\"出错:"
+
+#: gitk:3659
+msgid "command failed:"
+msgstr "执行命令失败:"
+
+#: gitk:3808
+msgid "No such commit"
+msgstr "无此提交"
+
+#: gitk:3822
+msgid "git gui blame: command failed:"
+msgstr "git gui blame:执行命令失败:"
+
+#: gitk:3853
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "不能读取合并头(merge head):%s"
+
+#: gitk:3861
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "读取索引出错:%s"
+
+#: gitk:3886
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "不能执行git blame:%s"
+
+#: gitk:3889 gitk:6778
+msgid "Searching"
+msgstr "搜索中"
+
+#: gitk:3921
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "运行git blame出错:%s"
+
+#: gitk:3949
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "此行来自提交%s,不在此视图中"
+
+#: gitk:3963
+msgid "External diff viewer failed:"
+msgstr "外部diff查看器失败:"
+
+#: gitk:4067
+msgid "All files"
+msgstr "所有文件"
+
+#: gitk:4091
+msgid "View"
+msgstr "视图"
+
+#: gitk:4094
+msgid "Gitk view definition"
+msgstr "Gitk视图定义"
+
+#: gitk:4098
+msgid "Remember this view"
+msgstr "记住此视图"
+
+#: gitk:4099
+msgid "References (space separated list):"
+msgstr "引用(空格切分的列表):"
+
+#: gitk:4100
+msgid "Branches & tags:"
+msgstr "分支和tags"
+
+#: gitk:4101
+msgid "All refs"
+msgstr "所有引用"
+
+#: gitk:4102
+msgid "All (local) branches"
+msgstr "所有(本地)分支"
+
+#: gitk:4103
+msgid "All tags"
+msgstr "所有tag"
+
+#: gitk:4104
+msgid "All remote-tracking branches"
+msgstr "所有远程跟踪分支"
+
+#: gitk:4105
+msgid "Commit Info (regular expressions):"
+msgstr "提交信息 (正则表达式):"
+
+#: gitk:4106
+msgid "Author:"
+msgstr "作者:"
+
+#: gitk:4107
+msgid "Committer:"
+msgstr "提交者:"
+
+#: gitk:4108
+msgid "Commit Message:"
+msgstr "提交信息:"
+
+#: gitk:4109
+msgid "Matches all Commit Info criteria"
+msgstr "匹配所有提交信息标准"
+
+#: gitk:4110
+msgid "Matches no Commit Info criteria"
+msgstr "匹配无提交信息标准"
+
+#: gitk:4111
+msgid "Changes to Files:"
+msgstr "文件修改列表:"
+
+#: gitk:4112
+msgid "Fixed String"
+msgstr "固定字符串"
+
+#: gitk:4113
+msgid "Regular Expression"
+msgstr "正则表达式:"
+
+#: gitk:4114
+msgid "Search string:"
+msgstr "搜索字符串:"
+
+#: gitk:4115
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr "提交日期 (\"2星期之前\", \"2009-03-17 15:27:38\", \"5月 17, 2009 15:27:38\"):"
+
+#: gitk:4116
+msgid "Since:"
+msgstr "自:"
+
+#: gitk:4117
+msgid "Until:"
+msgstr "到:"
+
+#: gitk:4118
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "限制 且/或 跳过一定数量的版本(正整数):"
+
+#: gitk:4119
+msgid "Number to show:"
+msgstr "显示数量:"
+
+#: gitk:4120
+msgid "Number to skip:"
+msgstr "跳过数量:"
+
+#: gitk:4121
+msgid "Miscellaneous options:"
+msgstr "其他选项:"
+
+#: gitk:4122
+msgid "Strictly sort by date"
+msgstr "严格按日期整理"
+
+#: gitk:4123
+msgid "Mark branch sides"
+msgstr "标记分支边界"
+
+#: gitk:4124
+msgid "Limit to first parent"
+msgstr "限制到第一个父提交"
+
+#: gitk:4125
+msgid "Simple history"
+msgstr "简易历史"
+
+#: gitk:4126
+msgid "Additional arguments to git log:"
+msgstr "git log命令的额外参数:"
+
+#: gitk:4127
+msgid "Enter files and directories to include, one per line:"
+msgstr "输入文件和文件夹来引用,每行一个:"
+
+#: gitk:4128
+msgid "Command to generate more commits to include:"
+msgstr "命令产生更多的提交来引用:"
+
+#: gitk:4252
+msgid "Gitk: edit view"
+msgstr "Gitk: 编辑视图"
+
+#: gitk:4260
+msgid "-- criteria for selecting revisions"
+msgstr "-- 用来选择版本的规则"
+
+#: gitk:4265
+msgid "View Name"
+msgstr "视图名称"
+
+#: gitk:4340
+msgid "Apply (F5)"
+msgstr "应用(F5)"
+
+#: gitk:4378
+msgid "Error in commit selection arguments:"
+msgstr "提交选择参数错误:"
+
+#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525
+msgid "None"
+msgstr "无"
+
+#: gitk:5045 gitk:5050
+msgid "Descendant"
+msgstr "子提交"
+
+#: gitk:5046
+msgid "Not descendant"
+msgstr "非子提交"
+
+#: gitk:5053 gitk:5058
+msgid "Ancestor"
+msgstr "父提交"
+
+#: gitk:5054
+msgid "Not ancestor"
+msgstr "非父提交"
+
+#: gitk:5348
+msgid "Local changes checked in to index but not committed"
+msgstr "已添加到索引但未提交的修改"
+
+#: gitk:5384
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "未添加到索引且未提交的修改"
+
+#: gitk:7158
+msgid "and many more"
+msgstr "更多"
+
+#: gitk:7161
+msgid "many"
+msgstr "很多"
+
+#: gitk:7352
+msgid "Tags:"
+msgstr "Tags:"
+
+#: gitk:7369 gitk:7375 gitk:8854
+msgid "Parent"
+msgstr "父节点"
+
+#: gitk:7380
+msgid "Child"
+msgstr "子节点"
+
+#: gitk:7389
+msgid "Branch"
+msgstr "分支"
+
+#: gitk:7392
+msgid "Follows"
+msgstr "之后的tag"
+
+#: gitk:7395
+msgid "Precedes"
+msgstr "之前的tag"
+
+#: gitk:7990
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "获取差异错误:%s"
+
+#: gitk:8679
+msgid "Goto:"
+msgstr "转到:"
+
+#: gitk:8700
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "短格式的SHA1提交号%s不明确、有歧义"
+
+#: gitk:8707
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "版本%s未知"
+
+#: gitk:8717
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "提交号(SHA1 id)%s未知"
+
+#: gitk:8719
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "版本%s不在当前视图中"
+
+#: gitk:8861 gitk:8876
+msgid "Date"
+msgstr "日期"
+
+#: gitk:8864
+msgid "Children"
+msgstr "子节点"
+
+#: gitk:8927
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "重置分支%s到此处"
+
+#: gitk:8929
+msgid "Detached head: can't reset"
+msgstr "分离的头(head):不能重置(reset)"
+
+#: gitk:9034 gitk:9040
+msgid "Skipping merge commit "
+msgstr "跳过合并提交"
+
+#: gitk:9049 gitk:9054
+msgid "Error getting patch ID for "
+msgstr "获取补丁ID出错"
+
+#: gitk:9050 gitk:9055
+msgid " - stopping\n"
+msgstr " — 停止中\n"
+
+#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094
+msgid "Commit "
+msgstr "提交"
+
+#: gitk:9064
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr " 是相同的补丁(patch)\n       "
+
+#: gitk:9072
+msgid ""
+" differs from\n"
+"       "
+msgstr " 差异来自\n       "
+
+#: gitk:9074
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr "提交的差异(Diff):\n\n"
+
+#: gitk:9086 gitk:9095
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr "有%s子节点 — 停止中\n"
+
+#: gitk:9114
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "写入提交到文件出错:%s"
+
+#: gitk:9120
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "比较提交差异出错:%s"
+
+#: gitk:9166
+msgid "Top"
+msgstr "顶部"
+
+#: gitk:9167
+msgid "From"
+msgstr "从"
+
+#: gitk:9172
+msgid "To"
+msgstr "到"
+
+#: gitk:9196
+msgid "Generate patch"
+msgstr "生成补丁(patch)"
+
+#: gitk:9198
+msgid "From:"
+msgstr "从:"
+
+#: gitk:9207
+msgid "To:"
+msgstr "到:"
+
+#: gitk:9216
+msgid "Reverse"
+msgstr "反向(Reverse)"
+
+#: gitk:9218 gitk:9428
+msgid "Output file:"
+msgstr "输出文件:"
+
+#: gitk:9224
+msgid "Generate"
+msgstr "生成"
+
+#: gitk:9262
+msgid "Error creating patch:"
+msgstr "创建补丁(patch)出错:"
+
+#: gitk:9285 gitk:9416 gitk:9504
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:9294
+msgid "Tag name:"
+msgstr "Tag名称:"
+
+#: gitk:9297
+msgid "Tag message is optional"
+msgstr "Tag信息是可选的"
+
+#: gitk:9299
+msgid "Tag message:"
+msgstr "Tag信息:"
+
+#: gitk:9303 gitk:9474
+msgid "Create"
+msgstr "创建"
+
+#: gitk:9321
+msgid "No tag name specified"
+msgstr "未指定tag名称"
+
+#: gitk:9325
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Tag\"%s\"已经存在"
+
+#: gitk:9335
+msgid "Error creating tag:"
+msgstr "创建tag出错:"
+
+#: gitk:9425
+msgid "Command:"
+msgstr "命令:"
+
+#: gitk:9433
+msgid "Write"
+msgstr "写入"
+
+#: gitk:9451
+msgid "Error writing commit:"
+msgstr "写入提交出错:"
+
+#: gitk:9473
+msgid "Create branch"
+msgstr "创建分支"
+
+#: gitk:9489
+#, tcl-format
+msgid "Rename branch %s"
+msgstr "重命名分支%s"
+
+#: gitk:9490
+msgid "Rename"
+msgstr "重命名"
+
+#: gitk:9514
+msgid "Name:"
+msgstr "名称:"
+
+#: gitk:9538
+msgid "Please specify a name for the new branch"
+msgstr "请指定新分支的名称"
+
+#: gitk:9543
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "分支\"%s\"已经存在。覆盖它?"
+
+#: gitk:9587
+msgid "Please specify a new name for the branch"
+msgstr "请重新指定新分支的名称"
+
+#: gitk:9650
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "提交%s已经存在于分支%s。确定重新应用它?"
+
+#: gitk:9655
+msgid "Cherry-picking"
+msgstr "打补丁中(Cherry-picking)"
+
+#: gitk:9664
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr "打补丁(Cherry-pick)失败,因为本地修改了文件\"%s\"。\n请提交(commit)、重置(reset)或暂存(stash)修改后重试。"
+
+#: gitk:9670
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr "打补丁(Cherry-pick)失败因为合并冲突。\n你是否希望运行git citool 来解决冲突?"
+
+#: gitk:9686 gitk:9744
+msgid "No changes committed"
+msgstr "无已经提交的修改"
+
+#: gitk:9713
+#, tcl-format
+msgid "Commit %s is not included in branch %s -- really revert it?"
+msgstr "提交%s不包含在分支%s中,确认回滚(revert)它?"
+
+#: gitk:9718
+msgid "Reverting"
+msgstr "回滚中(Reverting)"
+
+#: gitk:9726
+#, tcl-format
+msgid ""
+"Revert failed because of local changes to the following files:%s Please "
+"commit, reset or stash  your changes and try again."
+msgstr "回滚(revert)失败,因为如下的本地文件修改:%s\n请提交(commit)、重置(reset)或者暂存(stash)改变后重试。"
+
+#: gitk:9730
+msgid ""
+"Revert failed because of merge conflict.\n"
+" Do you wish to run git citool to resolve it?"
+msgstr "回滚(revert)失败,因为合并冲突。\n你是否希望运行git citool来解决冲突?"
+
+#: gitk:9773
+msgid "Confirm reset"
+msgstr "确认重置(reset)"
+
+#: gitk:9775
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "重置(reset)分支%s到%s?"
+
+#: gitk:9777
+msgid "Reset type:"
+msgstr "重置(reset)类型:"
+
+#: gitk:9780
+msgid "Soft: Leave working tree and index untouched"
+msgstr "软性:离开工作树,索引未改变"
+
+#: gitk:9783
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "混合:离开工作树(未改变),索引重置"
+
+#: gitk:9786
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr "硬性:重置工作树和索引\n(丢弃所有的本地修改)"
+
+#: gitk:9803
+msgid "Resetting"
+msgstr "重置中(Resetting)"
+
+#: gitk:9876
+#, tcl-format
+msgid "A local branch named %s exists already"
+msgstr "本地分支%s已经存在"
+
+#: gitk:9884
+msgid "Checking out"
+msgstr "检出中(Checking out)"
+
+#: gitk:9943
+msgid "Cannot delete the currently checked-out branch"
+msgstr "不能删除当前检出(checkout)分支"
+
+#: gitk:9949
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr "在分支%s上的提交不在其他任何分支上。\n确认删除分支%s?"
+
+#: gitk:9980
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Tags和头指针(heads):%s"
+
+#: gitk:9997
+msgid "Filter"
+msgstr "过滤器"
+
+#: gitk:10293
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr "读取提交拓扑信息出错;分支和之前/之后的tag信息将不能完成。"
+
+#: gitk:11270
+msgid "Tag"
+msgstr "标签(Tag)"
+
+#: gitk:11274
+msgid "Id"
+msgstr "Id"
+
+#: gitk:11357
+msgid "Gitk font chooser"
+msgstr "Gitk字体选择"
+
+#: gitk:11374
+msgid "B"
+msgstr "粗体"
+
+#: gitk:11377
+msgid "I"
+msgstr "斜体"
+
+#: gitk:11495
+msgid "Commit list display options"
+msgstr "提交列表展示选项"
+
+#: gitk:11498
+msgid "Maximum graph width (lines)"
+msgstr "最大图宽度(行数)"
+
+#: gitk:11502
+#, no-tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "最大图宽度(%窗口百分比)"
+
+#: gitk:11505
+msgid "Show local changes"
+msgstr "显示本地修改"
+
+#: gitk:11508
+msgid "Auto-select SHA1 (length)"
+msgstr "自动选择SHA1(长度)"
+
+#: gitk:11512
+msgid "Hide remote refs"
+msgstr "隐藏远程引用"
+
+#: gitk:11516
+msgid "Diff display options"
+msgstr "差异(Diff)展示选项"
+
+#: gitk:11518
+msgid "Tab spacing"
+msgstr "制表符宽度"
+
+#: gitk:11521
+msgid "Display nearby tags/heads"
+msgstr "显示临近的tags/heads"
+
+#: gitk:11524
+msgid "Maximum # tags/heads to show"
+msgstr "最大tags/heads展示数量"
+
+#: gitk:11527
+msgid "Limit diffs to listed paths"
+msgstr "diff中列出文件限制"
+
+#: gitk:11530
+msgid "Support per-file encodings"
+msgstr "单独文件编码支持"
+
+#: gitk:11536 gitk:11683
+msgid "External diff tool"
+msgstr "外部差异(diff)工具"
+
+#: gitk:11537
+msgid "Choose..."
+msgstr "选择..."
+
+#: gitk:11542
+msgid "General options"
+msgstr "常规选项"
+
+#: gitk:11545
+msgid "Use themed widgets"
+msgstr "使用主题小部件"
+
+#: gitk:11547
+msgid "(change requires restart)"
+msgstr "(需重启生效)"
+
+#: gitk:11549
+msgid "(currently unavailable)"
+msgstr "(当前不可用)"
+
+#: gitk:11560
+msgid "Colors: press to choose"
+msgstr "颜色:点击来选择"
+
+#: gitk:11563
+msgid "Interface"
+msgstr "界面"
+
+#: gitk:11564
+msgid "interface"
+msgstr "界面"
+
+#: gitk:11567
+msgid "Background"
+msgstr "背景"
+
+#: gitk:11568 gitk:11598
+msgid "background"
+msgstr "背景"
+
+#: gitk:11571
+msgid "Foreground"
+msgstr "前景"
+
+#: gitk:11572
+msgid "foreground"
+msgstr "前景"
+
+#: gitk:11575
+msgid "Diff: old lines"
+msgstr "差异(Diff):老代码行"
+
+#: gitk:11576
+msgid "diff old lines"
+msgstr "差异(diff)老代码行"
+
+#: gitk:11580
+msgid "Diff: new lines"
+msgstr "差异(Diff):新代码行"
+
+#: gitk:11581
+msgid "diff new lines"
+msgstr "差异(diff)新代码行"
+
+#: gitk:11585
+msgid "Diff: hunk header"
+msgstr "差异(Diff):补丁片段头信息"
+
+#: gitk:11587
+msgid "diff hunk header"
+msgstr "差异(diff)补丁片段头信息"
+
+#: gitk:11591
+msgid "Marked line bg"
+msgstr "已标记代码行背景"
+
+#: gitk:11593
+msgid "marked line background"
+msgstr "已标记代码行背景"
+
+#: gitk:11597
+msgid "Select bg"
+msgstr "选择背景"
+
+#: gitk:11606
+msgid "Fonts: press to choose"
+msgstr "字体:点击来选择"
+
+#: gitk:11608
+msgid "Main font"
+msgstr "主字体"
+
+#: gitk:11609
+msgid "Diff display font"
+msgstr "差异(Diff)显示字体"
+
+#: gitk:11610
+msgid "User interface font"
+msgstr "用户界面字体"
+
+#: gitk:11632
+msgid "Gitk preferences"
+msgstr "Gitk偏好设置"
+
+#: gitk:11641
+msgid "General"
+msgstr "常规"
+
+#: gitk:11642
+msgid "Colors"
+msgstr "颜色"
+
+#: gitk:11643
+msgid "Fonts"
+msgstr "字体"
+
+#: gitk:11693
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk:选择颜色用于%s"
+
+#: gitk:12206
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr "对不起,gitk不能运行在当前版本的Tcl/Tk中。\nGitk运行需要最低版本为Tcl/Tk8.4。"
+
+#: gitk:12416
+msgid "Cannot find a git repository here."
+msgstr "在此位置未发现git仓库。"
+
+#: gitk:12463
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "不明确有歧义的参数\"%s\":版本和文件名称"
+
+#: gitk:12475
+msgid "Bad arguments to gitk:"
+msgstr "运行gitk参数错误:"
diff --git a/http.c b/http.c
index 27aa0a3192988cd0c272dab0e9f6cf52d538b6fa..938b9e55af435c46484a4fd54747a9c0bc2db0fe 100644 (file)
--- a/http.c
+++ b/http.c
@@ -513,9 +513,11 @@ static void set_proxyauth_name_password(CURL *result)
 #else
                struct strbuf s = STRBUF_INIT;
 
-               strbuf_addstr_urlencode(&s, proxy_auth.username, 1);
+               strbuf_addstr_urlencode(&s, proxy_auth.username,
+                                       is_rfc3986_unreserved);
                strbuf_addch(&s, ':');
-               strbuf_addstr_urlencode(&s, proxy_auth.password, 1);
+               strbuf_addstr_urlencode(&s, proxy_auth.password,
+                                       is_rfc3986_unreserved);
                curl_proxyuserpwd = strbuf_detach(&s, NULL);
                curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd);
 #endif
diff --git a/http.h b/http.h
index b429f1cf042bbb61e97e41cd1aacb012c4db834e..5e0ad724f92f3c708d2b330bb689f6eef6bb8de4 100644 (file)
--- a/http.h
+++ b/http.h
 #if LIBCURL_VERSION_NUM < 0x070704
 #define curl_global_cleanup() do { /* nothing */ } while (0)
 #endif
+
 #if LIBCURL_VERSION_NUM < 0x070800
 #define curl_global_init(a) do { /* nothing */ } while (0)
+#elif LIBCURL_VERSION_NUM >= 0x070c00
+#define curl_global_init(a) curl_global_init_mem(a, xmalloc, free, \
+                                               xrealloc, xstrdup, xcalloc)
 #endif
 
 #if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000)
index 3aff1849e7d5c7dba5c14b2fa34f7af64064a42d..9010e00950b379501a6607d660497e94f8412d95 100644 (file)
@@ -737,6 +737,38 @@ static struct line_log_data *lookup_line_range(struct rev_info *revs,
        return ret;
 }
 
+static int same_paths_in_pathspec_and_range(struct pathspec *pathspec,
+                                           struct line_log_data *range)
+{
+       int i;
+       struct line_log_data *r;
+
+       for (i = 0, r = range; i < pathspec->nr && r; i++, r = r->next)
+               if (strcmp(pathspec->items[i].match, r->path))
+                       return 0;
+       if (i < pathspec->nr || r)
+               /* different number of pathspec items and ranges */
+               return 0;
+
+       return 1;
+}
+
+static void parse_pathspec_from_ranges(struct pathspec *pathspec,
+                                      struct line_log_data *range)
+{
+       struct line_log_data *r;
+       struct argv_array array = ARGV_ARRAY_INIT;
+       const char **paths;
+
+       for (r = range; r; r = r->next)
+               argv_array_push(&array, r->path);
+       paths = argv_array_detach(&array);
+
+       parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL, "", paths);
+       /* strings are now owned by pathspec */
+       free(paths);
+}
+
 void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args)
 {
        struct commit *commit = NULL;
@@ -746,20 +778,7 @@ void line_log_init(struct rev_info *rev, const char *prefix, struct string_list
        range = parse_lines(rev->diffopt.repo, commit, prefix, args);
        add_line_range(rev, commit, range);
 
-       if (!rev->diffopt.detect_rename) {
-               struct line_log_data *r;
-               struct argv_array array = ARGV_ARRAY_INIT;
-               const char **paths;
-
-               for (r = range; r; r = r->next)
-                       argv_array_push(&array, r->path);
-               paths = argv_array_detach(&array);
-
-               parse_pathspec(&rev->diffopt.pathspec, 0,
-                              PATHSPEC_PREFER_FULL, "", paths);
-               /* strings are now owned by pathspec */
-               free(paths);
-       }
+       parse_pathspec_from_ranges(&rev->diffopt.pathspec, range);
 }
 
 static void move_diff_queue(struct diff_queue_struct *dst,
@@ -817,15 +836,29 @@ static void queue_diffs(struct line_log_data *range,
                        struct diff_queue_struct *queue,
                        struct commit *commit, struct commit *parent)
 {
+       struct object_id *tree_oid, *parent_tree_oid;
+
        assert(commit);
 
+       tree_oid = get_commit_tree_oid(commit);
+       parent_tree_oid = parent ? get_commit_tree_oid(parent) : NULL;
+
+       if (opt->detect_rename &&
+           !same_paths_in_pathspec_and_range(&opt->pathspec, range)) {
+               clear_pathspec(&opt->pathspec);
+               parse_pathspec_from_ranges(&opt->pathspec, range);
+       }
        DIFF_QUEUE_CLEAR(&diff_queued_diff);
-       diff_tree_oid(parent ? get_commit_tree_oid(parent) : NULL,
-                     get_commit_tree_oid(commit), "", opt);
-       if (opt->detect_rename) {
+       diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
+       if (opt->detect_rename && diff_might_be_rename()) {
+               /* must look at the full tree diff to detect renames */
+               clear_pathspec(&opt->pathspec);
+               DIFF_QUEUE_CLEAR(&diff_queued_diff);
+
+               diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
+
                filter_diffs_for_paths(range, 1);
-               if (diff_might_be_rename())
-                       diffcore_std(opt);
+               diffcore_std(opt);
                filter_diffs_for_paths(range, 0);
        }
        move_diff_queue(queue, &diff_queued_diff);
index 1cb20c659c82b151a652da0528d0673ac629cc6c..4d88bfe64ad230b4055a77b79432ae2db8af87f6 100644 (file)
@@ -6,6 +6,14 @@
 #include "list-objects.h"
 #include "list-objects-filter.h"
 #include "list-objects-filter-options.h"
+#include "promisor-remote.h"
+#include "trace.h"
+#include "url.h"
+
+static int parse_combine_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg,
+       struct strbuf *errbuf);
 
 /*
  * Parse value of the argument to the "filter" keyword.
@@ -29,16 +37,11 @@ static int gently_parse_list_objects_filter(
 {
        const char *v0;
 
-       if (filter_options->choice) {
-               if (errbuf) {
-                       strbuf_addstr(
-                               errbuf,
-                               _("multiple filter-specs cannot be combined"));
-               }
-               return 1;
-       }
+       if (!arg)
+               return 0;
 
-       filter_options->filter_spec = strdup(arg);
+       if (filter_options->choice)
+               BUG("filter_options already populated");
 
        if (!strcmp(arg, "blob:none")) {
                filter_options->choice = LOFC_BLOB_NONE;
@@ -52,11 +55,7 @@ static int gently_parse_list_objects_filter(
 
        } else if (skip_prefix(arg, "tree:", &v0)) {
                if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) {
-                       if (errbuf) {
-                               strbuf_addstr(
-                                       errbuf,
-                                       _("expected 'tree:<depth>'"));
-                       }
+                       strbuf_addstr(errbuf, _("expected 'tree:<depth>'"));
                        return 1;
                }
                filter_options->choice = LOFC_TREE_DEPTH;
@@ -84,103 +83,298 @@ static int gently_parse_list_objects_filter(
                                _("sparse:path filters support has been dropped"));
                }
                return 1;
+
+       } else if (skip_prefix(arg, "combine:", &v0)) {
+               return parse_combine_filter(filter_options, v0, errbuf);
+
        }
        /*
         * Please update _git_fetch() in git-completion.bash when you
         * add new filters
         */
 
-       if (errbuf)
-               strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
+       strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
 
        memset(filter_options, 0, sizeof(*filter_options));
        return 1;
 }
 
-int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
-                             const char *arg)
+static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?";
+
+static int has_reserved_character(
+       struct strbuf *sub_spec, struct strbuf *errbuf)
 {
-       struct strbuf buf = STRBUF_INIT;
-       if (gently_parse_list_objects_filter(filter_options, arg, &buf))
-               die("%s", buf.buf);
+       const char *c = sub_spec->buf;
+       while (*c) {
+               if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) {
+                       strbuf_addf(
+                               errbuf,
+                               _("must escape char in sub-filter-spec: '%c'"),
+                               *c);
+                       return 1;
+               }
+               c++;
+       }
+
        return 0;
 }
 
+static int parse_combine_subfilter(
+       struct list_objects_filter_options *filter_options,
+       struct strbuf *subspec,
+       struct strbuf *errbuf)
+{
+       size_t new_index = filter_options->sub_nr;
+       char *decoded;
+       int result;
+
+       ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1,
+                     filter_options->sub_alloc);
+
+       decoded = url_percent_decode(subspec->buf);
+
+       result = has_reserved_character(subspec, errbuf) ||
+               gently_parse_list_objects_filter(
+                       &filter_options->sub[new_index], decoded, errbuf);
+
+       free(decoded);
+       return result;
+}
+
+static int parse_combine_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg,
+       struct strbuf *errbuf)
+{
+       struct strbuf **subspecs = strbuf_split_str(arg, '+', 0);
+       size_t sub;
+       int result = 0;
+
+       if (!subspecs[0]) {
+               strbuf_addstr(errbuf, _("expected something after combine:"));
+               result = 1;
+               goto cleanup;
+       }
+
+       for (sub = 0; subspecs[sub] && !result; sub++) {
+               if (subspecs[sub + 1]) {
+                       /*
+                        * This is not the last subspec. Remove trailing "+" so
+                        * we can parse it.
+                        */
+                       size_t last = subspecs[sub]->len - 1;
+                       assert(subspecs[sub]->buf[last] == '+');
+                       strbuf_remove(subspecs[sub], last, 1);
+               }
+               result = parse_combine_subfilter(
+                       filter_options, subspecs[sub], errbuf);
+       }
+
+       filter_options->choice = LOFC_COMBINE;
+
+cleanup:
+       strbuf_list_free(subspecs);
+       if (result) {
+               list_objects_filter_release(filter_options);
+               memset(filter_options, 0, sizeof(*filter_options));
+       }
+       return result;
+}
+
+static int allow_unencoded(char ch)
+{
+       if (ch <= ' ' || ch == '%' || ch == '+')
+               return 0;
+       return !strchr(RESERVED_NON_WS, ch);
+}
+
+static void filter_spec_append_urlencode(
+       struct list_objects_filter_options *filter, const char *raw)
+{
+       struct strbuf buf = STRBUF_INIT;
+       strbuf_addstr_urlencode(&buf, raw, allow_unencoded);
+       trace_printf("Add to combine filter-spec: %s\n", buf.buf);
+       string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL));
+}
+
+/*
+ * Changes filter_options into an equivalent LOFC_COMBINE filter options
+ * instance. Does not do anything if filter_options is already LOFC_COMBINE.
+ */
+static void transform_to_combine_type(
+       struct list_objects_filter_options *filter_options)
+{
+       assert(filter_options->choice);
+       if (filter_options->choice == LOFC_COMBINE)
+               return;
+       {
+               const int initial_sub_alloc = 2;
+               struct list_objects_filter_options *sub_array =
+                       xcalloc(initial_sub_alloc, sizeof(*sub_array));
+               sub_array[0] = *filter_options;
+               memset(filter_options, 0, sizeof(*filter_options));
+               filter_options->sub = sub_array;
+               filter_options->sub_alloc = initial_sub_alloc;
+       }
+       filter_options->sub_nr = 1;
+       filter_options->choice = LOFC_COMBINE;
+       string_list_append(&filter_options->filter_spec, xstrdup("combine:"));
+       filter_spec_append_urlencode(
+               filter_options,
+               list_objects_filter_spec(&filter_options->sub[0]));
+       /*
+        * We don't need the filter_spec strings for subfilter specs, only the
+        * top level.
+        */
+       string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0);
+}
+
+void list_objects_filter_die_if_populated(
+       struct list_objects_filter_options *filter_options)
+{
+       if (filter_options->choice)
+               die(_("multiple filter-specs cannot be combined"));
+}
+
+void parse_list_objects_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg)
+{
+       struct strbuf errbuf = STRBUF_INIT;
+       int parse_error;
+
+       if (!filter_options->choice) {
+               string_list_append(&filter_options->filter_spec, xstrdup(arg));
+
+               parse_error = gently_parse_list_objects_filter(
+                       filter_options, arg, &errbuf);
+       } else {
+               /*
+                * Make filter_options an LOFC_COMBINE spec so we can trivially
+                * add subspecs to it.
+                */
+               transform_to_combine_type(filter_options);
+
+               string_list_append(&filter_options->filter_spec, xstrdup("+"));
+               filter_spec_append_urlencode(filter_options, arg);
+               ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1,
+                             filter_options->sub_alloc);
+
+               parse_error = gently_parse_list_objects_filter(
+                       &filter_options->sub[filter_options->sub_nr - 1], arg,
+                       &errbuf);
+       }
+       if (parse_error)
+               die("%s", errbuf.buf);
+}
+
 int opt_parse_list_objects_filter(const struct option *opt,
                                  const char *arg, int unset)
 {
        struct list_objects_filter_options *filter_options = opt->value;
 
-       if (unset || !arg) {
+       if (unset || !arg)
                list_objects_filter_set_no_filter(filter_options);
-               return 0;
+       else
+               parse_list_objects_filter(filter_options, arg);
+       return 0;
+}
+
+const char *list_objects_filter_spec(struct list_objects_filter_options *filter)
+{
+       if (!filter->filter_spec.nr)
+               BUG("no filter_spec available for this filter");
+       if (filter->filter_spec.nr != 1) {
+               struct strbuf concatted = STRBUF_INIT;
+               strbuf_add_separated_string_list(
+                       &concatted, "", &filter->filter_spec);
+               string_list_clear(&filter->filter_spec, /*free_util=*/0);
+               string_list_append(
+                       &filter->filter_spec, strbuf_detach(&concatted, NULL));
        }
 
-       return parse_list_objects_filter(filter_options, arg);
+       return filter->filter_spec.items[0].string;
 }
 
-void expand_list_objects_filter_spec(
-       const struct list_objects_filter_options *filter,
-       struct strbuf *expanded_spec)
+const char *expand_list_objects_filter_spec(
+       struct list_objects_filter_options *filter)
 {
-       strbuf_init(expanded_spec, strlen(filter->filter_spec));
-       if (filter->choice == LOFC_BLOB_LIMIT)
-               strbuf_addf(expanded_spec, "blob:limit=%lu",
+       if (filter->choice == LOFC_BLOB_LIMIT) {
+               struct strbuf expanded_spec = STRBUF_INIT;
+               strbuf_addf(&expanded_spec, "blob:limit=%lu",
                            filter->blob_limit_value);
-       else if (filter->choice == LOFC_TREE_DEPTH)
-               strbuf_addf(expanded_spec, "tree:%lu",
-                           filter->tree_exclude_depth);
-       else
-               strbuf_addstr(expanded_spec, filter->filter_spec);
+               string_list_clear(&filter->filter_spec, /*free_util=*/0);
+               string_list_append(
+                       &filter->filter_spec,
+                       strbuf_detach(&expanded_spec, NULL));
+       }
+
+       return list_objects_filter_spec(filter);
 }
 
 void list_objects_filter_release(
        struct list_objects_filter_options *filter_options)
 {
-       free(filter_options->filter_spec);
+       size_t sub;
+
+       if (!filter_options)
+               return;
+       string_list_clear(&filter_options->filter_spec, /*free_util=*/0);
        free(filter_options->sparse_oid_value);
+       for (sub = 0; sub < filter_options->sub_nr; sub++)
+               list_objects_filter_release(&filter_options->sub[sub]);
+       free(filter_options->sub);
        memset(filter_options, 0, sizeof(*filter_options));
 }
 
 void partial_clone_register(
        const char *remote,
-       const struct list_objects_filter_options *filter_options)
+       struct list_objects_filter_options *filter_options)
 {
-       /*
-        * Record the name of the partial clone remote in the
-        * config and in the global variable -- the latter is
-        * used throughout to indicate that partial clone is
-        * enabled and to expect missing objects.
-        */
-       if (repository_format_partial_clone &&
-           *repository_format_partial_clone &&
-           strcmp(remote, repository_format_partial_clone))
-               die(_("cannot change partial clone promisor remote"));
+       char *cfg_name;
+       char *filter_name;
 
-       git_config_set("core.repositoryformatversion", "1");
-       git_config_set("extensions.partialclone", remote);
+       /* Check if it is already registered */
+       if (!promisor_remote_find(remote)) {
+               git_config_set("core.repositoryformatversion", "1");
 
-       repository_format_partial_clone = xstrdup(remote);
+               /* Add promisor config for the remote */
+               cfg_name = xstrfmt("remote.%s.promisor", remote);
+               git_config_set(cfg_name, "true");
+               free(cfg_name);
+       }
 
        /*
         * Record the initial filter-spec in the config as
         * the default for subsequent fetches from this remote.
         */
-       core_partial_clone_filter_default =
-               xstrdup(filter_options->filter_spec);
-       git_config_set("core.partialclonefilter",
-                      core_partial_clone_filter_default);
+       filter_name = xstrfmt("remote.%s.partialclonefilter", remote);
+       /* NEEDSWORK: 'expand' result leaking??? */
+       git_config_set(filter_name,
+                      expand_list_objects_filter_spec(filter_options));
+       free(filter_name);
+
+       /* Make sure the config info are reset */
+       promisor_remote_reinit();
 }
 
 void partial_clone_get_default_filter_spec(
-       struct list_objects_filter_options *filter_options)
+       struct list_objects_filter_options *filter_options,
+       const char *remote)
 {
+       struct promisor_remote *promisor = promisor_remote_find(remote);
+       struct strbuf errbuf = STRBUF_INIT;
+
        /*
         * Parse default value, but silently ignore it if it is invalid.
         */
-       if (!core_partial_clone_filter_default)
+       if (!promisor)
                return;
+
+       string_list_append(&filter_options->filter_spec,
+                          promisor->partial_clone_filter);
        gently_parse_list_objects_filter(filter_options,
-                                        core_partial_clone_filter_default,
-                                        NULL);
+                                        promisor->partial_clone_filter,
+                                        &errbuf);
+       strbuf_release(&errbuf);
 }
index c54f0000fbade5608e81345a9c5dbd54137d352d..b63c5ee1a368aa4d34cda8bbc41fce905692495b 100644 (file)
@@ -2,7 +2,7 @@
 #define LIST_OBJECTS_FILTER_OPTIONS_H
 
 #include "parse-options.h"
-#include "strbuf.h"
+#include "string-list.h"
 
 /*
  * The list of defined filters for list-objects.
@@ -13,6 +13,7 @@ enum list_objects_filter_choice {
        LOFC_BLOB_LIMIT,
        LOFC_TREE_DEPTH,
        LOFC_SPARSE_OID,
+       LOFC_COMBINE,
        LOFC__COUNT /* must be last */
 };
 
@@ -23,8 +24,10 @@ struct list_objects_filter_options {
         * commands that launch filtering sub-processes, or for communication
         * over the network, don't use this value; use the result of
         * expand_list_objects_filter_spec() instead.
+        * To get the raw filter spec given by the user, use the result of
+        * list_objects_filter_spec().
         */
-       char *filter_spec;
+       struct string_list filter_spec;
 
        /*
         * 'choice' is determined by parsing the filter-spec.  This indicates
@@ -38,19 +41,40 @@ struct list_objects_filter_options {
        unsigned int no_filter : 1;
 
        /*
-        * Parsed values (fields) from within the filter-spec.  These are
-        * choice-specific; not all values will be defined for any given
-        * choice.
+        * BEGIN choice-specific parsed values from within the filter-spec. Only
+        * some values will be defined for any given choice.
         */
+
        struct object_id *sparse_oid_value;
        unsigned long blob_limit_value;
        unsigned long tree_exclude_depth;
+
+       /* LOFC_COMBINE values */
+
+       /* This array contains all the subfilters which this filter combines. */
+       size_t sub_nr, sub_alloc;
+       struct list_objects_filter_options *sub;
+
+       /*
+        * END choice-specific parsed values.
+        */
 };
 
 /* Normalized command line arguments */
 #define CL_ARG__FILTER "filter"
 
-int parse_list_objects_filter(
+void list_objects_filter_die_if_populated(
+       struct list_objects_filter_options *filter_options);
+
+/*
+ * Parses the filter spec string given by arg and either (1) simply places the
+ * result in filter_options if it is not yet populated or (2) combines it with
+ * the filter already in filter_options if it is already populated. In the case
+ * of (2), the filter specs are combined as if specified with 'combine:'.
+ *
+ * Dies and prints a user-facing message if an error occurs.
+ */
+void parse_list_objects_filter(
        struct list_objects_filter_options *filter_options,
        const char *arg);
 
@@ -65,13 +89,22 @@ int opt_parse_list_objects_filter(const struct option *opt,
 /*
  * Translates abbreviated numbers in the filter's filter_spec into their
  * fully-expanded forms (e.g., "limit:blob=1k" becomes "limit:blob=1024").
+ * Returns a string owned by the list_objects_filter_options object.
  *
- * This form should be used instead of the raw filter_spec field when
- * communicating with a remote process or subprocess.
+ * This form should be used instead of the raw list_objects_filter_spec()
+ * value when communicating with a remote process or subprocess.
+ */
+const char *expand_list_objects_filter_spec(
+       struct list_objects_filter_options *filter);
+
+/*
+ * Returns the filter spec string more or less in the form as the user
+ * entered it. This form of the filter_spec can be used in user-facing
+ * messages.  Returns a string owned by the list_objects_filter_options
+ * object.
  */
-void expand_list_objects_filter_spec(
-       const struct list_objects_filter_options *filter,
-       struct strbuf *expanded_spec);
+const char *list_objects_filter_spec(
+       struct list_objects_filter_options *filter);
 
 void list_objects_filter_release(
        struct list_objects_filter_options *filter_options);
@@ -85,8 +118,9 @@ static inline void list_objects_filter_set_no_filter(
 
 void partial_clone_register(
        const char *remote,
-       const struct list_objects_filter_options *filter_options);
-void partial_clone_get_default_filter_spec(
        struct list_objects_filter_options *filter_options);
+void partial_clone_get_default_filter_spec(
+       struct list_objects_filter_options *filter_options,
+       const char *remote);
 
 #endif /* LIST_OBJECTS_FILTER_OPTIONS_H */
index 36e1f774bcfc50d0475ad835464cec092314eb79..d664264d65947f140d04d25d25db54af23b4a2d3 100644 (file)
  */
 #define FILTER_SHOWN_BUT_REVISIT (1<<21)
 
-/*
- * A filter for list-objects to omit ALL blobs from the traversal.
- * And to OPTIONALLY collect a list of the omitted OIDs.
- */
-struct filter_blobs_none_data {
+struct subfilter {
+       struct filter *filter;
+       struct oidset seen;
+       struct oidset omits;
+       struct object_id skip_tree;
+       unsigned is_skipping_tree : 1;
+};
+
+struct filter {
+       enum list_objects_filter_result (*filter_object_fn)(
+               struct repository *r,
+               enum list_objects_filter_situation filter_situation,
+               struct object *obj,
+               const char *pathname,
+               const char *filename,
+               struct oidset *omits,
+               void *filter_data);
+
+       /*
+        * Optional. If this function is supplied and the filter needs
+        * to collect omits, then this function is called once before
+        * free_fn is called.
+        *
+        * This is required because the following two conditions hold:
+        *
+        *   a. A tree filter can add and remove objects as an object
+        *      graph is traversed.
+        *   b. A combine filter's omit set is the union of all its
+        *      subfilters, which may include tree: filters.
+        *
+        * As such, the omits sets must be separate sets, and can only
+        * be unioned after the traversal is completed.
+        */
+       void (*finalize_omits_fn)(struct oidset *omits, void *filter_data);
+
+       void (*free_fn)(void *filter_data);
+
+       void *filter_data;
+
+       /* If non-NULL, the filter collects a list of the omitted OIDs here. */
        struct oidset *omits;
 };
 
@@ -40,10 +75,9 @@ static enum list_objects_filter_result filter_blobs_none(
        struct object *obj,
        const char *pathname,
        const char *filename,
+       struct oidset *omits,
        void *filter_data_)
 {
-       struct filter_blobs_none_data *filter_data = filter_data_;
-
        switch (filter_situation) {
        default:
                BUG("unknown filter_situation: %d", filter_situation);
@@ -61,24 +95,18 @@ static enum list_objects_filter_result filter_blobs_none(
                assert(obj->type == OBJ_BLOB);
                assert((obj->flags & SEEN) == 0);
 
-               if (filter_data->omits)
-                       oidset_insert(filter_data->omits, &obj->oid);
+               if (omits)
+                       oidset_insert(omits, &obj->oid);
                return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
        }
 }
 
-static void *filter_blobs_none__init(
-       struct oidset *omitted,
+static void filter_blobs_none__init(
        struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn)
+       struct filter *filter)
 {
-       struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d));
-       d->omits = omitted;
-
-       *filter_fn = filter_blobs_none;
-       *filter_free_fn = free;
-       return d;
+       filter->filter_object_fn = filter_blobs_none;
+       filter->free_fn = free;
 }
 
 /*
@@ -86,8 +114,6 @@ static void *filter_blobs_none__init(
  * Can OPTIONALLY collect a list of the omitted OIDs.
  */
 struct filter_trees_depth_data {
-       struct oidset *omits;
-
        /*
         * Maps trees to the minimum depth at which they were seen. It is not
         * necessary to re-traverse a tree at deeper or equal depths than it has
@@ -110,16 +136,16 @@ struct seen_map_entry {
 /* Returns 1 if the oid was in the omits set before it was invoked. */
 static int filter_trees_update_omits(
        struct object *obj,
-       struct filter_trees_depth_data *filter_data,
+       struct oidset *omits,
        int include_it)
 {
-       if (!filter_data->omits)
+       if (!omits)
                return 0;
 
        if (include_it)
-               return oidset_remove(filter_data->omits, &obj->oid);
+               return oidset_remove(omits, &obj->oid);
        else
-               return oidset_insert(filter_data->omits, &obj->oid);
+               return oidset_insert(omits, &obj->oid);
 }
 
 static enum list_objects_filter_result filter_trees_depth(
@@ -128,6 +154,7 @@ static enum list_objects_filter_result filter_trees_depth(
        struct object *obj,
        const char *pathname,
        const char *filename,
+       struct oidset *omits,
        void *filter_data_)
 {
        struct filter_trees_depth_data *filter_data = filter_data_;
@@ -152,7 +179,7 @@ static enum list_objects_filter_result filter_trees_depth(
                return LOFR_ZERO;
 
        case LOFS_BLOB:
-               filter_trees_update_omits(obj, filter_data, include_it);
+               filter_trees_update_omits(obj, omits, include_it);
                return include_it ? LOFR_MARK_SEEN | LOFR_DO_SHOW : LOFR_ZERO;
 
        case LOFS_BEGIN_TREE:
@@ -173,12 +200,12 @@ static enum list_objects_filter_result filter_trees_depth(
                        filter_res = LOFR_SKIP_TREE;
                } else {
                        int been_omitted = filter_trees_update_omits(
-                               obj, filter_data, include_it);
+                               obj, omits, include_it);
                        seen_info->depth = filter_data->current_depth;
 
                        if (include_it)
                                filter_res = LOFR_DO_SHOW;
-                       else if (filter_data->omits && !been_omitted)
+                       else if (omits && !been_omitted)
                                /*
                                 * Must update omit information of children
                                 * recursively; they have not been omitted yet.
@@ -201,21 +228,18 @@ static void filter_trees_free(void *filter_data) {
        free(d);
 }
 
-static void *filter_trees_depth__init(
-       struct oidset *omitted,
+static void filter_trees_depth__init(
        struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn)
+       struct filter *filter)
 {
        struct filter_trees_depth_data *d = xcalloc(1, sizeof(*d));
-       d->omits = omitted;
        oidmap_init(&d->seen_at_depth, 0);
        d->exclude_depth = filter_options->tree_exclude_depth;
        d->current_depth = 0;
 
-       *filter_fn = filter_trees_depth;
-       *filter_free_fn = filter_trees_free;
-       return d;
+       filter->filter_data = d;
+       filter->filter_object_fn = filter_trees_depth;
+       filter->free_fn = filter_trees_free;
 }
 
 /*
@@ -223,7 +247,6 @@ static void *filter_trees_depth__init(
  * And to OPTIONALLY collect a list of the omitted OIDs.
  */
 struct filter_blobs_limit_data {
-       struct oidset *omits;
        unsigned long max_bytes;
 };
 
@@ -233,6 +256,7 @@ static enum list_objects_filter_result filter_blobs_limit(
        struct object *obj,
        const char *pathname,
        const char *filename,
+       struct oidset *omits,
        void *filter_data_)
 {
        struct filter_blobs_limit_data *filter_data = filter_data_;
@@ -270,30 +294,27 @@ static enum list_objects_filter_result filter_blobs_limit(
                if (object_length < filter_data->max_bytes)
                        goto include_it;
 
-               if (filter_data->omits)
-                       oidset_insert(filter_data->omits, &obj->oid);
+               if (omits)
+                       oidset_insert(omits, &obj->oid);
                return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
        }
 
 include_it:
-       if (filter_data->omits)
-               oidset_remove(filter_data->omits, &obj->oid);
+       if (omits)
+               oidset_remove(omits, &obj->oid);
        return LOFR_MARK_SEEN | LOFR_DO_SHOW;
 }
 
-static void *filter_blobs_limit__init(
-       struct oidset *omitted,
+static void filter_blobs_limit__init(
        struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn)
+       struct filter *filter)
 {
        struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d));
-       d->omits = omitted;
        d->max_bytes = filter_options->blob_limit_value;
 
-       *filter_fn = filter_blobs_limit;
-       *filter_free_fn = free;
-       return d;
+       filter->filter_data = d;
+       filter->filter_object_fn = filter_blobs_limit;
+       filter->free_fn = free;
 }
 
 /*
@@ -326,7 +347,6 @@ struct frame {
 };
 
 struct filter_sparse_data {
-       struct oidset *omits;
        struct exclude_list el;
 
        size_t nr, alloc;
@@ -339,6 +359,7 @@ static enum list_objects_filter_result filter_sparse(
        struct object *obj,
        const char *pathname,
        const char *filename,
+       struct oidset *omits,
        void *filter_data_)
 {
        struct filter_sparse_data *filter_data = filter_data_;
@@ -420,8 +441,8 @@ static enum list_objects_filter_result filter_sparse(
                if (val < 0)
                        val = frame->defval;
                if (val > 0) {
-                       if (filter_data->omits)
-                               oidset_remove(filter_data->omits, &obj->oid);
+                       if (omits)
+                               oidset_remove(omits, &obj->oid);
                        return LOFR_MARK_SEEN | LOFR_DO_SHOW;
                }
 
@@ -435,8 +456,8 @@ static enum list_objects_filter_result filter_sparse(
                 * Leave the LOFR_ bits unset so that if the blob appears
                 * again in the traversal, we will be asked again.
                 */
-               if (filter_data->omits)
-                       oidset_insert(filter_data->omits, &obj->oid);
+               if (omits)
+                       oidset_insert(omits, &obj->oid);
 
                /*
                 * Remember that at least 1 blob in this tree was
@@ -456,14 +477,11 @@ static void filter_sparse_free(void *filter_data)
        free(d);
 }
 
-static void *filter_sparse_oid__init(
-       struct oidset *omitted,
+static void filter_sparse_oid__init(
        struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn)
+       struct filter *filter)
 {
        struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
-       d->omits = omitted;
        if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value,
                                           NULL, 0, &d->el) < 0)
                die("could not load filter specification");
@@ -473,16 +491,147 @@ static void *filter_sparse_oid__init(
        d->array_frame[d->nr].child_prov_omit = 0;
        d->nr++;
 
-       *filter_fn = filter_sparse;
-       *filter_free_fn = filter_sparse_free;
-       return d;
+       filter->filter_data = d;
+       filter->filter_object_fn = filter_sparse;
+       filter->free_fn = filter_sparse_free;
 }
 
-typedef void *(*filter_init_fn)(
-       struct oidset *omitted,
+/* A filter which only shows objects shown by all sub-filters. */
+struct combine_filter_data {
+       struct subfilter *sub;
+       size_t nr;
+};
+
+static enum list_objects_filter_result process_subfilter(
+       struct repository *r,
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       struct subfilter *sub)
+{
+       enum list_objects_filter_result result;
+
+       /*
+        * Check and update is_skipping_tree before oidset_contains so
+        * that is_skipping_tree gets unset even when the object is
+        * marked as seen.  As of this writing, no filter uses
+        * LOFR_MARK_SEEN on trees that also uses LOFR_SKIP_TREE, so the
+        * ordering is only theoretically important. Be cautious if you
+        * change the order of the below checks and more filters have
+        * been added!
+        */
+       if (sub->is_skipping_tree) {
+               if (filter_situation == LOFS_END_TREE &&
+                   oideq(&obj->oid, &sub->skip_tree))
+                       sub->is_skipping_tree = 0;
+               else
+                       return LOFR_ZERO;
+       }
+       if (oidset_contains(&sub->seen, &obj->oid))
+               return LOFR_ZERO;
+
+       result = list_objects_filter__filter_object(
+               r, filter_situation, obj, pathname, filename, sub->filter);
+
+       if (result & LOFR_MARK_SEEN)
+               oidset_insert(&sub->seen, &obj->oid);
+
+       if (result & LOFR_SKIP_TREE) {
+               sub->is_skipping_tree = 1;
+               sub->skip_tree = obj->oid;
+       }
+
+       return result;
+}
+
+static enum list_objects_filter_result filter_combine(
+       struct repository *r,
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       struct oidset *omits,
+       void *filter_data)
+{
+       struct combine_filter_data *d = filter_data;
+       enum list_objects_filter_result combined_result =
+               LOFR_DO_SHOW | LOFR_MARK_SEEN | LOFR_SKIP_TREE;
+       size_t sub;
+
+       for (sub = 0; sub < d->nr; sub++) {
+               enum list_objects_filter_result sub_result = process_subfilter(
+                       r, filter_situation, obj, pathname, filename,
+                       &d->sub[sub]);
+               if (!(sub_result & LOFR_DO_SHOW))
+                       combined_result &= ~LOFR_DO_SHOW;
+               if (!(sub_result & LOFR_MARK_SEEN))
+                       combined_result &= ~LOFR_MARK_SEEN;
+               if (!d->sub[sub].is_skipping_tree)
+                       combined_result &= ~LOFR_SKIP_TREE;
+       }
+
+       return combined_result;
+}
+
+static void filter_combine__free(void *filter_data)
+{
+       struct combine_filter_data *d = filter_data;
+       size_t sub;
+       for (sub = 0; sub < d->nr; sub++) {
+               list_objects_filter__free(d->sub[sub].filter);
+               oidset_clear(&d->sub[sub].seen);
+               if (d->sub[sub].omits.set.size)
+                       BUG("expected oidset to be cleared already");
+       }
+       free(d->sub);
+}
+
+static void add_all(struct oidset *dest, struct oidset *src) {
+       struct oidset_iter iter;
+       struct object_id *src_oid;
+
+       oidset_iter_init(src, &iter);
+       while ((src_oid = oidset_iter_next(&iter)) != NULL)
+               oidset_insert(dest, src_oid);
+}
+
+static void filter_combine__finalize_omits(
+       struct oidset *omits,
+       void *filter_data)
+{
+       struct combine_filter_data *d = filter_data;
+       size_t sub;
+
+       for (sub = 0; sub < d->nr; sub++) {
+               add_all(omits, &d->sub[sub].omits);
+               oidset_clear(&d->sub[sub].omits);
+       }
+}
+
+static void filter_combine__init(
        struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn);
+       struct filter* filter)
+{
+       struct combine_filter_data *d = xcalloc(1, sizeof(*d));
+       size_t sub;
+
+       d->nr = filter_options->sub_nr;
+       d->sub = xcalloc(d->nr, sizeof(*d->sub));
+       for (sub = 0; sub < d->nr; sub++)
+               d->sub[sub].filter = list_objects_filter__init(
+                       filter->omits ? &d->sub[sub].omits : NULL,
+                       &filter_options->sub[sub]);
+
+       filter->filter_data = d;
+       filter->filter_object_fn = filter_combine;
+       filter->free_fn = filter_combine__free;
+       filter->finalize_omits_fn = filter_combine__finalize_omits;
+}
+
+typedef void (*filter_init_fn)(
+       struct list_objects_filter_options *filter_options,
+       struct filter *filter);
 
 /*
  * Must match "enum list_objects_filter_choice".
@@ -493,14 +642,14 @@ static filter_init_fn s_filters[] = {
        filter_blobs_limit__init,
        filter_trees_depth__init,
        filter_sparse_oid__init,
+       filter_combine__init,
 };
 
-void *list_objects_filter__init(
+struct filter *list_objects_filter__init(
        struct oidset *omitted,
-       struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn)
+       struct list_objects_filter_options *filter_options)
 {
+       struct filter *filter;
        filter_init_fn init_fn;
 
        assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
@@ -510,10 +659,44 @@ void *list_objects_filter__init(
                    filter_options->choice);
 
        init_fn = s_filters[filter_options->choice];
-       if (init_fn)
-               return init_fn(omitted, filter_options,
-                              filter_fn, filter_free_fn);
-       *filter_fn = NULL;
-       *filter_free_fn = NULL;
-       return NULL;
+       if (!init_fn)
+               return NULL;
+
+       filter = xcalloc(1, sizeof(*filter));
+       filter->omits = omitted;
+       init_fn(filter_options, filter);
+       return filter;
+}
+
+enum list_objects_filter_result list_objects_filter__filter_object(
+       struct repository *r,
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       struct filter *filter)
+{
+       if (filter && (obj->flags & NOT_USER_GIVEN))
+               return filter->filter_object_fn(r, filter_situation, obj,
+                                               pathname, filename,
+                                               filter->omits,
+                                               filter->filter_data);
+       /*
+        * No filter is active or user gave object explicitly. In this case,
+        * always show the object (except when LOFS_END_TREE, since this tree
+        * had already been shown when LOFS_BEGIN_TREE).
+        */
+       if (filter_situation == LOFS_END_TREE)
+               return 0;
+       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+}
+
+void list_objects_filter__free(struct filter *filter)
+{
+       if (!filter)
+               return;
+       if (filter->finalize_omits_fn && filter->omits)
+               filter->finalize_omits_fn(filter->omits, filter->filter_data);
+       filter->free_fn(filter->filter_data);
+       free(filter);
 }
index 1d45a4ad5786c915f1fd597ce782cd96b7301532..cfd784e203f30fa6d85980fb019e83c37cc935e2 100644 (file)
@@ -60,30 +60,36 @@ enum list_objects_filter_situation {
        LOFS_BLOB
 };
 
-typedef enum list_objects_filter_result (*filter_object_fn)(
+struct filter;
+
+/*
+ * Constructor for the set of defined list-objects filters.
+ * The `omitted` set is optional. It is populated with objects that the
+ * filter excludes. This set should not be considered finalized until
+ * after list_objects_filter__free is called on the returned `struct
+ * filter *`.
+ */
+struct filter *list_objects_filter__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options);
+
+/*
+ * Lets `filter` decide how to handle the `obj`. If `filter` is NULL, this
+ * function behaves as expected if no filter is configured: all objects are
+ * included.
+ */
+enum list_objects_filter_result list_objects_filter__filter_object(
        struct repository *r,
        enum list_objects_filter_situation filter_situation,
        struct object *obj,
        const char *pathname,
        const char *filename,
-       void *filter_data);
-
-typedef void (*filter_free_fn)(void *filter_data);
+       struct filter *filter);
 
 /*
- * Constructor for the set of defined list-objects filters.
- * Returns a generic "void *filter_data".
- *
- * The returned "filter_fn" will be used by traverse_commit_list()
- * to filter the results.
- *
- * The returned "filter_free_fn" is a destructor for the
- * filter_data.
+ * Destroys `filter` and finalizes the `omitted` set, if present. Does
+ * nothing if `filter` is null.
  */
-void *list_objects_filter__init(
-       struct oidset *omitted,
-       struct list_objects_filter_options *filter_options,
-       filter_object_fn *filter_fn,
-       filter_free_fn *filter_free_fn);
+void list_objects_filter__free(struct filter *filter);
 
 #endif /* LIST_OBJECTS_FILTER_H */
index b5651ddd5bfdd6cda4047f71cc3cb66fc31a793d..9307d91fb3fc8bee9cbeb1a6387b5d5bf92dbd4d 100644 (file)
@@ -18,8 +18,7 @@ struct traversal_context {
        show_object_fn show_object;
        show_commit_fn show_commit;
        void *show_data;
-       filter_object_fn filter_fn;
-       void *filter_data;
+       struct filter *filter;
 };
 
 static void process_blob(struct traversal_context *ctx,
@@ -29,7 +28,7 @@ static void process_blob(struct traversal_context *ctx,
 {
        struct object *obj = &blob->object;
        size_t pathlen;
-       enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
+       enum list_objects_filter_result r;
 
        if (!ctx->revs->blob_objects)
                return;
@@ -54,11 +53,10 @@ static void process_blob(struct traversal_context *ctx,
 
        pathlen = path->len;
        strbuf_addstr(path, name);
-       if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
-               r = ctx->filter_fn(ctx->revs->repo,
-                                  LOFS_BLOB, obj,
-                                  path->buf, &path->buf[pathlen],
-                                  ctx->filter_data);
+       r = list_objects_filter__filter_object(ctx->revs->repo,
+                                              LOFS_BLOB, obj,
+                                              path->buf, &path->buf[pathlen],
+                                              ctx->filter);
        if (r & LOFR_MARK_SEEN)
                obj->flags |= SEEN;
        if (r & LOFR_DO_SHOW)
@@ -157,7 +155,7 @@ static void process_tree(struct traversal_context *ctx,
        struct object *obj = &tree->object;
        struct rev_info *revs = ctx->revs;
        int baselen = base->len;
-       enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
+       enum list_objects_filter_result r;
        int failed_parse;
 
        if (!revs->tree_objects)
@@ -186,11 +184,10 @@ static void process_tree(struct traversal_context *ctx,
        }
 
        strbuf_addstr(base, name);
-       if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
-               r = ctx->filter_fn(ctx->revs->repo,
-                                  LOFS_BEGIN_TREE, obj,
-                                  base->buf, &base->buf[baselen],
-                                  ctx->filter_data);
+       r = list_objects_filter__filter_object(ctx->revs->repo,
+                                              LOFS_BEGIN_TREE, obj,
+                                              base->buf, &base->buf[baselen],
+                                              ctx->filter);
        if (r & LOFR_MARK_SEEN)
                obj->flags |= SEEN;
        if (r & LOFR_DO_SHOW)
@@ -203,16 +200,14 @@ static void process_tree(struct traversal_context *ctx,
        else if (!failed_parse)
                process_tree_contents(ctx, tree, base);
 
-       if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) {
-               r = ctx->filter_fn(ctx->revs->repo,
-                                  LOFS_END_TREE, obj,
-                                  base->buf, &base->buf[baselen],
-                                  ctx->filter_data);
-               if (r & LOFR_MARK_SEEN)
-                       obj->flags |= SEEN;
-               if (r & LOFR_DO_SHOW)
-                       ctx->show_object(obj, base->buf, ctx->show_data);
-       }
+       r = list_objects_filter__filter_object(ctx->revs->repo,
+                                              LOFS_END_TREE, obj,
+                                              base->buf, &base->buf[baselen],
+                                              ctx->filter);
+       if (r & LOFR_MARK_SEEN)
+               obj->flags |= SEEN;
+       if (r & LOFR_DO_SHOW)
+               ctx->show_object(obj, base->buf, ctx->show_data);
 
        strbuf_setlen(base, baselen);
        free_tree_buffer(tree);
@@ -402,8 +397,7 @@ void traverse_commit_list(struct rev_info *revs,
        ctx.show_commit = show_commit;
        ctx.show_object = show_object;
        ctx.show_data = show_data;
-       ctx.filter_fn = NULL;
-       ctx.filter_data = NULL;
+       ctx.filter = NULL;
        do_traverse(&ctx);
 }
 
@@ -416,17 +410,12 @@ void traverse_commit_list_filtered(
        struct oidset *omitted)
 {
        struct traversal_context ctx;
-       filter_free_fn filter_free_fn = NULL;
 
        ctx.revs = revs;
        ctx.show_object = show_object;
        ctx.show_commit = show_commit;
        ctx.show_data = show_data;
-       ctx.filter_fn = NULL;
-
-       ctx.filter_data = list_objects_filter__init(omitted, filter_options,
-                                                   &ctx.filter_fn, &filter_free_fn);
+       ctx.filter = list_objects_filter__init(omitted, filter_options);
        do_traverse(&ctx);
-       if (ctx.filter_data && filter_free_fn)
-               filter_free_fn(ctx.filter_data);
+       list_objects_filter__free(ctx.filter);
 }
index 5b8d46aedee72aab8b027cf4310b4234268965b6..d65a8971db732782822feede96e02f86d336e547 100644 (file)
@@ -32,6 +32,20 @@ struct ll_merge_driver {
        char *cmdline;
 };
 
+static struct attr_check *merge_attributes;
+static struct attr_check *load_merge_attributes(void)
+{
+       if (!merge_attributes)
+               merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL);
+       return merge_attributes;
+}
+
+void reset_merge_attributes(void)
+{
+       attr_check_free(merge_attributes);
+       merge_attributes = NULL;
+}
+
 /*
  * Built-in low-levels
  */
@@ -354,7 +368,7 @@ int ll_merge(mmbuffer_t *result_buf,
             struct index_state *istate,
             const struct ll_merge_options *opts)
 {
-       static struct attr_check *check;
+       struct attr_check *check = load_merge_attributes();
        static const struct ll_merge_options default_opts;
        const char *ll_driver_name = NULL;
        int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
@@ -369,9 +383,6 @@ int ll_merge(mmbuffer_t *result_buf,
                normalize_file(theirs, path, istate);
        }
 
-       if (!check)
-               check = attr_check_initl("merge", "conflict-marker-size", NULL);
-
        git_check_attr(istate, path, check);
        ll_driver_name = check->items[0].value;
        if (check->items[1].value) {
index b9e2af1c884042adda8127a07ce8565b64303047..e78973dd55ed596e26dcf4a372671ad03b97986a 100644 (file)
@@ -26,5 +26,6 @@ int ll_merge(mmbuffer_t *result_buf,
             const struct ll_merge_options *opts);
 
 int ll_merge_marker_size(struct index_state *istate, const char *path);
+void reset_merge_attributes(void);
 
 #endif
index fc43a6c52c75a32548c20bbc4a5aa7d0cc3ddd0d..1a7d69fe32a8808dd34063c48daf61e32886b47a 100644 (file)
@@ -17,6 +17,7 @@
 #include "object-store.h"
 #include "midx.h"
 #include "commit-graph.h"
+#include "promisor-remote.h"
 
 char *odb_pack_name(struct strbuf *buf,
                    const unsigned char *sha1,
@@ -287,13 +288,6 @@ static int unuse_one_window(struct packed_git *current)
        return 0;
 }
 
-void release_pack_memory(size_t need)
-{
-       size_t cur = pack_mapped;
-       while (need >= (cur - pack_mapped) && unuse_one_window(NULL))
-               ; /* nothing */
-}
-
 void close_pack_windows(struct packed_git *p)
 {
        while (p->windows) {
@@ -710,23 +704,12 @@ void unuse_pack(struct pack_window **w_cursor)
        }
 }
 
-static void try_to_free_pack_memory(size_t size)
-{
-       release_pack_memory(size);
-}
-
 struct packed_git *add_packed_git(const char *path, size_t path_len, int local)
 {
-       static int have_set_try_to_free_routine;
        struct stat st;
        size_t alloc;
        struct packed_git *p;
 
-       if (!have_set_try_to_free_routine) {
-               have_set_try_to_free_routine = 1;
-               set_try_to_free_routine(try_to_free_pack_memory);
-       }
-
        /*
         * Make sure a corresponding .pack file exists and that
         * the index looks sane.
@@ -2150,7 +2133,7 @@ int is_promisor_object(const struct object_id *oid)
        static int promisor_objects_prepared;
 
        if (!promisor_objects_prepared) {
-               if (repository_format_partial_clone) {
+               if (has_promisor_remote()) {
                        for_each_packed_object(add_promisor_object,
                                               &promisor_objects,
                                               FOR_EACH_OBJECT_PROMISOR_ONLY);
index 3e98910bdd191f45d3dd86ff0360f40060944705..fc7904ec8147004cdcec5f013a7bace27c8697ed 100644 (file)
@@ -100,7 +100,7 @@ struct packed_git *add_packed_git(const char *path, size_t path_len, int local);
  * Does not unlink if 'force_delete' is false and the pack-file is
  * marked as ".keep".
  */
-extern void unlink_pack_path(const char *pack_name, int force_delete);
+void unlink_pack_path(const char *pack_name, int force_delete);
 
 /*
  * Make sure that a pointer access into an mmap'd index file is within bounds,
index 87b26a1d922e8905fe369b164ae5cad7ea372426..b42f54d48b96c05a207ffb02eac8c235c2b723b0 100644 (file)
@@ -780,7 +780,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                        continue;
                }
 
-               if (!arg[2]) { /* "--" */
+               if (!arg[2] /* "--" */ ||
+                   !strcmp(arg + 2, "end-of-options")) {
                        if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
                                ctx->argc--;
                                ctx->argv++;
index a4bd40bb6acf90fdafefa0983fb523dca4e8b11c..38a33a087ec2aa783be7e2276d669247bd9f8d10 100644 (file)
@@ -46,6 +46,15 @@ enum parse_opt_option_flags {
        PARSE_OPT_COMP_ARG = 1024
 };
 
+enum parse_opt_result {
+       PARSE_OPT_COMPLETE = -3,
+       PARSE_OPT_HELP = -2,
+       PARSE_OPT_ERROR = -1,   /* must be the same as error() */
+       PARSE_OPT_DONE = 0,     /* fixed so that "return 0" works */
+       PARSE_OPT_NON_OPTION,
+       PARSE_OPT_UNKNOWN
+};
+
 struct option;
 typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
 
@@ -241,15 +250,6 @@ const char *optname(const struct option *opt, int flags);
 
 /*----- incremental advanced APIs -----*/
 
-enum parse_opt_result {
-       PARSE_OPT_COMPLETE = -3,
-       PARSE_OPT_HELP = -2,
-       PARSE_OPT_ERROR = -1,   /* must be the same as error() */
-       PARSE_OPT_DONE = 0,     /* fixed so that "return 0" works */
-       PARSE_OPT_NON_OPTION,
-       PARSE_OPT_UNKNOWN
-};
-
 /*
  * It's okay for the caller to consume argv/argc in the usual way.
  * Other fields of that structure are private to parse-options and should not
diff --git a/path.c b/path.c
index 25e97b8c3f76ce9246d8d985adba9777acd5f43c..e3da1f3c4e2c7ed077c1ed3a98103b178045a45a 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1221,31 +1221,52 @@ static inline int chomp_trailing_dir_sep(const char *path, int len)
 }
 
 /*
- * If path ends with suffix (complete path components), returns the
- * part before suffix (sans trailing directory separators).
- * Otherwise returns NULL.
+ * If path ends with suffix (complete path components), returns the offset of
+ * the last character in the path before the suffix (sans trailing directory
+ * separators), and -1 otherwise.
  */
-char *strip_path_suffix(const char *path, const char *suffix)
+static ssize_t stripped_path_suffix_offset(const char *path, const char *suffix)
 {
        int path_len = strlen(path), suffix_len = strlen(suffix);
 
        while (suffix_len) {
                if (!path_len)
-                       return NULL;
+                       return -1;
 
                if (is_dir_sep(path[path_len - 1])) {
                        if (!is_dir_sep(suffix[suffix_len - 1]))
-                               return NULL;
+                               return -1;
                        path_len = chomp_trailing_dir_sep(path, path_len);
                        suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
                }
                else if (path[--path_len] != suffix[--suffix_len])
-                       return NULL;
+                       return -1;
        }
 
        if (path_len && !is_dir_sep(path[path_len - 1]))
-               return NULL;
-       return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
+               return -1;
+       return chomp_trailing_dir_sep(path, path_len);
+}
+
+/*
+ * Returns true if the path ends with components, considering only complete path
+ * components, and false otherwise.
+ */
+int ends_with_path_components(const char *path, const char *components)
+{
+       return stripped_path_suffix_offset(path, components) != -1;
+}
+
+/*
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
+ */
+char *strip_path_suffix(const char *path, const char *suffix)
+{
+       ssize_t offset = stripped_path_suffix_offset(path, suffix);
+
+       return offset == -1 ? NULL : xstrndup(path, offset);
 }
 
 int daemon_avoid_alias(const char *p)
diff --git a/path.h b/path.h
index 2ba6ca58c83487b5e02a71ce7c2f0d556c59aebe..14d6dcad161e3720629ed60d1ec7e4a4b3e34fdd 100644 (file)
--- a/path.h
+++ b/path.h
@@ -193,4 +193,7 @@ const char *git_path_merge_head(struct repository *r);
 const char *git_path_fetch_head(struct repository *r);
 const char *git_path_shallow(struct repository *r);
 
+
+int ends_with_path_components(const char *path, const char *components);
+
 #endif /* PATH_H */
diff --git a/promisor-remote.c b/promisor-remote.c
new file mode 100644 (file)
index 0000000..9bc296c
--- /dev/null
@@ -0,0 +1,265 @@
+#include "cache.h"
+#include "object-store.h"
+#include "promisor-remote.h"
+#include "config.h"
+#include "transport.h"
+
+static char *repository_format_partial_clone;
+static const char *core_partial_clone_filter_default;
+
+void set_repository_format_partial_clone(char *partial_clone)
+{
+       repository_format_partial_clone = xstrdup_or_null(partial_clone);
+}
+
+static int fetch_refs(const char *remote_name, struct ref *ref)
+{
+       struct remote *remote;
+       struct transport *transport;
+       int original_fetch_if_missing = fetch_if_missing;
+       int res;
+
+       fetch_if_missing = 0;
+       remote = remote_get(remote_name);
+       if (!remote->url[0])
+               die(_("Remote with no URL"));
+       transport = transport_get(remote, remote->url[0]);
+
+       transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
+       transport_set_option(transport, TRANS_OPT_NO_DEPENDENTS, "1");
+       res = transport_fetch_refs(transport, ref);
+       fetch_if_missing = original_fetch_if_missing;
+
+       return res;
+}
+
+static int fetch_objects(const char *remote_name,
+                        const struct object_id *oids,
+                        int oid_nr)
+{
+       struct ref *ref = NULL;
+       int i;
+
+       for (i = 0; i < oid_nr; i++) {
+               struct ref *new_ref = alloc_ref(oid_to_hex(&oids[i]));
+               oidcpy(&new_ref->old_oid, &oids[i]);
+               new_ref->exact_oid = 1;
+               new_ref->next = ref;
+               ref = new_ref;
+       }
+       return fetch_refs(remote_name, ref);
+}
+
+static struct promisor_remote *promisors;
+static struct promisor_remote **promisors_tail = &promisors;
+
+static struct promisor_remote *promisor_remote_new(const char *remote_name)
+{
+       struct promisor_remote *r;
+
+       if (*remote_name == '/') {
+               warning(_("promisor remote name cannot begin with '/': %s"),
+                       remote_name);
+               return NULL;
+       }
+
+       FLEX_ALLOC_STR(r, name, remote_name);
+
+       *promisors_tail = r;
+       promisors_tail = &r->next;
+
+       return r;
+}
+
+static struct promisor_remote *promisor_remote_lookup(const char *remote_name,
+                                                     struct promisor_remote **previous)
+{
+       struct promisor_remote *r, *p;
+
+       for (p = NULL, r = promisors; r; p = r, r = r->next)
+               if (!strcmp(r->name, remote_name)) {
+                       if (previous)
+                               *previous = p;
+                       return r;
+               }
+
+       return NULL;
+}
+
+static void promisor_remote_move_to_tail(struct promisor_remote *r,
+                                        struct promisor_remote *previous)
+{
+       if (previous)
+               previous->next = r->next;
+       else
+               promisors = r->next ? r->next : r;
+       r->next = NULL;
+       *promisors_tail = r;
+       promisors_tail = &r->next;
+}
+
+static int promisor_remote_config(const char *var, const char *value, void *data)
+{
+       const char *name;
+       int namelen;
+       const char *subkey;
+
+       if (!strcmp(var, "core.partialclonefilter"))
+               return git_config_string(&core_partial_clone_filter_default,
+                                        var, value);
+
+       if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0)
+               return 0;
+
+       if (!strcmp(subkey, "promisor")) {
+               char *remote_name;
+
+               if (!git_config_bool(var, value))
+                       return 0;
+
+               remote_name = xmemdupz(name, namelen);
+
+               if (!promisor_remote_lookup(remote_name, NULL))
+                       promisor_remote_new(remote_name);
+
+               free(remote_name);
+               return 0;
+       }
+       if (!strcmp(subkey, "partialclonefilter")) {
+               struct promisor_remote *r;
+               char *remote_name = xmemdupz(name, namelen);
+
+               r = promisor_remote_lookup(remote_name, NULL);
+               if (!r)
+                       r = promisor_remote_new(remote_name);
+
+               free(remote_name);
+
+               if (!r)
+                       return 0;
+
+               return git_config_string(&r->partial_clone_filter, var, value);
+       }
+
+       return 0;
+}
+
+static int initialized;
+
+static void promisor_remote_init(void)
+{
+       if (initialized)
+               return;
+       initialized = 1;
+
+       git_config(promisor_remote_config, NULL);
+
+       if (repository_format_partial_clone) {
+               struct promisor_remote *o, *previous;
+
+               o = promisor_remote_lookup(repository_format_partial_clone,
+                                          &previous);
+               if (o)
+                       promisor_remote_move_to_tail(o, previous);
+               else
+                       promisor_remote_new(repository_format_partial_clone);
+       }
+}
+
+static void promisor_remote_clear(void)
+{
+       while (promisors) {
+               struct promisor_remote *r = promisors;
+               promisors = promisors->next;
+               free(r);
+       }
+
+       promisors_tail = &promisors;
+}
+
+void promisor_remote_reinit(void)
+{
+       initialized = 0;
+       promisor_remote_clear();
+       promisor_remote_init();
+}
+
+struct promisor_remote *promisor_remote_find(const char *remote_name)
+{
+       promisor_remote_init();
+
+       if (!remote_name)
+               return promisors;
+
+       return promisor_remote_lookup(remote_name, NULL);
+}
+
+int has_promisor_remote(void)
+{
+       return !!promisor_remote_find(NULL);
+}
+
+static int remove_fetched_oids(struct repository *repo,
+                              struct object_id **oids,
+                              int oid_nr, int to_free)
+{
+       int i, remaining_nr = 0;
+       int *remaining = xcalloc(oid_nr, sizeof(*remaining));
+       struct object_id *old_oids = *oids;
+       struct object_id *new_oids;
+
+       for (i = 0; i < oid_nr; i++)
+               if (oid_object_info_extended(repo, &old_oids[i], NULL,
+                                            OBJECT_INFO_SKIP_FETCH_OBJECT)) {
+                       remaining[i] = 1;
+                       remaining_nr++;
+               }
+
+       if (remaining_nr) {
+               int j = 0;
+               new_oids = xcalloc(remaining_nr, sizeof(*new_oids));
+               for (i = 0; i < oid_nr; i++)
+                       if (remaining[i])
+                               oidcpy(&new_oids[j++], &old_oids[i]);
+               *oids = new_oids;
+               if (to_free)
+                       free(old_oids);
+       }
+
+       free(remaining);
+
+       return remaining_nr;
+}
+
+int promisor_remote_get_direct(struct repository *repo,
+                              const struct object_id *oids,
+                              int oid_nr)
+{
+       struct promisor_remote *r;
+       struct object_id *remaining_oids = (struct object_id *)oids;
+       int remaining_nr = oid_nr;
+       int to_free = 0;
+       int res = -1;
+
+       promisor_remote_init();
+
+       for (r = promisors; r; r = r->next) {
+               if (fetch_objects(r->name, remaining_oids, remaining_nr) < 0) {
+                       if (remaining_nr == 1)
+                               continue;
+                       remaining_nr = remove_fetched_oids(repo, &remaining_oids,
+                                                        remaining_nr, to_free);
+                       if (remaining_nr) {
+                               to_free = 1;
+                               continue;
+                       }
+               }
+               res = 0;
+               break;
+       }
+
+       if (to_free)
+               free(remaining_oids);
+
+       return res;
+}
diff --git a/promisor-remote.h b/promisor-remote.h
new file mode 100644 (file)
index 0000000..8200dfc
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef PROMISOR_REMOTE_H
+#define PROMISOR_REMOTE_H
+
+struct object_id;
+
+/*
+ * Promisor remote linked list
+ *
+ * Information in its fields come from remote.XXX config entries or
+ * from extensions.partialclone or core.partialclonefilter.
+ */
+struct promisor_remote {
+       struct promisor_remote *next;
+       const char *partial_clone_filter;
+       const char name[FLEX_ARRAY];
+};
+
+extern void promisor_remote_reinit(void);
+extern struct promisor_remote *promisor_remote_find(const char *remote_name);
+extern int has_promisor_remote(void);
+extern int promisor_remote_get_direct(struct repository *repo,
+                                     const struct object_id *oids,
+                                     int oid_nr);
+
+/*
+ * This should be used only once from setup.c to set the value we got
+ * from the extensions.partialclone config option.
+ */
+extern void set_repository_format_partial_clone(char *partial_clone);
+
+#endif /* PROMISOR_REMOTE_H */
index 52ffa8a3139f2e427fadb7d233fb4910819becc9..cff1280975b5d400844916083c070b9ee861d1cf 100644 (file)
@@ -1599,16 +1599,17 @@ struct cache_entry *refresh_cache_entry(struct index_state *istate,
 
 #define INDEX_FORMAT_DEFAULT 3
 
-static unsigned int get_index_format_default(void)
+static unsigned int get_index_format_default(struct repository *r)
 {
        char *envversion = getenv("GIT_INDEX_VERSION");
        char *endp;
-       int value;
        unsigned int version = INDEX_FORMAT_DEFAULT;
 
        if (!envversion) {
-               if (!git_config_get_int("index.version", &value))
-                       version = value;
+               prepare_repo_settings(r);
+
+               if (r->settings.index_version >= 0)
+                       version = r->settings.index_version;
                if (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {
                        warning(_("index.version set, but the value is invalid.\n"
                                  "Using version %i"), INDEX_FORMAT_DEFAULT);
@@ -1844,18 +1845,17 @@ static void check_ce_order(struct index_state *istate)
 
 static void tweak_untracked_cache(struct index_state *istate)
 {
-       switch (git_config_get_untracked_cache()) {
-       case -1: /* keep: do nothing */
-               break;
-       case 0: /* false */
+       struct repository *r = the_repository;
+
+       prepare_repo_settings(r);
+
+       if (r->settings.core_untracked_cache  == UNTRACKED_CACHE_REMOVE) {
                remove_untracked_cache(istate);
-               break;
-       case 1: /* true */
-               add_untracked_cache(istate);
-               break;
-       default: /* unknown value: do nothing */
-               break;
+               return;
        }
+
+       if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)
+               add_untracked_cache(istate);
 }
 
 static void tweak_split_index(struct index_state *istate)
@@ -2765,7 +2765,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
        }
 
        if (!istate->version) {
-               istate->version = get_index_format_default();
+               istate->version = get_index_format_default(the_repository);
                if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
                        init_split_index(istate);
        }
index f27cfc8c3e358fa27d7aec78c0b3c44c816402b3..7338cfc67158ede687ba2c410ca6de29e414f5c4 100644 (file)
@@ -1028,7 +1028,7 @@ static const char *copy_name(const char *buf)
                if (!strncmp(cp, " <", 2))
                        return xmemdupz(buf, cp - buf);
        }
-       return "";
+       return xstrdup("");
 }
 
 static const char *copy_email(const char *buf)
@@ -1036,10 +1036,10 @@ static const char *copy_email(const char *buf)
        const char *email = strchr(buf, '<');
        const char *eoemail;
        if (!email)
-               return "";
+               return xstrdup("");
        eoemail = strchr(email, '>');
        if (!eoemail)
-               return "";
+               return xstrdup("");
        return xmemdupz(email, eoemail + 1 - email);
 }
 
diff --git a/repo-settings.c b/repo-settings.c
new file mode 100644 (file)
index 0000000..3779b85
--- /dev/null
@@ -0,0 +1,64 @@
+#include "cache.h"
+#include "config.h"
+#include "repository.h"
+
+#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)
+
+void prepare_repo_settings(struct repository *r)
+{
+       int value;
+       char *strval;
+
+       if (r->settings.initialized)
+               return;
+
+       /* Defaults */
+       memset(&r->settings, -1, sizeof(r->settings));
+
+       if (!repo_config_get_bool(r, "core.commitgraph", &value))
+               r->settings.core_commit_graph = value;
+       if (!repo_config_get_bool(r, "gc.writecommitgraph", &value))
+               r->settings.gc_write_commit_graph = value;
+       UPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);
+       UPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);
+
+       if (!repo_config_get_bool(r, "index.version", &value))
+               r->settings.index_version = value;
+       if (!repo_config_get_maybe_bool(r, "core.untrackedcache", &value)) {
+               if (value == 0)
+                       r->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;
+               else
+                       r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;
+       } else if (!repo_config_get_string(r, "core.untrackedcache", &strval)) {
+               if (!strcasecmp(strval, "keep"))
+                       r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
+
+               free(strval);
+       }
+
+       if (!repo_config_get_string(r, "fetch.negotiationalgorithm", &strval)) {
+               if (!strcasecmp(strval, "skipping"))
+                       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
+               else
+                       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
+       }
+
+       if (!repo_config_get_bool(r, "pack.usesparse", &value))
+               r->settings.pack_use_sparse = value;
+       if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) {
+               UPDATE_DEFAULT_BOOL(r->settings.index_version, 4);
+               UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);
+       }
+       if (!repo_config_get_bool(r, "feature.experimental", &value) && value) {
+               UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
+               UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);
+       }
+
+       /* Hack for test programs like test-dump-untracked-cache */
+       if (ignore_untracked_cache_config)
+               r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
+       else
+               UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);
+
+       UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);
+}
index 4fb6a5885f794dea9ff7a1ecf37f27bc34e4d218..4da275e73fac1a97c39109b7bf31ae01f20e0730 100644 (file)
@@ -11,6 +11,33 @@ struct pathspec;
 struct raw_object_store;
 struct submodule_cache;
 
+enum untracked_cache_setting {
+       UNTRACKED_CACHE_UNSET = -1,
+       UNTRACKED_CACHE_REMOVE = 0,
+       UNTRACKED_CACHE_KEEP = 1,
+       UNTRACKED_CACHE_WRITE = 2
+};
+
+enum fetch_negotiation_setting {
+       FETCH_NEGOTIATION_UNSET = -1,
+       FETCH_NEGOTIATION_NONE = 0,
+       FETCH_NEGOTIATION_DEFAULT = 1,
+       FETCH_NEGOTIATION_SKIPPING = 2,
+};
+
+struct repo_settings {
+       int initialized;
+
+       int core_commit_graph;
+       int gc_write_commit_graph;
+
+       int index_version;
+       enum untracked_cache_setting core_untracked_cache;
+
+       int pack_use_sparse;
+       enum fetch_negotiation_setting fetch_negotiation_algorithm;
+};
+
 struct repository {
        /* Environment */
        /*
@@ -72,6 +99,8 @@ struct repository {
         */
        char *submodule_prefix;
 
+       struct repo_settings settings;
+
        /* Subsystems */
        /*
         * Repository's config which contains key-value pairs from the usual
@@ -157,5 +186,6 @@ int repo_read_index_unmerged(struct repository *);
  */
 void repo_update_index_if_able(struct repository *, struct lock_file *);
 
+void prepare_repo_settings(struct repository *r);
 
 #endif /* REPOSITORY_H */
index 07412297f0248aae886eeb77c3a1cab13c93039c..51690e480d5b626224e919f04dbfb8d1c3426f3e 100644 (file)
@@ -2523,6 +2523,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        int i, flags, left, seen_dashdash, got_rev_arg = 0, revarg_opt;
        struct argv_array prune_data = ARGV_ARRAY_INIT;
        const char *submodule = NULL;
+       int seen_end_of_options = 0;
 
        if (opt)
                submodule = opt->submodule;
@@ -2552,7 +2553,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                revarg_opt |= REVARG_CANNOT_BE_FILENAME;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               if (*arg == '-') {
+               if (!seen_end_of_options && *arg == '-') {
                        int opts;
 
                        opts = handle_revision_pseudo_opt(submodule,
@@ -2574,6 +2575,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                                continue;
                        }
 
+                       if (!strcmp(arg, "--end-of-options")) {
+                               seen_end_of_options = 1;
+                               continue;
+                       }
+
                        opts = handle_revision_opt(revs, argc - i, argv + i,
                                                   &left, argv, opt);
                        if (opts > 0) {
index 34ebf8ed94ad7d8df6773337d31ff7d9c2c84c4a..d648aaf416510e656a372abe54d1a28d995de889 100644 (file)
@@ -3364,6 +3364,9 @@ static int do_merge(struct repository *r,
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
        struct commit_list *to_merge = NULL, **tail = &to_merge;
+       const char *strategy = !opts->xopts_nr &&
+               (!opts->strategy || !strcmp(opts->strategy, "recursive")) ?
+               NULL : opts->strategy;
        struct merge_options o;
        int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
        static struct lock_file lock;
@@ -3516,7 +3519,7 @@ static int do_merge(struct repository *r,
                goto leave_merge;
        }
 
-       if (to_merge->next) {
+       if (strategy || to_merge->next) {
                /* Octopus merge */
                struct child_process cmd = CHILD_PROCESS_INIT;
 
@@ -3530,7 +3533,14 @@ static int do_merge(struct repository *r,
                cmd.git_cmd = 1;
                argv_array_push(&cmd.args, "merge");
                argv_array_push(&cmd.args, "-s");
-               argv_array_push(&cmd.args, "octopus");
+               if (!strategy)
+                       argv_array_push(&cmd.args, "octopus");
+               else {
+                       argv_array_push(&cmd.args, strategy);
+                       for (k = 0; k < opts->xopts_nr; k++)
+                               argv_array_pushf(&cmd.args,
+                                                "-X%s", opts->xopts[k]);
+               }
                argv_array_push(&cmd.args, "--no-edit");
                argv_array_push(&cmd.args, "--no-ff");
                argv_array_push(&cmd.args, "--no-log");
@@ -4554,6 +4564,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 {
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
+       int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
        struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
        struct strbuf label = STRBUF_INIT;
        struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -4720,7 +4731,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 
                if (!commit)
                        strbuf_addf(out, "%s %s\n", cmd_reset,
-                                   rebase_cousins ? "onto" : "[new root]");
+                                   rebase_cousins || root_with_onto ?
+                                   "onto" : "[new root]");
                else {
                        const char *to = NULL;
 
index 6704acbb9c93a55cb7ec69d2e045d67850bc4049..574260f6215f60e8c1aedb227c80a26c34da6c94 100644 (file)
@@ -143,6 +143,12 @@ int sequencer_remove_state(struct replay_opts *opts);
  */
 #define TODO_LIST_REBASE_COUSINS (1U << 4)
 #define TODO_LIST_APPEND_TODO_HELP (1U << 5)
+/*
+ * When generating a script that rebases merges with `--root` *and* with
+ * `--onto`, we do not want to re-generate the root commits.
+ */
+#define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
+
 
 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
                          const char **argv, unsigned flags);
diff --git a/setup.c b/setup.c
index 8dcb4631f7d330a290ab0bb6810f24999f65a1b7..25a3038277cdaaca736a82191dd6b7bb82cd9ae2 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -4,6 +4,7 @@
 #include "dir.h"
 #include "string-list.h"
 #include "chdir-notify.h"
+#include "promisor-remote.h"
 
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
@@ -478,7 +479,7 @@ static int check_repository_format_gently(const char *gitdir, struct repository_
        }
 
        repository_format_precious_objects = candidate->precious_objects;
-       repository_format_partial_clone = xstrdup_or_null(candidate->partial_clone);
+       set_repository_format_partial_clone(candidate->partial_clone);
        repository_format_worktree_config = candidate->worktree_config;
        string_list_clear(&candidate->unknown_extensions, 0);
 
index 487ea35d2d398434d2a907188d8f7ede17721fe3..e85f249a5db027594d25f9882d85d1253ddbf36f 100644 (file)
@@ -30,8 +30,8 @@
 #include "mergesort.h"
 #include "quote.h"
 #include "packfile.h"
-#include "fetch-object.h"
 #include "object-store.h"
+#include "promisor-remote.h"
 
 /* The maximum size for an object header. */
 #define MAX_HEADER_LEN 32
@@ -952,12 +952,8 @@ void *xmmap_gently(void *start, size_t length,
 
        mmap_limit_check(length);
        ret = mmap(start, length, prot, flags, fd, offset);
-       if (ret == MAP_FAILED) {
-               if (!length)
-                       return NULL;
-               release_pack_memory(length);
-               ret = mmap(start, length, prot, flags, fd, offset);
-       }
+       if (ret == MAP_FAILED && !length)
+               ret = NULL;
        return ret;
 }
 
@@ -1475,16 +1471,17 @@ int oid_object_info_extended(struct repository *r, const struct object_id *oid,
                }
 
                /* Check if it is a missing object */
-               if (fetch_if_missing && repository_format_partial_clone &&
+               if (fetch_if_missing && has_promisor_remote() &&
                    !already_retried && r == the_repository &&
                    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {
                        /*
-                        * TODO Investigate having fetch_object() return
-                        * TODO error/success and stopping the music here.
-                        * TODO Pass a repository struct through fetch_object,
-                        * such that arbitrary repositories work.
+                        * TODO Investigate checking promisor_remote_get_direct()
+                        * TODO return value and stopping on error here.
+                        * TODO Pass a repository struct through
+                        * promisor_remote_get_direct(), such that arbitrary
+                        * repositories work.
                         */
-                       fetch_objects(repository_format_partial_clone, real, 1);
+                       promisor_remote_get_direct(r, real, 1);
                        already_retried = 1;
                        continue;
                }
index 2989e27b717abdabd6623299d7737e9a51641990..c665e3f96d5b67db3a3079483bbd28044fbbe120 100644 (file)
@@ -403,9 +403,9 @@ static int repo_collect_ambiguous(struct repository *r,
        return collect_ambiguous(oid, data);
 }
 
-static struct repository *sort_ambiguous_repo;
-static int sort_ambiguous(const void *a, const void *b)
+static int sort_ambiguous(const void *a, const void *b, void *ctx)
 {
+       struct repository *sort_ambiguous_repo = ctx;
        int a_type = oid_object_info(sort_ambiguous_repo, a, NULL);
        int b_type = oid_object_info(sort_ambiguous_repo, b, NULL);
        int a_type_sort;
@@ -434,10 +434,7 @@ static int sort_ambiguous(const void *a, const void *b)
 
 static void sort_ambiguous_oid_array(struct repository *r, struct oid_array *a)
 {
-       /* mutex will be needed if this code is to be made thread safe */
-       sort_ambiguous_repo = r;
-       QSORT(a->oid, a->nr, sort_ambiguous);
-       sort_ambiguous_repo = NULL;
+       QSORT_S(a->oid, a->nr, sort_ambiguous, r);
 }
 
 static enum get_oid_result get_short_oid(struct repository *r,
index d30f916858883aa312bd824a53516cb099a2e922..aa48d179a9aec236069fc88501c5c26c3569d502 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -774,8 +774,10 @@ void strbuf_addstr_xml_quoted(struct strbuf *buf, const char *s)
        }
 }
 
-static int is_rfc3986_reserved(char ch)
+int is_rfc3986_reserved_or_unreserved(char ch)
 {
+       if (is_rfc3986_unreserved(ch))
+               return 1;
        switch (ch) {
                case '!': case '*': case '\'': case '(': case ')': case ';':
                case ':': case '@': case '&': case '=': case '+': case '$':
@@ -785,20 +787,19 @@ static int is_rfc3986_reserved(char ch)
        return 0;
 }
 
-static int is_rfc3986_unreserved(char ch)
+int is_rfc3986_unreserved(char ch)
 {
        return isalnum(ch) ||
                ch == '-' || ch == '_' || ch == '.' || ch == '~';
 }
 
 static void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,
-                                int reserved)
+                                char_predicate allow_unencoded_fn)
 {
        strbuf_grow(sb, len);
        while (len--) {
                char ch = *s++;
-               if (is_rfc3986_unreserved(ch) ||
-                   (!reserved && is_rfc3986_reserved(ch)))
+               if (allow_unencoded_fn(ch))
                        strbuf_addch(sb, ch);
                else
                        strbuf_addf(sb, "%%%02x", (unsigned char)ch);
@@ -806,9 +807,9 @@ static void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,
 }
 
 void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
-                            int reserved)
+                            char_predicate allow_unencoded_fn)
 {
-       strbuf_add_urlencode(sb, s, strlen(s), reserved);
+       strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn);
 }
 
 static void strbuf_humanise(struct strbuf *buf, off_t bytes,
index f62278a0be59be4c6cff17f0a0adcc6361e93e82..84cf96972144fae197c6350ead80880452e3d5b2 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -672,8 +672,13 @@ void strbuf_branchname(struct strbuf *sb, const char *name,
  */
 int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
 
+typedef int (*char_predicate)(char ch);
+
+int is_rfc3986_unreserved(char ch);
+int is_rfc3986_reserved_or_unreserved(char ch);
+
 void strbuf_addstr_urlencode(struct strbuf *sb, const char *name,
-                            int reserved);
+                            char_predicate allow_unencoded_fn);
 
 __attribute__((format (printf,1,2)))
 int printf_ln(const char *fmt, ...);
index 7ea30e50068be8f892a289c8bf705d8096a27ec3..6d87961e419e10257b3f619d42004bae0a35d7cf 100644 (file)
@@ -44,10 +44,10 @@ set_fake_editor () {
        rm -f "$1"
        echo 'rebase -i script before editing:'
        cat "$1".tmp
-       action=pick
+       action=\&
        for line in $FAKE_LINES; do
                case $line in
-               pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d)
+               pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|r|merge|m)
                        action="$line";;
                exec_*|x_*|break|b)
                        echo "$line" | sed 's/_/ /g' >> "$1";;
@@ -58,11 +58,12 @@ set_fake_editor () {
                bad)
                        action="badcmd";;
                fakesha)
+                       test \& != "$action" || action=pick
                        echo "$action XXXXXXX False commit" >> "$1"
                        action=pick;;
                *)
-                       sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-                       action=pick;;
+                       sed -n "${line}s/^[a-z][a-z]*/$action/p" < "$1".tmp >> "$1"
+                       action=\&;;
                esac
        done
        echo 'rebase -i script after editing:'
index e10f5f787fca8b3f7789bcf9043d7c81929f070e..c954c709ad2f50dac563cf237f7d485a2d41b7aa 100755 (executable)
@@ -390,6 +390,9 @@ test_expect_success PERL 'required process filter should filter data' '
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
 
+               # Make sure that the file appears dirty, so checkout below has to
+               # run the configured filter.
+               touch test.r &&
                filter_git checkout --quiet --no-progress empty-branch &&
                cat >expected.log <<-EOF &&
                        START
index cebc77fab0b254fc2e6f63e7eb68956b2b3dec9c..705a136ed92c99cda688f5e267204e59b0e532a9 100755 (executable)
@@ -399,4 +399,11 @@ test_expect_success 'GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS works' '
                test-tool parse-options --ye
 '
 
+test_expect_success '--end-of-options treats remainder as args' '
+       test-tool parse-options \
+           --expect="verbose: -1" \
+           --expect="arg 00: --verbose" \
+           --end-of-options --verbose
+'
+
 test_done
index 6415063980b49985b25d351f5c0079f44ad92533..d4b7e535ea134569ed8bf5643edf8d435a22bc1a 100755 (executable)
@@ -26,7 +26,7 @@ promise_and_delete () {
 test_expect_success 'extensions.partialclone without filter' '
        test_create_repo server &&
        git clone --filter="blob:none" "file://$(pwd)/server" client &&
-       git -C client config --unset core.partialclonefilter &&
+       git -C client config --unset remote.origin.partialclonefilter &&
        git -C client fetch origin
 '
 
@@ -166,8 +166,9 @@ test_expect_success 'fetching of missing objects' '
        # associated packfile contains the object
        ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
        test_line_count = 1 promisorlist &&
-       IDX=$(cat promisorlist | sed "s/promisor$/idx/") &&
-       git verify-pack --verbose "$IDX" | grep "$HASH"
+       IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+       git verify-pack --verbose "$IDX" >out &&
+       grep "$HASH" out
 '
 
 test_expect_success 'fetching of missing objects works with ref-in-want enabled' '
@@ -182,8 +183,55 @@ test_expect_success 'fetching of missing objects works with ref-in-want enabled'
        grep "git< fetch=.*ref-in-want" trace
 '
 
+test_expect_success 'fetching of missing objects from another promisor remote' '
+       git clone "file://$(pwd)/server" server2 &&
+       test_commit -C server2 bar &&
+       git -C server2 repack -a -d --write-bitmap-index &&
+       HASH2=$(git -C server2 rev-parse bar) &&
+
+       git -C repo remote add server2 "file://$(pwd)/server2" &&
+       git -C repo config remote.server2.promisor true &&
+       git -C repo cat-file -p "$HASH2" &&
+
+       git -C repo fetch server2 &&
+       rm -rf repo/.git/objects/* &&
+       git -C repo cat-file -p "$HASH2" &&
+
+       # Ensure that the .promisor file is written, and check that its
+       # associated packfile contains the object
+       ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
+       test_line_count = 1 promisorlist &&
+       IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+       git verify-pack --verbose "$IDX" >out &&
+       grep "$HASH2" out
+'
+
+test_expect_success 'fetching of missing objects configures a promisor remote' '
+       git clone "file://$(pwd)/server" server3 &&
+       test_commit -C server3 baz &&
+       git -C server3 repack -a -d --write-bitmap-index &&
+       HASH3=$(git -C server3 rev-parse baz) &&
+       git -C server3 config uploadpack.allowfilter 1 &&
+
+       rm repo/.git/objects/pack/pack-*.promisor &&
+
+       git -C repo remote add server3 "file://$(pwd)/server3" &&
+       git -C repo fetch --filter="blob:none" server3 $HASH3 &&
+
+       test_cmp_config -C repo true remote.server3.promisor &&
+
+       # Ensure that the .promisor file is written, and check that its
+       # associated packfile contains the object
+       ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
+       test_line_count = 1 promisorlist &&
+       IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+       git verify-pack --verbose "$IDX" >out &&
+       grep "$HASH3" out
+'
+
 test_expect_success 'fetching of missing blobs works' '
-       rm -rf server repo &&
+       rm -rf server server2 repo &&
+       rm -rf server server3 repo &&
        test_create_repo server &&
        test_commit -C server foo &&
        git -C server repack -a -d --write-bitmap-index &&
@@ -234,7 +282,7 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '
 
        git -C repo config core.repositoryformatversion 1 &&
        git -C repo config extensions.partialclone "arbitrary string" &&
-       GIT_TEST_COMMIT_GRAPH=0 git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
+       GIT_TEST_COMMIT_GRAPH=0 git -C repo -c core.commitGraph=false rev-list --exclude-promisor-objects --objects bar >out &&
        grep $(git -C repo rev-parse bar) out &&
        ! grep $FOO out
 '
@@ -514,8 +562,9 @@ test_expect_success 'fetching of missing objects from an HTTP server' '
        # associated packfile contains the object
        ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
        test_line_count = 1 promisorlist &&
-       IDX=$(cat promisorlist | sed "s/promisor$/idx/") &&
-       git verify-pack --verbose "$IDX" | grep "$HASH"
+       IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+       git verify-pack --verbose "$IDX" >out &&
+       grep "$HASH" out
 '
 
 # DO NOT add non-httpd-specific tests here, because the last part of this
index 0c37e7180d1cde57d49c12deb28239c509d9207d..3a0de0ddaa553e82b7db8594f4c45489237fb7db 100755 (executable)
@@ -91,7 +91,12 @@ test_expect_failure 'ignore .git/ with invalid config' '
 
 test_expect_success 'early config and onbranch' '
        echo "[broken" >broken &&
-       test_with_config "[includeif \"onbranch:refs/heads/master\"]path=../broken"
+       test_with_config "[includeif \"onbranch:master\"]path=../broken"
+'
+
+test_expect_success 'onbranch config outside of git repo' '
+       test_config_global includeIf.onbranch:master.path non-existent &&
+       nongit git help
 '
 
 test_done
index 42962ed7d46f6dafa09c7b276942c32da130e300..c77721b580c1535de33f3bd109c95bb52200a691 100755 (executable)
@@ -59,17 +59,38 @@ test_expect_success 'out of bounds index.version issues warning' '
        )
 '
 
-test_expect_success 'GIT_INDEX_VERSION takes precedence over config' '
+test_index_version () {
+       INDEX_VERSION_CONFIG=$1 &&
+       FEATURE_MANY_FILES=$2 &&
+       ENV_VAR_VERSION=$3
+       EXPECTED_OUTPUT_VERSION=$4 &&
        (
                rm -f .git/index &&
-               GIT_INDEX_VERSION=4 &&
-               export GIT_INDEX_VERSION &&
-               git config --add index.version 2 &&
+               rm -f .git/config &&
+               if test "$INDEX_VERSION_CONFIG" -ne 0
+               then
+                       git config --add index.version $INDEX_VERSION_CONFIG
+               fi &&
+               git config --add feature.manyFiles $FEATURE_MANY_FILES
+               if test "$ENV_VAR_VERSION" -ne 0
+               then
+                       GIT_INDEX_VERSION=$ENV_VAR_VERSION &&
+                       export GIT_INDEX_VERSION
+               else
+                       unset GIT_INDEX_VERSION
+               fi &&
                git add a 2>&1 &&
-               echo 4 >expect &&
+               echo $EXPECTED_OUTPUT_VERSION >expect &&
                test-tool index-version <.git/index >actual &&
                test_cmp expect actual
        )
+}
+
+test_expect_success 'index version config precedence' '
+       test_index_version 2 false 4 4 &&
+       test_index_version 2 true 0 2 &&
+       test_index_version 0 true 0 4 &&
+       test_index_version 0 true 2 2
 '
 
 test_done
index 80b23fd3269c7828660469207728f67087c82467..23469cc78937eeec8aabc26c158680c2ca60dd7d 100755 (executable)
@@ -301,6 +301,42 @@ test_expect_success 'rebase --am and --show-current-patch' '
        )
 '
 
+test_expect_success 'rebase --am and .gitattributes' '
+       test_create_repo attributes &&
+       (
+               cd attributes &&
+               test_commit init &&
+               git config filter.test.clean "sed -e '\''s/smudged/clean/g'\''" &&
+               git config filter.test.smudge "sed -e '\''s/clean/smudged/g'\''" &&
+
+               test_commit second &&
+               git checkout -b test HEAD^ &&
+
+               echo "*.txt filter=test" >.gitattributes &&
+               git add .gitattributes &&
+               test_commit third &&
+
+               echo "This text is smudged." >a.txt &&
+               git add a.txt &&
+               test_commit fourth &&
+
+               git checkout -b removal HEAD^ &&
+               git rm .gitattributes &&
+               git add -u &&
+               test_commit fifth &&
+               git cherry-pick test &&
+
+               git checkout test &&
+               git rebase master &&
+               grep "smudged" a.txt &&
+
+               git checkout removal &&
+               git reset --hard &&
+               git rebase master &&
+               grep "clean" a.txt
+       )
+'
+
 test_expect_success 'rebase--merge.sh and --show-current-patch' '
        test_create_repo conflict-merge &&
        (
index 4eff14dae53223fb432ff5d9147543850a2c9ad5..7a2da972fd373cde3cc233472acadc0ae803bd29 100755 (executable)
@@ -120,6 +120,20 @@ test_expect_success REBASE_P 'rebase passes merge strategy options correctly' '
        git rebase --continue
 '
 
+test_expect_success 'rebase -r passes merge strategy options correctly' '
+       rm -fr .git/rebase-* &&
+       git reset --hard commit-new-file-F3-on-topic-branch &&
+       test_commit merge-theirs &&
+       git reset --hard HEAD^ &&
+       test_commit some-other-commit &&
+       test_tick &&
+       git merge --no-ff merge-theirs &&
+       FAKE_LINES="1 3 edit 4 5 7 8 9" git rebase -i -f -r -m \
+               -s recursive --strategy-option=theirs HEAD~2 &&
+       test_commit force-change-ours &&
+       git rebase --continue
+'
+
 test_expect_success '--skip after failed fixup cleans commit message' '
        test_when_finished "test_might_fail git rebase --abort" &&
        git checkout -b with-conflicting-fixup &&
index a5868ea152f94108418175a4c8af583462c09fce..50e7960702da4fa0306818f305f5d735f2292f53 100755 (executable)
@@ -76,14 +76,4 @@ test_expect_success REBASE_P \
        test_must_fail git rebase --preserve-merges --rebase-merges A
 '
 
-test_expect_success '--rebase-merges incompatible with --strategy' '
-       git checkout B^0 &&
-       test_must_fail git rebase --rebase-merges -s resolve A
-'
-
-test_expect_success '--rebase-merges incompatible with --strategy-option' '
-       git checkout B^0 &&
-       test_must_fail git rebase --rebase-merges -Xignore-space-change A
-'
-
 test_done
index d8640522a08696264b9a033fe1b5ebda44632774..bec48e6a1f99a5d5db91b392987ff5b64cf16781 100755 (executable)
@@ -11,113 +11,99 @@ commit_message() {
        git log --pretty=format:%s -1 "$1"
 }
 
+# There are a few bugs in the rebase with regards to the subtree strategy, and
+# this test script tries to document them.  First, the following commit history
+# is generated (the onelines are shown, time flows from left to right):
+#
+# master1 - master2 - master3
+#                             \
+# README ---------------------- Add subproject master - master4 - files_subtree/master5
+#
+# Where the merge moves the files master[123].t into the subdirectory
+# files_subtree/ and master4 as well as files_subtree/master5 add files to that
+# directory directly.
+#
+# Then, in subsequent test cases, `git filter-branch` is used to distill just
+# the commits that touch files_subtree/. To give it a final pre-rebase touch,
+# an empty commit is added on top. The pre-rebase commit history looks like
+# this:
+#
+# Add subproject master - master4 - files_subtree/master5 - Empty commit
+#
+# where the root commit adds three files: master1.t, master2.t and master3.t.
+#
+# This commit history is then rebased onto `master3` with the
+# `-Xsubtree=files_subtree` option in three different ways:
+#
+# 1. using `--preserve-merges`
+# 2. using `--preserve-merges` and --keep-empty
+# 3. without specifying a rebase backend
+
 test_expect_success 'setup' '
        test_commit README &&
-       mkdir files &&
-       (
-               cd files &&
-               git init &&
-               test_commit master1 &&
-               test_commit master2 &&
-               test_commit master3
-       ) &&
-       git fetch files master &&
-       git branch files-master FETCH_HEAD &&
-       git read-tree --prefix=files_subtree files-master &&
-       git checkout -- files_subtree &&
-       tree=$(git write-tree) &&
-       head=$(git rev-parse HEAD) &&
-       rev=$(git rev-parse --verify files-master^0) &&
-       commit=$(git commit-tree -p $head -p $rev -m "Add subproject master" $tree) &&
-       git update-ref HEAD $commit &&
-       (
-               cd files_subtree &&
-               test_commit master4
-       ) &&
-       test_commit files_subtree/master5
-'
 
-# FAILURE: Does not preserve master4.
-test_expect_failure REBASE_P \
-       'Rebase -Xsubtree --preserve-merges --onto commit 4' '
-       reset_rebase &&
-       git checkout -b rebase-preserve-merges-4 master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
-       git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
-       verbose test "$(commit_message HEAD~)" = "files_subtree/master4"
+       git init files &&
+       test_commit -C files master1 &&
+       test_commit -C files master2 &&
+       test_commit -C files master3 &&
+
+       : perform subtree merge into files_subtree/ &&
+       git fetch files refs/heads/master:refs/heads/files-master &&
+       git merge -s ours --no-commit --allow-unrelated-histories \
+               files-master &&
+       git read-tree --prefix=files_subtree -u files-master &&
+       git commit -m "Add subproject master" &&
+
+       : add two extra commits to rebase &&
+       test_commit -C files_subtree master4 &&
+       test_commit files_subtree/master5 &&
+
+       git checkout -b to-rebase &&
+       git fast-export --no-data HEAD -- files_subtree/ |
+               sed -e "s%\([0-9a-f]\{40\} \)files_subtree/%\1%" |
+               git fast-import --force --quiet &&
+       git reset --hard &&
+       git commit -m "Empty commit" --allow-empty
 '
 
-# FAILURE: Does not preserve master5.
-test_expect_failure REBASE_P \
-       'Rebase -Xsubtree --preserve-merges --onto commit 5' '
+# FAILURE: Does not preserve master4.
+test_expect_failure REBASE_P 'Rebase -Xsubtree --preserve-merges --onto commit' '
        reset_rebase &&
-       git checkout -b rebase-preserve-merges-5 master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
+       git checkout -b rebase-preserve-merges to-rebase &&
        git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
+       verbose test "$(commit_message HEAD~)" = "master4" &&
        verbose test "$(commit_message HEAD)" = "files_subtree/master5"
 '
 
 # FAILURE: Does not preserve master4.
-test_expect_failure REBASE_P \
-       'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 4' '
-       reset_rebase &&
-       git checkout -b rebase-keep-empty-4 master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
-       git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
-       verbose test "$(commit_message HEAD~2)" = "files_subtree/master4"
-'
-
-# FAILURE: Does not preserve master5.
-test_expect_failure REBASE_P \
-       'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 5' '
-       reset_rebase &&
-       git checkout -b rebase-keep-empty-5 master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
-       git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
-       verbose test "$(commit_message HEAD~)" = "files_subtree/master5"
-'
-
-# FAILURE: Does not preserve Empty.
-test_expect_failure REBASE_P \
-       'Rebase -Xsubtree --keep-empty --preserve-merges --onto empty commit' '
+test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit' '
        reset_rebase &&
-       git checkout -b rebase-keep-empty-empty master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
+       git checkout -b rebase-keep-empty to-rebase &&
        git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
+       verbose test "$(commit_message HEAD~2)" = "master4" &&
+       verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
        verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-# FAILURE: fatal: Could not parse object
-test_expect_failure 'Rebase -Xsubtree --onto commit 4' '
+test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
        reset_rebase &&
-       git checkout -b rebase-onto-4 master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
-       git rebase -Xsubtree=files_subtree --onto files-master master &&
-       verbose test "$(commit_message HEAD~2)" = "files_subtree/master4"
+       git checkout -b rebase-onto to-rebase &&
+       test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
+       : first pick results in no changes &&
+       git rebase --continue &&
+       verbose test "$(commit_message HEAD~2)" = "master4" &&
+       verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
+       verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-# FAILURE: fatal: Could not parse object
-test_expect_failure 'Rebase -Xsubtree --onto commit 5' '
-       reset_rebase &&
-       git checkout -b rebase-onto-5 master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
-       git rebase -Xsubtree=files_subtree --onto files-master master &&
-       verbose test "$(commit_message HEAD~)" = "files_subtree/master5"
-'
-# FAILURE: fatal: Could not parse object
-test_expect_failure 'Rebase -Xsubtree --onto empty commit' '
+test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
        reset_rebase &&
-       git checkout -b rebase-onto-empty master &&
-       git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-       git commit -m "Empty commit" --allow-empty &&
-       git rebase -Xsubtree=files_subtree --onto files-master master &&
+       git checkout -b rebase-merges-onto to-rebase &&
+       test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
+       : first pick results in no changes &&
+       git rebase --continue &&
+       verbose test "$(commit_message HEAD~2)" = "master4" &&
+       verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
        verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
index 7b6c4847ad6b0993bc192f53dec4671edfdf7f91..fe6489fed6339f0a5144274a87c1afefa9ec3b4e 100755 (executable)
@@ -441,4 +441,25 @@ test_expect_success '--continue after resolving conflicts after a merge' '
        test_path_is_missing .git/MERGE_HEAD
 '
 
+test_expect_success '--rebase-merges with strategies' '
+       git checkout -b with-a-strategy F &&
+       test_tick &&
+       git merge -m "Merge conflicting-G" conflicting-G &&
+
+       : first, test with a merge strategy option &&
+       git rebase -ir -Xtheirs G &&
+       echo conflicting-G >expect &&
+       test_cmp expect G.t &&
+
+       : now, try with a merge strategy other than recursive &&
+       git reset --hard @{1} &&
+       write_script git-merge-override <<-\EOF &&
+       echo overridden$1 >>G.t
+       git add G.t
+       EOF
+       PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
+       test_write_lines G overridden--xopt >expect &&
+       test_cmp expect G.t
+'
+
 test_done
index 9261d6d3a0000e9891e1af58265349522ca40b84..6f5ef0035e92998eb74b14081aa021247abf67d8 100755 (executable)
@@ -31,6 +31,7 @@ diffpatterns="
        cpp
        csharp
        css
+       dts
        fortran
        fountain
        golang
diff --git a/t/t4018/dts-labels b/t/t4018/dts-labels
new file mode 100644 (file)
index 0000000..b21ef87
--- /dev/null
@@ -0,0 +1,9 @@
+/ {
+       label_1: node1@ff00 {
+               label2: RIGHT {
+                       vendor,some-property;
+
+                       ChangeMe = <0x45-30>;
+               };
+       };
+};
diff --git a/t/t4018/dts-node-unitless b/t/t4018/dts-node-unitless
new file mode 100644 (file)
index 0000000..c5287d9
--- /dev/null
@@ -0,0 +1,8 @@
+/ {
+       label_1: node1 {
+               RIGHT {
+                       prop-array = <1>, <4>;
+                       ChangeMe = <0xffeedd00>;
+               };
+       };
+};
diff --git a/t/t4018/dts-nodes b/t/t4018/dts-nodes
new file mode 100644 (file)
index 0000000..5a4334b
--- /dev/null
@@ -0,0 +1,8 @@
+/ {
+       label_1: node1@ff00 {
+               RIGHT@deadf00,4000 {
+                       #size-cells = <1>;
+                       ChangeMe = <0xffeedd00>;
+               };
+       };
+};
diff --git a/t/t4018/dts-nodes-comment1 b/t/t4018/dts-nodes-comment1
new file mode 100644 (file)
index 0000000..559dfce
--- /dev/null
@@ -0,0 +1,8 @@
+/ {
+       label_1: node1@ff00 {
+               RIGHT@deadf00,4000 /* &a comment */ {
+                       #size-cells = <1>;
+                       ChangeMe = <0xffeedd00>;
+               };
+       };
+};
diff --git a/t/t4018/dts-nodes-comment2 b/t/t4018/dts-nodes-comment2
new file mode 100644 (file)
index 0000000..27e9718
--- /dev/null
@@ -0,0 +1,8 @@
+/ {
+       label_1: node1@ff00 {
+               RIGHT@deadf00,4000 { /* a trailing comment */ 
+                       #size-cells = <1>;
+                       ChangeMe = <0xffeedd00>;
+               };
+       };
+};
diff --git a/t/t4018/dts-reference b/t/t4018/dts-reference
new file mode 100644 (file)
index 0000000..8f0c87d
--- /dev/null
@@ -0,0 +1,9 @@
+&label_1 {
+       TEST = <455>;
+};
+
+&RIGHT {
+       vendor,some-property;
+
+       ChangeMe = <0x45-30>;
+};
diff --git a/t/t4018/dts-root b/t/t4018/dts-root
new file mode 100644 (file)
index 0000000..2ef9e6f
--- /dev/null
@@ -0,0 +1,5 @@
+/RIGHT { /* Technically just supposed to be a slash */
+       #size-cells = <1>;
+
+       ChangeMe = <0xffeedd00>;
+};
index 912df91226f2a018016129a6634b837faee19869..9a93c2a3e0dd8a4763092549df77172657009c56 100755 (executable)
@@ -303,6 +303,7 @@ test_language_driver bibtex
 test_language_driver cpp
 test_language_driver csharp
 test_language_driver css
+test_language_driver dts
 test_language_driver fortran
 test_language_driver html
 test_language_driver java
diff --git a/t/t4034/dts/expect b/t/t4034/dts/expect
new file mode 100644 (file)
index 0000000..560fc99
--- /dev/null
@@ -0,0 +1,37 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index b6a9051..7803aee 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,32 +1,32 @@<RESET>
+/ {<RESET>
+       <RED>this_handle<RESET><GREEN>HANDLE_2<RESET>: <RED>node<RESET><GREEN>new-node<RESET>@<RED>f00<RESET><GREEN>eeda<RESET> {
+               compatible = "<RED>mydev<RESET><GREEN>vendor,compat<RESET>";
+               string-prop = <RED>start<RESET><GREEN>end<RESET>: "hello <RED>world!<RESET><GREEN>world?<RESET>" <RED>end<RESET><GREEN>start<RESET>: ;
+               <RED>#size-cells<RESET><GREEN>#address-cells<RESET> = <<RED>0+0<RESET><GREEN>0+40<RESET>>;
+               reg = <<RED>0xf00<RESET><GREEN>0xeeda<RESET>>;
+               prop = <<GREEN>(<RESET>1<GREEN>)<RESET>>;
+               prop = <<GREEN>(<RESET>-1e10<GREEN>)<RESET>>;
+               prop = <(!<RED>3<RESET><GREEN>1<RESET>)>;
+               prop = <(~<RED>3<RESET><GREEN>1<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>*<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>&<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>*<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>/<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>%<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3+4<RESET><GREEN>1+2<RESET>)>;
+               prop = <(<RED>3-4<RESET><GREEN>1-2<RESET>)>;
+               prop = /bits/ <RED>64<RESET><GREEN>32<RESET> <(<RED>3<RESET><GREEN>1<RESET><<<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>>><RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>&<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>^<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>|<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>&&<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>3<RESET><GREEN>1<RESET>||<RED>4<RESET><GREEN>2<RESET>)>;
+               prop = <(<RED>4?5<RESET><GREEN>1?2<RESET>:3)>;
+               list = <&<RED>this_handle<RESET><GREEN>HANDLE_2<RESET>>, <0 0 0 <RED>0<RESET><GREEN>1<RESET>>;
+       };<RESET>
+
+       &<RED>phandle<RESET><GREEN>phandle2<RESET> {
+               <RED>pre-phandle<RESET><GREEN>prop_handle<RESET> = <&<RED>this_handle<RESET><GREEN>HANDLE_2<RESET>>;
+       };<RESET>
+};<RESET>
diff --git a/t/t4034/dts/post b/t/t4034/dts/post
new file mode 100644 (file)
index 0000000..7803aee
--- /dev/null
@@ -0,0 +1,32 @@
+/ {
+       HANDLE_2: new-node@eeda {
+               compatible = "vendor,compat";
+               string-prop = end: "hello world?" start: ;
+               #address-cells = <0+40>;
+               reg = <0xeeda>;
+               prop = <(1)>;
+               prop = <(-1e10)>;
+               prop = <(!1)>;
+               prop = <(~1)>;
+               prop = <(1*2)>;
+               prop = <(1&2)>;
+               prop = <(1*2)>;
+               prop = <(1/2)>;
+               prop = <(1%2)>;
+               prop = <(1+2)>;
+               prop = <(1-2)>;
+               prop = /bits/ 32 <(1<<2)>;
+               prop = <(1>>2)>;
+               prop = <(1&2)>;
+               prop = <(1^2)>;
+               prop = <(1|2)>;
+               prop = <(1&&2)>;
+               prop = <(1||2)>;
+               prop = <(1?2:3)>;
+               list = <&HANDLE_2>, <0 0 0 1>;
+       };
+
+       &phandle2 {
+               prop_handle = <&HANDLE_2>;
+       };
+};
diff --git a/t/t4034/dts/pre b/t/t4034/dts/pre
new file mode 100644 (file)
index 0000000..b6a9051
--- /dev/null
@@ -0,0 +1,32 @@
+/ {
+       this_handle: node@f00 {
+               compatible = "mydev";
+               string-prop = start: "hello world!" end: ;
+               #size-cells = <0+0>;
+               reg = <0xf00>;
+               prop = <1>;
+               prop = <-1e10>;
+               prop = <(!3)>;
+               prop = <(~3)>;
+               prop = <(3*4)>;
+               prop = <(3&4)>;
+               prop = <(3*4)>;
+               prop = <(3/4)>;
+               prop = <(3%4)>;
+               prop = <(3+4)>;
+               prop = <(3-4)>;
+               prop = /bits/ 64 <(3<<4)>;
+               prop = <(3>>4)>;
+               prop = <(3&4)>;
+               prop = <(3^4)>;
+               prop = <(3|4)>;
+               prop = <(3&&4)>;
+               prop = <(3||4)>;
+               prop = <(4?5:3)>;
+               list = <&this_handle>, <0 0 0 0>;
+       };
+
+       &phandle {
+               pre-phandle = <&this_handle>;
+       };
+};
index 90c8fb29017562d5341a61a4bb872d323b8f8340..4831ad35e61436fdc400533fb0e7f88e6d27027c 100755 (executable)
@@ -75,6 +75,37 @@ test_expect_success 'diff skips same-OID blobs' '
        ! grep "want $(cat hash-b)" trace
 '
 
+test_expect_success 'when fetching missing objects, diff skips GITLINKs' '
+       test_when_finished "rm -rf sub server client trace" &&
+
+       test_create_repo sub &&
+       test_commit -C sub first &&
+
+       test_create_repo server &&
+       echo a >server/a &&
+       git -C server add a &&
+       git -C server submodule add "file://$(pwd)/sub" &&
+       git -C server commit -m x &&
+
+       test_commit -C server/sub second &&
+       echo another-a >server/a &&
+       git -C server add a sub &&
+       git -C server commit -m x &&
+
+       test_config -C server uploadpack.allowfilter 1 &&
+       test_config -C server uploadpack.allowanysha1inwant 1 &&
+       git clone --bare --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+       echo a | git hash-object --stdin >hash-old-a &&
+       echo another-a | git hash-object --stdin >hash-new-a &&
+
+       # Ensure that a and another-a are fetched, and check (by successful
+       # execution of the diff) that no invalid OIDs are sent.
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff HEAD^ HEAD &&
+       grep "want $(cat hash-old-a)" trace &&
+       grep "want $(cat hash-new-a)" trace
+'
+
 test_expect_success 'diff with rename detection batches blobs' '
        test_when_finished "rm -rf server client trace" &&
 
index 3f7f750cc8d7442458323f9ec77a371db3818320..4f1e24ecbecd87667251eab612c9b966405d53c6 100755 (executable)
@@ -1061,4 +1061,56 @@ test_expect_success 'am --quit keeps HEAD where it is' '
        test_cmp expected actual
 '
 
+test_expect_success 'am and .gitattibutes' '
+       test_create_repo attributes &&
+       (
+               cd attributes &&
+               test_commit init &&
+               git config filter.test.clean "sed -e '\''s/smudged/clean/g'\''" &&
+               git config filter.test.smudge "sed -e '\''s/clean/smudged/g'\''" &&
+
+               test_commit second &&
+               git checkout -b test HEAD^ &&
+
+               echo "*.txt filter=test conflict-marker-size=10" >.gitattributes &&
+               git add .gitattributes &&
+               test_commit third &&
+
+               echo "This text is smudged." >a.txt &&
+               git add a.txt &&
+               test_commit fourth &&
+
+               git checkout -b removal HEAD^ &&
+               git rm .gitattributes &&
+               git add -u &&
+               test_commit fifth &&
+               git cherry-pick test &&
+
+               git checkout -b conflict third &&
+               echo "This text is different." >a.txt &&
+               git add a.txt &&
+               test_commit sixth &&
+
+               git checkout test &&
+               git format-patch --stdout master..HEAD >patches &&
+               git reset --hard master &&
+               git am patches &&
+               grep "smudged" a.txt &&
+
+               git checkout removal &&
+               git reset --hard &&
+               git format-patch --stdout master..HEAD >patches &&
+               git reset --hard master &&
+               git am patches &&
+               grep "clean" a.txt &&
+
+               git checkout conflict &&
+               git reset --hard &&
+               git format-patch --stdout master..HEAD >patches &&
+               git reset --hard fourth &&
+               test_must_fail git am -3 patches &&
+               grep "<<<<<<<<<<" a.txt
+       )
+'
+
 test_done
index c20209324c8e71c677b70ce2217cf6439385024b..e88ccb04a9715b92e47976e66753c38c2547babf 100755 (executable)
@@ -1707,4 +1707,11 @@ test_expect_success '--exclude-promisor-objects does not BUG-crash' '
        test_must_fail git log --exclude-promisor-objects source-a
 '
 
+test_expect_success 'log --end-of-options' '
+       git update-ref refs/heads/--source HEAD &&
+       git log --end-of-options --source >actual &&
+       git log >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 1db7bd0f59b46768d6559c37e026cde41821763a..83191637441437b1c0b086e9417087940a221162 100755 (executable)
@@ -132,4 +132,86 @@ test_expect_success '--raw is forbidden' '
        test_must_fail git log -L1,24:b.c --raw
 '
 
+test_expect_success 'setup for checking fancy rename following' '
+       git checkout --orphan moves-start &&
+       git reset --hard &&
+
+       printf "%s\n"    12 13 14 15      b c d e   >file-1 &&
+       printf "%s\n"    22 23 24 25      B C D E   >file-2 &&
+       git add file-1 file-2 &&
+       test_tick &&
+       git commit -m "Add file-1 and file-2" &&
+       oid_add_f1_f2=$(git rev-parse --short HEAD) &&
+
+       git checkout -b moves-main &&
+       printf "%s\n" 11 12 13 14 15      b c d e   >file-1 &&
+       git commit -a -m "Modify file-1 on main" &&
+       oid_mod_f1_main=$(git rev-parse --short HEAD) &&
+
+       printf "%s\n" 21 22 23 24 25      B C D E   >file-2 &&
+       git commit -a -m "Modify file-2 on main #1" &&
+       oid_mod_f2_main_1=$(git rev-parse --short HEAD) &&
+
+       git mv file-1 renamed-1 &&
+       git commit -m "Rename file-1 to renamed-1 on main" &&
+
+       printf "%s\n" 11 12 13 14 15      b c d e f >renamed-1 &&
+       git commit -a -m "Modify renamed-1 on main" &&
+       oid_mod_r1_main=$(git rev-parse --short HEAD) &&
+
+       printf "%s\n" 21 22 23 24 25      B C D E F >file-2 &&
+       git commit -a -m "Modify file-2 on main #2" &&
+       oid_mod_f2_main_2=$(git rev-parse --short HEAD) &&
+
+       git checkout -b moves-side moves-start &&
+       printf "%s\n"    12 13 14 15 16   b c d e   >file-1 &&
+       git commit -a -m "Modify file-1 on side #1" &&
+       oid_mod_f1_side_1=$(git rev-parse --short HEAD) &&
+
+       printf "%s\n"    22 23 24 25 26   B C D E   >file-2 &&
+       git commit -a -m "Modify file-2 on side" &&
+       oid_mod_f2_side=$(git rev-parse --short HEAD) &&
+
+       git mv file-2 renamed-2 &&
+       git commit -m "Rename file-2 to renamed-2 on side" &&
+
+       printf "%s\n"    12 13 14 15 16 a b c d e   >file-1 &&
+       git commit -a -m "Modify file-1 on side #2" &&
+       oid_mod_f1_side_2=$(git rev-parse --short HEAD) &&
+
+       printf "%s\n"    22 23 24 25 26 A B C D E   >renamed-2 &&
+       git commit -a -m "Modify renamed-2 on side" &&
+       oid_mod_r2_side=$(git rev-parse --short HEAD) &&
+
+       git checkout moves-main &&
+       git merge moves-side &&
+       oid_merge=$(git rev-parse --short HEAD)
+'
+
+test_expect_success 'fancy rename following #1' '
+       cat >expect <<-EOF &&
+       $oid_merge Merge branch '\''moves-side'\'' into moves-main
+       $oid_mod_f1_side_2 Modify file-1 on side #2
+       $oid_mod_f1_side_1 Modify file-1 on side #1
+       $oid_mod_r1_main Modify renamed-1 on main
+       $oid_mod_f1_main Modify file-1 on main
+       $oid_add_f1_f2 Add file-1 and file-2
+       EOF
+       git log -L1:renamed-1 --oneline --no-patch >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'fancy rename following #2' '
+       cat >expect <<-EOF &&
+       $oid_merge Merge branch '\''moves-side'\'' into moves-main
+       $oid_mod_r2_side Modify renamed-2 on side
+       $oid_mod_f2_side Modify file-2 on side
+       $oid_mod_f2_main_2 Modify file-2 on main #2
+       $oid_mod_f2_main_1 Modify file-2 on main #1
+       $oid_add_f1_f2 Add file-1 and file-2
+       EOF
+       git log -L1:renamed-2 --oneline --no-patch >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 271eb5a1fdfbfe4aab216271fc1897968283639d..3e7b23cb32c581b88473b596036517a38c51c9fd 100755 (executable)
@@ -204,4 +204,23 @@ test_expect_success EXPENSIVE,LONG_IS_64BIT,UNZIP,UNZIP_ZIP64_SUPPORT,ZIPINFO \
        grep $size big.lst
 '
 
+build_tree() {
+       perl -e '
+               my $hash = $ARGV[0];
+               foreach my $order (2..6) {
+                       $first = 10 ** $order;
+                       foreach my $i (-13..-9) {
+                               my $name = "a" x ($first + $i);
+                               print "100644 blob $hash\t$name\n"
+                       }
+               }
+       ' "$1"
+}
+
+test_expect_success 'tar archive with long paths' '
+       blob=$(echo foo | git hash-object -w --stdin) &&
+       tree=$(build_tree $blob | git mktree) &&
+       git archive -o long_paths.tar $tree
+'
+
 test_done
index dacb440b2750e40b393a200229a9ee968f12ccd3..f4338abb78a83967a1bdc6b264600262a0f94d65 100755 (executable)
@@ -24,11 +24,11 @@ test_expect_success 'check corruption' '
 '
 
 test_expect_success 'rev-list notices corruption (1)' '
-       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list HEAD
+       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list HEAD
 '
 
 test_expect_success 'rev-list notices corruption (2)' '
-       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --objects HEAD
+       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --objects HEAD
 '
 
 test_expect_success 'pack-objects notices corruption' '
index 99f4ef4c19dff028facac6da810c973b3a82a4fc..e2c39533d3f8ce292f151d82f19dd753b564b882 100755 (executable)
@@ -8,6 +8,7 @@ GIT_TEST_COMMIT_GRAPH=0
 test_expect_success 'setup repo' '
        git init &&
        git config core.commitGraph true &&
+       git config gc.writeCommitGraph false &&
        infodir=".git/objects/info" &&
        graphdir="$infodir/commit-graphs" &&
        test_oid_init
@@ -334,6 +335,7 @@ test_expect_success 'split across alternate where alternate is not split' '
        git clone --no-hardlinks . alt-split &&
        (
                cd alt-split &&
+               rm -f .git/objects/info/commit-graph &&
                echo "$(pwd)"/../.git/objects >.git/objects/info/alternates &&
                test_commit 18 &&
                git commit-graph write --reachable --split &&
index 8a14be51a13280d3fccd340c027cd5f20bb941a5..f70cbcc9cabf6499de57529cbfa095ed9eff73e3 100755 (executable)
@@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc
        have_not_sent c6 c4 c3
 '
 
-test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '
-       rm -rf server client trace &&
-       git init server &&
-       test_commit -C server to_fetch &&
-
-       git init client &&
-       test_commit -C client on_client &&
-       git -C client checkout on_client &&
-
-       test_config -C client fetch.negotiationAlgorithm invalid &&
-       test_must_fail git -C client fetch "$(pwd)/server" 2>err &&
-       test_i18ngrep "unknown fetch negotiation algorithm" err &&
-
-       # Explicit "default" value
-       test_config -C client fetch.negotiationAlgorithm default &&
-       git -C client -c fetch.negotiationAlgorithm=default fetch "$(pwd)/server" &&
-
-       # Implementation detail: If there is nothing to fetch, we will not error out
-       test_config -C client fetch.negotiationAlgorithm invalid &&
-       git -C client fetch "$(pwd)/server" 2>err &&
-       test_i18ngrep ! "unknown fetch negotiation algorithm" err
-'
-
 test_expect_success 'when two skips collide, favor the larger one' '
        rm -rf server client trace &&
        git init server &&
diff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh
new file mode 100755 (executable)
index 0000000..81975ad
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/sh
+
+test_description='"git fetch/pull --set-upstream" basic tests.'
+. ./test-lib.sh
+
+check_config () {
+       printf "%s\n" "$2" "$3" >"expect.$1" &&
+       {
+               git config "branch.$1.remote" && git config "branch.$1.merge"
+       } >"actual.$1" &&
+       test_cmp "expect.$1" "actual.$1"
+}
+
+check_config_missing () {
+       test_expect_code 1 git config "branch.$1.remote" &&
+       test_expect_code 1 git config "branch.$1.merge"
+}
+
+clear_config () {
+       for branch in "$@"; do
+               test_might_fail git config --unset-all "branch.$branch.remote"
+               test_might_fail git config --unset-all "branch.$branch.merge"
+       done
+}
+
+ensure_fresh_upstream () {
+       rm -rf parent && git init --bare parent
+}
+
+test_expect_success 'setup bare parent fetch' '
+       ensure_fresh_upstream &&
+       git remote add upstream parent
+'
+
+test_expect_success 'setup commit on master and other fetch' '
+       test_commit one &&
+       git push upstream master &&
+       git checkout -b other &&
+       test_commit two &&
+       git push upstream other
+'
+
+# tests for fetch --set-upstream
+
+test_expect_success 'fetch --set-upstream does not set upstream w/o branch' '
+       clear_config master other &&
+       git checkout master &&
+       git fetch --set-upstream upstream &&
+       check_config_missing master &&
+       check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream upstream master sets branch master but not other' '
+       clear_config master other &&
+       git fetch --set-upstream upstream master &&
+       check_config master upstream refs/heads/master &&
+       check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream upstream other sets branch other' '
+       clear_config master other &&
+       git fetch --set-upstream upstream other &&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream master:other does not set the branch other2' '
+       clear_config other2 &&
+       git fetch --set-upstream upstream master:other2 &&
+       check_config_missing other2
+'
+
+test_expect_success 'fetch --set-upstream http://nosuchdomain.example.com fails with invalid url' '
+       # master explicitly not cleared, we check that it is not touched from previous value
+       clear_config other other2 &&
+       test_must_fail git fetch --set-upstream http://nosuchdomain.example.com &&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+test_expect_success 'fetch --set-upstream with valid URL sets upstream to URL' '
+       clear_config other other2 &&
+       url="file://'"$PWD"'" &&
+       git fetch --set-upstream "$url" &&
+       check_config master "$url" HEAD &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+# tests for pull --set-upstream
+
+test_expect_success 'setup bare parent pull' '
+       git remote rm upstream &&
+       ensure_fresh_upstream &&
+       git remote add upstream parent
+'
+
+test_expect_success 'setup commit on master and other pull' '
+       test_commit three &&
+       git push --tags upstream master &&
+       test_commit four &&
+       git push upstream other
+'
+
+test_expect_success 'pull --set-upstream upstream master sets branch master but not other' '
+       clear_config master other &&
+       git pull --set-upstream upstream master &&
+       check_config master upstream refs/heads/master &&
+       check_config_missing other
+'
+
+test_expect_success 'pull --set-upstream master:other2 does not set the branch other2' '
+       clear_config other2 &&
+       git pull --set-upstream upstream master:other2 &&
+       check_config_missing other2
+'
+
+test_expect_success 'pull --set-upstream upstream other sets branch master' '
+       clear_config master other &&
+       git pull --set-upstream upstream other &&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other
+'
+
+test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
+       clear_config three &&
+       git pull --tags --set-upstream upstream three &&
+       check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails with invalid url' '
+       # master explicitly not cleared, we check that it is not touched from previous value
+       clear_config other other2 three &&
+       test_must_fail git pull --set-upstream http://nosuchdomain.example.com &&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other &&
+       check_config_missing other2 &&
+       check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
+       clear_config master other &&
+       git pull --set-upstream upstream HEAD &&
+       check_config master upstream HEAD &&
+       git checkout other &&
+       git pull --set-upstream upstream HEAD &&
+       check_config other upstream HEAD
+'
+
+test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
+       clear_config master three &&
+       git pull --set-upstream upstream master three &&
+       check_config_missing master &&
+       check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream with valid URL sets upstream to URL' '
+       clear_config master other other2 &&
+       git checkout master &&
+       url="file://'"$PWD"'" &&
+       git pull --set-upstream "$url" &&
+       check_config master "$url" HEAD &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+test_expect_success 'pull --set-upstream with valid URL and branch sets branch' '
+       clear_config master other other2 &&
+       git checkout master &&
+       url="file://'"$PWD"'" &&
+       git pull --set-upstream "$url" master &&
+       check_config master "$url" refs/heads/master &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+test_done
index 4a3b901f067c2cb024247f2dc9299a6215939019..2e94354e5b11b3dfcf5104e4911cb37003a1c03e 100755 (executable)
@@ -654,7 +654,8 @@ partial_clone () {
        git -C client fsck &&
 
        # Ensure that unneeded blobs are not inadvertently fetched.
-       test_config -C client extensions.partialclone "not a remote" &&
+       test_config -C client remote.origin.promisor "false" &&
+       git -C client config --unset remote.origin.partialclonefilter &&
        test_must_fail git -C client cat-file -e "$HASH1" &&
 
        # But this blob was fetched, because clone performs an initial checkout
index 565254558f362397270bcf13bd3566993e1b0f50..fc634a56b2076283198060de467e3a4c8ce3ffa6 100755 (executable)
@@ -42,8 +42,8 @@ test_expect_success 'do partial clone 1' '
 
        test_cmp expect_1.oids observed.oids &&
        test "$(git -C pc1 config --local core.repositoryformatversion)" = "1" &&
-       test "$(git -C pc1 config --local extensions.partialclone)" = "origin" &&
-       test "$(git -C pc1 config --local core.partialclonefilter)" = "blob:none"
+       test "$(git -C pc1 config --local remote.origin.promisor)" = "true" &&
+       test "$(git -C pc1 config --local remote.origin.partialclonefilter)" = "blob:none"
 '
 
 # checkout master to force dynamic object fetch of blobs at HEAD.
@@ -208,6 +208,25 @@ test_expect_success 'use fsck before and after manually fetching a missing subtr
        test_cmp unique_types.expected unique_types.observed
 '
 
+test_expect_success 'implicitly construct combine: filter with repeated flags' '
+       GIT_TRACE=$(pwd)/trace git clone --bare \
+               --filter=blob:none --filter=tree:1 \
+               "file://$(pwd)/srv.bare" pc2 &&
+       grep "trace:.* git pack-objects .*--filter=combine:blob:none+tree:1" \
+               trace &&
+       git -C pc2 rev-list --objects --missing=allow-any HEAD >objects &&
+
+       # We should have gotten some root trees.
+       grep " $" objects &&
+       # Should not have gotten any non-root trees or blobs.
+       ! grep " ." objects &&
+
+       xargs -n 1 git -C pc2 cat-file -t <objects >types &&
+       sort -u types >unique_types.actual &&
+       test_write_lines commit tree >unique_types.expected &&
+       test_cmp unique_types.expected unique_types.actual
+'
+
 test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
        rm -rf src dst &&
        git init src &&
index 52a9e38d66f3222f4c02f7d067a9509db685f875..b8cf82349b1d6d0405342cf98ead948d2e81c2a5 100755 (executable)
@@ -140,4 +140,12 @@ test_expect_success '--header shows a NUL after each commit' '
        test_cmp expect actual
 '
 
+test_expect_success 'rev-list --end-of-options' '
+       git update-ref refs/heads/--output=yikes HEAD &&
+       git rev-list --end-of-options --output=yikes >actual &&
+       test_path_is_missing yikes &&
+       git rev-list HEAD >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 545b461e51d4fd5df39c7d27953fb0f26f73e052..bad02cf5b83dbc014c23cc8ac66135e0cf095138 100755 (executable)
@@ -42,7 +42,7 @@ test_expect_success 'corrupt second commit object' \
    '
 
 test_expect_success 'rev-list should fail' '
-       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --all > /dev/null
+       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --all > /dev/null
 '
 
 test_expect_success 'git repack _MUST_ fail' \
index acd7f5ab80d9c8a78c915ec9b868e704237841c5..de0e5a5d3646cdd7e60b98b566bb075200f311ad 100755 (executable)
@@ -278,7 +278,19 @@ test_expect_success 'verify skipping tree iteration when not collecting omits' '
        test_line_count = 2 actual &&
 
        # Make sure no other trees were considered besides the root.
-       ! grep "Skipping contents of tree [^.]" filter_trace
+       ! grep "Skipping contents of tree [^.]" filter_trace &&
+
+       # Try this again with "combine:". If both sub-filters are skipping
+       # trees, the composite filter should also skip trees. This is not
+       # important unless the user does combine:tree:X+tree:Y or another filter
+       # besides "tree:" is implemented in the future which can skip trees.
+       GIT_TRACE=1 git -C r3 rev-list \
+               --objects --filter=combine:tree:1+tree:3 HEAD 2>filter_trace &&
+
+       # Only skip the dir1/ tree, which is shared between the two commits.
+       grep "Skipping contents of tree " filter_trace >actual &&
+       test_write_lines "Skipping contents of tree dir1/..." >expected &&
+       test_cmp expected actual
 '
 
 # Test tree:# filters.
@@ -330,6 +342,148 @@ test_expect_success 'verify tree:3 includes everything expected' '
        test_line_count = 10 actual
 '
 
+test_expect_success 'combine:... for a simple combination' '
+       git -C r3 rev-list --objects --filter=combine:tree:2+blob:none HEAD \
+               >actual &&
+
+       expect_has HEAD "" &&
+       expect_has HEAD~1 "" &&
+       expect_has HEAD dir1 &&
+
+       # There are also 2 commit objects
+       test_line_count = 5 actual &&
+
+       cp actual expected &&
+
+       # Try again using repeated --filter - this is equivalent to a manual
+       # combine with "combine:...+..."
+       git -C r3 rev-list --objects --filter=combine:tree:2 \
+               --filter=blob:none HEAD >actual &&
+
+       test_cmp expected actual
+'
+
+test_expect_success 'combine:... with URL encoding' '
+       git -C r3 rev-list --objects \
+               --filter=combine:tree%3a2+blob:%6Eon%65 HEAD >actual &&
+
+       expect_has HEAD "" &&
+       expect_has HEAD~1 "" &&
+       expect_has HEAD dir1 &&
+
+       # There are also 2 commit objects
+       test_line_count = 5 actual
+'
+
+expect_invalid_filter_spec () {
+       spec="$1" &&
+       err="$2" &&
+
+       test_must_fail git -C r3 rev-list --objects --filter="$spec" HEAD \
+               >actual 2>actual_stderr &&
+       test_must_be_empty actual &&
+       test_i18ngrep "$err" actual_stderr
+}
+
+test_expect_success 'combine:... while URL-encoding things that should not be' '
+       expect_invalid_filter_spec combine%3Atree:2+blob:none \
+               "invalid filter-spec"
+'
+
+test_expect_success 'combine: with nothing after the :' '
+       expect_invalid_filter_spec combine: "expected something after combine:"
+'
+
+test_expect_success 'parse error in first sub-filter in combine:' '
+       expect_invalid_filter_spec combine:tree:asdf+blob:none \
+               "expected .tree:<depth>."
+'
+
+test_expect_success 'combine:... with non-encoded reserved chars' '
+       expect_invalid_filter_spec combine:tree:2+sparse:@xyz \
+               "must escape char in sub-filter-spec: .@." &&
+       expect_invalid_filter_spec combine:tree:2+sparse:\` \
+               "must escape char in sub-filter-spec: .\`." &&
+       expect_invalid_filter_spec combine:tree:2+sparse:~abc \
+               "must escape char in sub-filter-spec: .\~."
+'
+
+test_expect_success 'validate err msg for "combine:<valid-filter>+"' '
+       expect_invalid_filter_spec combine:tree:2+ "expected .tree:<depth>."
+'
+
+test_expect_success 'combine:... with edge-case hex digits: Ff Aa 0 9' '
+       git -C r3 rev-list --objects --filter="combine:tree:2+bl%6Fb:n%6fne" \
+               HEAD >actual &&
+       test_line_count = 5 actual &&
+       git -C r3 rev-list --objects --filter="combine:tree%3A2+blob%3anone" \
+               HEAD >actual &&
+       test_line_count = 5 actual &&
+       git -C r3 rev-list --objects --filter="combine:tree:%30" HEAD >actual &&
+       test_line_count = 2 actual &&
+       git -C r3 rev-list --objects --filter="combine:tree:%39+blob:none" \
+               HEAD >actual &&
+       test_line_count = 5 actual
+'
+
+test_expect_success 'add sparse pattern blobs whose paths have reserved chars' '
+       cp r3/pattern r3/pattern1+renamed% &&
+       cp r3/pattern "r3/p;at%ter+n" &&
+       cp r3/pattern r3/^~pattern &&
+       git -C r3 add pattern1+renamed% "p;at%ter+n" ^~pattern &&
+       git -C r3 commit -m "add sparse pattern files with reserved chars"
+'
+
+test_expect_success 'combine:... with more than two sub-filters' '
+       git -C r3 rev-list --objects \
+               --filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern \
+               HEAD >actual &&
+
+       expect_has HEAD "" &&
+       expect_has HEAD~1 "" &&
+       expect_has HEAD~2 "" &&
+       expect_has HEAD dir1 &&
+       expect_has HEAD dir1/sparse1 &&
+       expect_has HEAD dir1/sparse2 &&
+
+       # Should also have 3 commits
+       test_line_count = 9 actual &&
+
+       # Try again, this time making sure the last sub-filter is only
+       # URL-decoded once.
+       cp actual expect &&
+
+       git -C r3 rev-list --objects \
+               --filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern1%2brenamed%25 \
+               HEAD >actual &&
+       test_cmp expect actual &&
+
+       # Use the same composite filter again, but with a pattern file name that
+       # requires encoding multiple characters, and use implicit filter
+       # combining.
+       test_when_finished "rm -f trace1" &&
+       GIT_TRACE=$(pwd)/trace1 git -C r3 rev-list --objects \
+               --filter=tree:3 --filter=blob:limit=40 \
+               --filter=sparse:oid="master:p;at%ter+n" \
+               HEAD >actual &&
+
+       test_cmp expect actual &&
+       grep "Add to combine filter-spec: sparse:oid=master:p%3bat%25ter%2bn" \
+               trace1 &&
+
+       # Repeat the above test, but this time, the characters to encode are in
+       # the LHS of the combined filter.
+       test_when_finished "rm -f trace2" &&
+       GIT_TRACE=$(pwd)/trace2 git -C r3 rev-list --objects \
+               --filter=sparse:oid=master:^~pattern \
+               --filter=tree:3 --filter=blob:limit=40 \
+               HEAD >actual &&
+
+       test_cmp expect actual &&
+       grep "Add to combine filter-spec: sparse:oid=master:%5e%7epattern" \
+               trace2
+'
+
 # Test provisional omit collection logic with a repo that has objects appearing
 # at multiple depths - first deeper than the filter's threshold, then shallow.
 
@@ -373,6 +527,37 @@ test_expect_success 'verify skipping tree iteration when collecting omits' '
        test_cmp expect actual
 '
 
+test_expect_success 'setup r5' '
+       git init r5 &&
+       mkdir -p r5/subdir &&
+
+       echo 1     >r5/short-root          &&
+       echo 12345 >r5/long-root           &&
+       echo a     >r5/subdir/short-subdir &&
+       echo abcde >r5/subdir/long-subdir  &&
+
+       git -C r5 add short-root long-root subdir &&
+       git -C r5 commit -m "commit msg"
+'
+
+test_expect_success 'verify collecting omits in combined: filter' '
+       # Note that this test guards against the naive implementation of simply
+       # giving both filters the same "omits" set and expecting it to
+       # automatically merge them.
+       git -C r5 rev-list --objects --quiet --filter-print-omitted \
+               --filter=combine:tree:2+blob:limit=3 HEAD >actual &&
+
+       # Expect 0 trees/commits, 3 blobs omitted (all blobs except short-root)
+       omitted_1=$(echo 12345 | git hash-object --stdin) &&
+       omitted_2=$(echo a     | git hash-object --stdin) &&
+       omitted_3=$(echo abcde | git hash-object --stdin) &&
+
+       grep ~$omitted_1 actual &&
+       grep ~$omitted_2 actual &&
+       grep ~$omitted_3 actual &&
+       test_line_count = 3 actual
+'
+
 # Test tree:<depth> where a tree is iterated to twice - once where a subentry is
 # too deep to be included, and again where the blob inside it is shallow enough
 # to be included. This makes sure we don't use LOFR_MARK_SEEN incorrectly (we
@@ -441,11 +626,4 @@ test_expect_success 'expand blob limit in protocol' '
        grep "blob:limit=1024" trace
 '
 
-test_expect_success 'expand tree depth limit in protocol' '
-       GIT_TRACE_PACKET="$(pwd)/tree_trace" git -c protocol.version=2 clone \
-               --filter=tree:0k "file://$(pwd)/r2" tree &&
-       ! grep "tree:0k" tree_trace &&
-       grep "tree:0" tree_trace
-'
-
 test_done
index ab69aa176d14bf2d8dd88be1d7cbae0560609be8..9c910ce746733cabaafd34adceeb59cf53dca1f7 100755 (executable)
@@ -526,6 +526,25 @@ test_expect_success 'Check ambiguous head and tag refs II (loose)' '
        test_cmp expected actual
 '
 
+test_expect_success 'create tag without tagger' '
+       git tag -a -m "Broken tag" taggerless &&
+       git tag -f taggerless $(git cat-file tag taggerless |
+               sed -e "/^tagger /d" |
+               git hash-object --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
 test_expect_success 'an unusual tag with an incomplete line' '
 
        git tag -m "bogo" bogo &&
index 033871ee5f35c1a143aec6d2b30c5116bf3f94f5..f30b4849b64ea8bd79a82117333b0f7f225def63 100755 (executable)
@@ -137,7 +137,7 @@ test_expect_success 'do not complain about existing broken links (commit)' '
        some message
        EOF
        commit=$(git hash-object -t commit -w broken-commit) &&
-       git gc 2>stderr &&
+       git gc -q 2>stderr &&
        verbose git cat-file -e $commit &&
        test_must_be_empty stderr
 '
@@ -147,7 +147,7 @@ test_expect_success 'do not complain about existing broken links (tree)' '
        100644 blob 0000000000000000000000000000000000000003    foo
        EOF
        tree=$(git mktree --missing <broken-tree) &&
-       git gc 2>stderr &&
+       git gc -q 2>stderr &&
        git cat-file -e $tree &&
        test_must_be_empty stderr
 '
@@ -162,7 +162,7 @@ test_expect_success 'do not complain about existing broken links (tag)' '
        this is a broken tag
        EOF
        tag=$(git hash-object -t tag -w broken-tag) &&
-       git gc 2>stderr &&
+       git gc -q 2>stderr &&
        git cat-file -e $tag &&
        test_must_be_empty stderr
 '
diff --git a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh
new file mode 100755 (executable)
index 0000000..b348545
--- /dev/null
@@ -0,0 +1,281 @@
+#!/bin/sh
+
+test_description='pre-commit and pre-merge-commit hooks'
+
+. ./test-lib.sh
+
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+PRECOMMIT="$HOOKDIR/pre-commit"
+PREMERGE="$HOOKDIR/pre-merge-commit"
+
+# Prepare sample scripts that write their $0 to actual_hooks
+test_expect_success 'sample script setup' '
+       mkdir -p "$HOOKDIR" &&
+       write_script "$HOOKDIR/success.sample" <<-\EOF &&
+       echo $0 >>actual_hooks
+       exit 0
+       EOF
+       write_script "$HOOKDIR/fail.sample" <<-\EOF &&
+       echo $0 >>actual_hooks
+       exit 1
+       EOF
+       write_script "$HOOKDIR/non-exec.sample" <<-\EOF &&
+       echo $0 >>actual_hooks
+       exit 1
+       EOF
+       chmod -x "$HOOKDIR/non-exec.sample" &&
+       write_script "$HOOKDIR/require-prefix.sample" <<-\EOF &&
+       echo $0 >>actual_hooks
+       test $GIT_PREFIX = "success/"
+       EOF
+       write_script "$HOOKDIR/check-author.sample" <<-\EOF
+       echo $0 >>actual_hooks
+       test "$GIT_AUTHOR_NAME" = "New Author" &&
+       test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
+       EOF
+'
+
+test_expect_success 'root commit' '
+       echo "root" >file &&
+       git add file &&
+       git commit -m "zeroth" &&
+       git checkout -b side &&
+       echo "foo" >foo &&
+       git add foo &&
+       git commit -m "make it non-ff" &&
+       git branch side-orig side &&
+       git checkout master
+'
+
+test_expect_success 'setup conflicting branches' '
+       test_when_finished "git checkout master" &&
+       git checkout -b conflicting-a master &&
+       echo a >conflicting &&
+       git add conflicting &&
+       git commit -m conflicting-a &&
+       git checkout -b conflicting-b master &&
+       echo b >conflicting &&
+       git add conflicting &&
+       git commit -m conflicting-b
+'
+
+test_expect_success 'with no hook' '
+       test_when_finished "rm -f actual_hooks" &&
+       echo "foo" >file &&
+       git add file &&
+       git commit -m "first" &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with no hook (merge)' '
+       test_when_finished "rm -f actual_hooks" &&
+       git branch -f side side-orig &&
+       git checkout side &&
+       git merge -m "merge master" master &&
+       git checkout master &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success '--no-verify with no hook' '
+       test_when_finished "rm -f actual_hooks" &&
+       echo "bar" >file &&
+       git add file &&
+       git commit --no-verify -m "bar" &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success '--no-verify with no hook (merge)' '
+       test_when_finished "rm -f actual_hooks" &&
+       git branch -f side side-orig &&
+       git checkout side &&
+       git merge --no-verify -m "merge master" master &&
+       git checkout master &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with succeeding hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
+       cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
+       echo "$PRECOMMIT" >expected_hooks &&
+       echo "more" >>file &&
+       git add file &&
+       git commit -m "more" &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'with succeeding hook (merge)' '
+       test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
+       cp "$HOOKDIR/success.sample" "$PREMERGE" &&
+       echo "$PREMERGE" >expected_hooks &&
+       git checkout side &&
+       git merge -m "merge master" master &&
+       git checkout master &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'automatic merge fails; both hooks are available' '
+       test_when_finished "rm -f \"$PREMERGE\" \"$PRECOMMIT\"" &&
+       test_when_finished "rm -f expected_hooks actual_hooks" &&
+       test_when_finished "git checkout master" &&
+       cp "$HOOKDIR/success.sample" "$PREMERGE" &&
+       cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
+
+       git checkout conflicting-a &&
+       test_must_fail git merge -m "merge conflicting-b" conflicting-b &&
+       test_path_is_missing actual_hooks &&
+
+       echo "$PRECOMMIT" >expected_hooks &&
+       echo a+b >conflicting &&
+       git add conflicting &&
+       git commit -m "resolve conflict" &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success '--no-verify with succeeding hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+       cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
+       echo "even more" >>file &&
+       git add file &&
+       git commit --no-verify -m "even more" &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success '--no-verify with succeeding hook (merge)' '
+       test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+       cp "$HOOKDIR/success.sample" "$PREMERGE" &&
+       git branch -f side side-orig &&
+       git checkout side &&
+       git merge --no-verify -m "merge master" master &&
+       git checkout master &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with failing hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
+       cp "$HOOKDIR/fail.sample" "$PRECOMMIT" &&
+       echo "$PRECOMMIT" >expected_hooks &&
+       echo "another" >>file &&
+       git add file &&
+       test_must_fail git commit -m "another" &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success '--no-verify with failing hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+       cp "$HOOKDIR/fail.sample" "$PRECOMMIT" &&
+       echo "stuff" >>file &&
+       git add file &&
+       git commit --no-verify -m "stuff" &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with failing hook (merge)' '
+       test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
+       cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
+       echo "$PREMERGE" >expected_hooks &&
+       git checkout side &&
+       test_must_fail git merge -m "merge master" master &&
+       git checkout master &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success '--no-verify with failing hook (merge)' '
+       test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+       cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
+       git branch -f side side-orig &&
+       git checkout side &&
+       git merge --no-verify -m "merge master" master &&
+       git checkout master &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM 'with non-executable hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+       cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
+       echo "content" >>file &&
+       git add file &&
+       git commit -m "content" &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM '--no-verify with non-executable hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+       cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
+       echo "more content" >>file &&
+       git add file &&
+       git commit --no-verify -m "more content" &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM 'with non-executable hook (merge)' '
+       test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+       cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
+       git branch -f side side-orig &&
+       git checkout side &&
+       git merge -m "merge master" master &&
+       git checkout master &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM '--no-verify with non-executable hook (merge)' '
+       test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+       cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
+       git branch -f side side-orig &&
+       git checkout side &&
+       git merge --no-verify -m "merge master" master &&
+       git checkout master &&
+       test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with hook requiring GIT_PREFIX' '
+       test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks success" &&
+       cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&
+       echo "$PRECOMMIT" >expected_hooks &&
+       echo "more content" >>file &&
+       git add file &&
+       mkdir success &&
+       (
+               cd success &&
+               git commit -m "hook requires GIT_PREFIX = success/"
+       ) &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'with failing hook requiring GIT_PREFIX' '
+       test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks fail" &&
+       cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&
+       echo "$PRECOMMIT" >expected_hooks &&
+       echo "more content" >>file &&
+       git add file &&
+       mkdir fail &&
+       (
+               cd fail &&
+               test_must_fail git commit -m "hook must fail"
+       ) &&
+       git checkout -- file &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'check the author in hook' '
+       test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
+       cp "$HOOKDIR/check-author.sample" "$PRECOMMIT" &&
+       cat >expected_hooks <<-EOF &&
+       $PRECOMMIT
+       $PRECOMMIT
+       $PRECOMMIT
+       EOF
+       test_must_fail git commit --allow-empty -m "by a.u.thor" &&
+       (
+               GIT_AUTHOR_NAME="New Author" &&
+               GIT_AUTHOR_EMAIL="newauthor@example.com" &&
+               export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
+               git commit --allow-empty -m "by new.author via env" &&
+               git show -s
+       ) &&
+       git commit --author="New Author <newauthor@example.com>" \
+               --allow-empty -m "by new.author via command line" &&
+       git show -s &&
+       test_cmp expected_hooks actual_hooks
+'
+
+test_done
diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh
deleted file mode 100755 (executable)
index 984889b..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/bin/sh
-
-test_description='pre-commit hook'
-
-. ./test-lib.sh
-
-test_expect_success 'with no hook' '
-
-       echo "foo" > file &&
-       git add file &&
-       git commit -m "first"
-
-'
-
-test_expect_success '--no-verify with no hook' '
-
-       echo "bar" > file &&
-       git add file &&
-       git commit --no-verify -m "bar"
-
-'
-
-# now install hook that always succeeds
-HOOKDIR="$(git rev-parse --git-dir)/hooks"
-HOOK="$HOOKDIR/pre-commit"
-mkdir -p "$HOOKDIR"
-cat > "$HOOK" <<EOF
-#!/bin/sh
-exit 0
-EOF
-chmod +x "$HOOK"
-
-test_expect_success 'with succeeding hook' '
-
-       echo "more" >> file &&
-       git add file &&
-       git commit -m "more"
-
-'
-
-test_expect_success '--no-verify with succeeding hook' '
-
-       echo "even more" >> file &&
-       git add file &&
-       git commit --no-verify -m "even more"
-
-'
-
-# now a hook that fails
-cat > "$HOOK" <<EOF
-#!/bin/sh
-exit 1
-EOF
-
-test_expect_success 'with failing hook' '
-
-       echo "another" >> file &&
-       git add file &&
-       test_must_fail git commit -m "another"
-
-'
-
-test_expect_success '--no-verify with failing hook' '
-
-       echo "stuff" >> file &&
-       git add file &&
-       git commit --no-verify -m "stuff"
-
-'
-
-chmod -x "$HOOK"
-test_expect_success POSIXPERM 'with non-executable hook' '
-
-       echo "content" >> file &&
-       git add file &&
-       git commit -m "content"
-
-'
-
-test_expect_success POSIXPERM '--no-verify with non-executable hook' '
-
-       echo "more content" >> file &&
-       git add file &&
-       git commit --no-verify -m "more content"
-
-'
-chmod +x "$HOOK"
-
-# a hook that checks $GIT_PREFIX and succeeds inside the
-# success/ subdirectory only
-cat > "$HOOK" <<EOF
-#!/bin/sh
-test \$GIT_PREFIX = success/
-EOF
-
-test_expect_success 'with hook requiring GIT_PREFIX' '
-
-       echo "more content" >> file &&
-       git add file &&
-       mkdir success &&
-       (
-               cd success &&
-               git commit -m "hook requires GIT_PREFIX = success/"
-       ) &&
-       rmdir success
-'
-
-test_expect_success 'with failing hook requiring GIT_PREFIX' '
-
-       echo "more content" >> file &&
-       git add file &&
-       mkdir fail &&
-       (
-               cd fail &&
-               test_must_fail git commit -m "hook must fail"
-       ) &&
-       rmdir fail &&
-       git checkout -- file
-'
-
-test_expect_success 'check the author in hook' '
-       write_script "$HOOK" <<-\EOF &&
-       test "$GIT_AUTHOR_NAME" = "New Author" &&
-       test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
-       EOF
-       test_must_fail git commit --allow-empty -m "by a.u.thor" &&
-       (
-               GIT_AUTHOR_NAME="New Author" &&
-               GIT_AUTHOR_EMAIL="newauthor@example.com" &&
-               export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
-               git commit --allow-empty -m "by new.author via env" &&
-               git show -s
-       ) &&
-       git commit --author="New Author <newauthor@example.com>" \
-               --allow-empty -m "by new.author via command line" &&
-       git show -s
-'
-
-test_done
index 75512c340366f3034c58effb62c8796d0b1463a8..5d98d66dbdb1ebc427ad4ddd50c1ce058ada0aaf 100755 (executable)
@@ -1698,6 +1698,69 @@ do
        '
 done
 
+test_expect_success 'git config - section' '
+       test_completion "git config br" <<-\EOF
+       branch.Z
+       browser.Z
+       EOF
+'
+
+test_expect_success 'git config - variable name' '
+       test_completion "git config log.d" <<-\EOF
+       log.date Z
+       log.decorate Z
+       EOF
+'
+
+test_expect_success 'git config - value' '
+       test_completion "git config color.pager " <<-\EOF
+       false Z
+       true Z
+       EOF
+'
+
+test_expect_success 'git -c - section' '
+       test_completion "git -c br" <<-\EOF
+       branch.Z
+       browser.Z
+       EOF
+'
+
+test_expect_success 'git -c - variable name' '
+       test_completion "git -c log.d" <<-\EOF
+       log.date=Z
+       log.decorate=Z
+       EOF
+'
+
+test_expect_success 'git -c - value' '
+       test_completion "git -c color.pager=" <<-\EOF
+       false Z
+       true Z
+       EOF
+'
+
+test_expect_success 'git clone --config= - section' '
+       test_completion "git clone --config=br" <<-\EOF
+       branch.Z
+       browser.Z
+       EOF
+'
+
+test_expect_success 'git clone --config= - variable name' '
+       test_completion "git clone --config=log.d" <<-\EOF
+       log.date=Z
+       log.decorate=Z
+       EOF
+'
+
+test_expect_success 'git clone --config= - value' '
+       test_completion "git clone --config=color.pager=" <<-\EOF
+       false Z
+       true Z
+       EOF
+'
+
 test_expect_success 'sourcing the completion script clears cached commands' '
        __git_compute_all_commands &&
        verbose test -n "$__git_all_commands" &&
diff --git a/templates/hooks--pre-merge-commit.sample b/templates/hooks--pre-merge-commit.sample
new file mode 100755 (executable)
index 0000000..399eab1
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git merge" with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message to
+# stderr if it wants to stop the merge commit.
+#
+# To enable this hook, rename this file to "pre-merge-commit".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+        exec "$GIT_DIR/hooks/pre-commit"
+:
diff --git a/trace.c b/trace.c
index fa4a2e7120e405f3cf2d12422802b785f9e37fad..b3ef0e627f8cec18433c17ffb080575894882758 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -88,8 +88,6 @@ static int prepare_trace_line(const char *file, int line,
        if (!trace_want(key))
                return 0;
 
-       set_try_to_free_routine(NULL);  /* is never reset */
-
        /* unit tests may want to disable additional trace output */
        if (trace_want(&trace_bare))
                return 1;
index 96955d4004a8f5feaac1a5cc9baa189c67597f16..9e1279b928bb3eadf110e43c091b4a3ba789fe3b 100644 (file)
@@ -700,13 +700,9 @@ static int fetch(struct transport *transport,
                set_helper_option(transport, "update-shallow", "true");
 
        if (data->transport_options.filter_options.choice) {
-               struct strbuf expanded_filter_spec = STRBUF_INIT;
-               expand_list_objects_filter_spec(
-                       &data->transport_options.filter_options,
-                       &expanded_filter_spec);
-               set_helper_option(transport, "filter",
-                                 expanded_filter_spec.buf);
-               strbuf_release(&expanded_filter_spec);
+               const char *spec = expand_list_objects_filter_spec(
+                       &data->transport_options.filter_options);
+               set_helper_option(transport, "filter", spec);
        }
 
        if (data->transport_options.negotiation_tips)
index 662a2d9ae0bfb0a82596bc693d30e7f49595b934..ae558af94414bfba0592c0915a0cd552438c4f1f 100644 (file)
@@ -230,6 +230,7 @@ static int set_git_option(struct git_transport_options *opts,
                opts->no_dependents = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_LIST_OBJECTS_FILTER)) {
+               list_objects_filter_die_if_populated(&opts->filter_options);
                parse_list_objects_filter(&opts->filter_options, value);
                return 0;
        }
index 50f257bd5c768ae87f8e526c587fa11afad98ad6..9c25126aecaabc8f0e3d0841ee1aa959acdf8dbd 100644 (file)
@@ -16,7 +16,7 @@
 #include "submodule-config.h"
 #include "fsmonitor.h"
 #include "object-store.h"
-#include "fetch-object.h"
+#include "promisor-remote.h"
 
 /*
  * Error messages expected by scripts out of plumbing commands such as
@@ -400,7 +400,7 @@ static int check_updates(struct unpack_trees_options *o)
                load_gitmodules_file(index, &state);
 
        enable_delayed_checkout(&state);
-       if (repository_format_partial_clone && o->update && !o->dry_run) {
+       if (has_promisor_remote() && o->update && !o->dry_run) {
                /*
                 * Prefetch the objects that are to be checked out in the loop
                 * below.
@@ -419,8 +419,8 @@ static int check_updates(struct unpack_trees_options *o)
                        oid_array_append(&to_fetch, &ce->oid);
                }
                if (to_fetch.nr)
-                       fetch_objects(repository_format_partial_clone,
-                                     to_fetch.oid, to_fetch.nr);
+                       promisor_remote_get_direct(the_repository,
+                                                  to_fetch.oid, to_fetch.nr);
                oid_array_clear(&to_fetch);
        }
        for (i = 0; i < index->cache_nr; i++) {
index 222cd3ad8960f352ee711915323ec1f36e5e7673..875db9299682c5d9fb593241e1907c3df9e7d597 100644 (file)
@@ -140,18 +140,17 @@ static void create_pack_file(const struct object_array *have_obj,
                argv_array_push(&pack_objects.args, "--delta-base-offset");
        if (use_include_tag)
                argv_array_push(&pack_objects.args, "--include-tag");
-       if (filter_options.filter_spec) {
-               struct strbuf expanded_filter_spec = STRBUF_INIT;
-               expand_list_objects_filter_spec(&filter_options,
-                                               &expanded_filter_spec);
+       if (filter_options.choice) {
+               const char *spec =
+                       expand_list_objects_filter_spec(&filter_options);
                if (pack_objects.use_shell) {
                        struct strbuf buf = STRBUF_INIT;
-                       sq_quote_buf(&buf, expanded_filter_spec.buf);
+                       sq_quote_buf(&buf, spec);
                        argv_array_pushf(&pack_objects.args, "--filter=%s", buf.buf);
                        strbuf_release(&buf);
                } else {
                        argv_array_pushf(&pack_objects.args, "--filter=%s",
-                                        expanded_filter_spec.buf);
+                                        spec);
                }
        }
 
@@ -884,6 +883,7 @@ static void receive_needs(struct packet_reader *reader, struct object_array *wan
                if (skip_prefix(reader->line, "filter ", &arg)) {
                        if (!filter_capability_requested)
                                die("git upload-pack: filtering capability not negotiated");
+                       list_objects_filter_die_if_populated(&filter_options);
                        parse_list_objects_filter(&filter_options, arg);
                        continue;
                }
@@ -1305,6 +1305,7 @@ static void process_args(struct packet_reader *request,
                }
 
                if (allow_filter && skip_prefix(arg, "filter ", &p)) {
+                       list_objects_filter_die_if_populated(&filter_options);
                        parse_list_objects_filter(&filter_options, p);
                        continue;
                }
diff --git a/url.c b/url.c
index 1b8ef78ceab03784ad48f8411b20669e2ea1ea1f..e34e5e751737aeb10a6afecc0a2cdf0ec78fa5fc 100644 (file)
--- a/url.c
+++ b/url.c
@@ -86,6 +86,12 @@ char *url_decode_mem(const char *url, int len)
        return url_decode_internal(&url, len, NULL, &out, 0);
 }
 
+char *url_percent_decode(const char *encoded)
+{
+       struct strbuf out = STRBUF_INIT;
+       return url_decode_internal(&encoded, strlen(encoded), NULL, &out, 0);
+}
+
 char *url_decode_parameter_name(const char **query)
 {
        struct strbuf out = STRBUF_INIT;
diff --git a/url.h b/url.h
index 00b7d58c33e38b2a48e4e2ab557239b1ed0c759d..2a27c3427763b210b123b56c1e6553f8012f00bc 100644 (file)
--- a/url.h
+++ b/url.h
@@ -7,6 +7,14 @@ int is_url(const char *url);
 int is_urlschemechar(int first_flag, int ch);
 char *url_decode(const char *url);
 char *url_decode_mem(const char *url, int len);
+
+/*
+ * Similar to the url_decode_{,mem} methods above, but doesn't assume there
+ * is a scheme followed by a : at the start of the string. Instead, %-sequences
+ * before any : are also parsed.
+ */
+char *url_percent_decode(const char *encoded);
+
 char *url_decode_parameter_name(const char **query);
 char *url_decode_parameter_value(const char **query);
 
index e74a6d402255b0eaf1022863ba30305930d29b6f..86e3244e15ddc8beaf2fc6e6a0b4b0767c87f96c 100644 (file)
@@ -23,6 +23,14 @@ IPATTERN("ada",
         "[a-zA-Z][a-zA-Z0-9_]*"
         "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?"
         "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"),
+PATTERNS("dts",
+        "!;\n"
+        /* lines beginning with a word optionally preceded by '&' or the root */
+        "^[ \t]*((/|&?[a-zA-Z_]).*)",
+        /* -- */
+        /* Property names and math operators */
+        "[a-zA-Z0-9,._+?#-]+"
+        "|[-+*/%&^|!~]|>>|<<|&&|\\|\\|"),
 IPATTERN("fortran",
         "!^([C*]|[ \t]*!)\n"
         "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n"
index 1e45ab7b92749b653484f522701f5d86521b03c7..c55d7722d7be5a5933f2b662f685e6536357380e 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -4,12 +4,6 @@
 #include "cache.h"
 #include "config.h"
 
-static void do_nothing(size_t size)
-{
-}
-
-static void (*try_to_free_routine)(size_t size) = do_nothing;
-
 static int memory_limit_check(size_t size, int gentle)
 {
        static size_t limit = 0;
@@ -30,24 +24,11 @@ static int memory_limit_check(size_t size, int gentle)
        return 0;
 }
 
-try_to_free_t set_try_to_free_routine(try_to_free_t routine)
-{
-       try_to_free_t old = try_to_free_routine;
-       if (!routine)
-               routine = do_nothing;
-       try_to_free_routine = routine;
-       return old;
-}
-
 char *xstrdup(const char *str)
 {
        char *ret = strdup(str);
-       if (!ret) {
-               try_to_free_routine(strlen(str) + 1);
-               ret = strdup(str);
-               if (!ret)
-                       die("Out of memory, strdup failed");
-       }
+       if (!ret)
+               die("Out of memory, strdup failed");
        return ret;
 }
 
@@ -61,19 +42,13 @@ static void *do_xmalloc(size_t size, int gentle)
        if (!ret && !size)
                ret = malloc(1);
        if (!ret) {
-               try_to_free_routine(size);
-               ret = malloc(size);
-               if (!ret && !size)
-                       ret = malloc(1);
-               if (!ret) {
-                       if (!gentle)
-                               die("Out of memory, malloc failed (tried to allocate %lu bytes)",
-                                   (unsigned long)size);
-                       else {
-                               error("Out of memory, malloc failed (tried to allocate %lu bytes)",
-                                     (unsigned long)size);
-                               return NULL;
-                       }
+               if (!gentle)
+                       die("Out of memory, malloc failed (tried to allocate %lu bytes)",
+                           (unsigned long)size);
+               else {
+                       error("Out of memory, malloc failed (tried to allocate %lu bytes)",
+                             (unsigned long)size);
+                       return NULL;
                }
        }
 #ifdef XMALLOC_POISON
@@ -138,14 +113,8 @@ void *xrealloc(void *ptr, size_t size)
        ret = realloc(ptr, size);
        if (!ret && !size)
                ret = realloc(ptr, 1);
-       if (!ret) {
-               try_to_free_routine(size);
-               ret = realloc(ptr, size);
-               if (!ret && !size)
-                       ret = realloc(ptr, 1);
-               if (!ret)
-                       die("Out of memory, realloc failed");
-       }
+       if (!ret)
+               die("Out of memory, realloc failed");
        return ret;
 }
 
@@ -160,14 +129,8 @@ void *xcalloc(size_t nmemb, size_t size)
        ret = calloc(nmemb, size);
        if (!ret && (!nmemb || !size))
                ret = calloc(1, 1);
-       if (!ret) {
-               try_to_free_routine(nmemb * size);
-               ret = calloc(nmemb, size);
-               if (!ret && (!nmemb || !size))
-                       ret = calloc(1, 1);
-               if (!ret)
-                       die("Out of memory, calloc failed");
-       }
+       if (!ret)
+               die("Out of memory, calloc failed");
        return ret;
 }