Merge branch 'jk/add-i-use-pathspecs'
authorJunio C Hamano <gitster@pobox.com>
Fri, 17 Mar 2017 20:50:26 +0000 (13:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Mar 2017 20:50:26 +0000 (13:50 -0700)
"git add -p <pathspec>" unnecessarily expanded the pathspec to a
list of individual files that matches the pathspec by running "git
ls-files <pathspec>", before feeding it to "git diff-index" to see
which paths have changes, because historically the pathspec
language supported by "diff-index" was weaker.  These days they are
equivalent and there is no reason to internally expand it.  This
helps both performance and avoids command line argument limit on
some platforms.

* jk/add-i-use-pathspecs:
  add--interactive: do not expand pathspecs with ls-files

220 files changed:
.gitignore
.mailmap
.travis.yml
Documentation/RelNotes/2.12.1.txt [new file with mode: 0644]
Documentation/RelNotes/2.13.0.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-format.txt
Documentation/diff-generate-patch.txt
Documentation/diff-options.txt
Documentation/git-apply.txt
Documentation/git-branch.txt
Documentation/git-commit.txt
Documentation/git-describe.txt
Documentation/git-filter-branch.txt
Documentation/git-for-each-ref.txt
Documentation/git-ls-files.txt
Documentation/git-ls-tree.txt
Documentation/git-name-rev.txt
Documentation/git-rev-parse.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitdiffcore.txt
Documentation/glossary-content.txt
Documentation/technical/api-gitattributes.txt
Documentation/technical/api-parse-options.txt
GIT-VERSION-GEN
Makefile
README.md
RelNotes
abspath.c
archive.c
attr.c
attr.h
bisect.c
branch.c
branch.h
builtin.h
builtin/am.c
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/check-attr.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/commit.c
builtin/count-objects.c
builtin/describe.c
builtin/diff-tree.c
builtin/fast-export.c
builtin/fetch-pack.c
builtin/fmt-merge-msg.c
builtin/fsck.c
builtin/gc.c
builtin/grep.c
builtin/init-db.c
builtin/log.c
builtin/merge-base.c
builtin/merge.c
builtin/name-rev.c
builtin/notes.c
builtin/pack-objects.c
builtin/prune-packed.c
builtin/prune.c
builtin/rebase--helper.c [new file with mode: 0644]
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/replace.c
builtin/reset.c
builtin/rev-parse.c
builtin/shortlog.c
builtin/show-branch.c
builtin/submodule--helper.c
builtin/symbolic-ref.c
builtin/tag.c
builtin/update-index.c
builtin/update-ref.c
cache.h
ci/run-linux32-build.sh [new file with mode: 0755]
commit.c
commit.h
common-main.c
config.c
config.mak.uname
configure.ac
connect.c
contrib/coccinelle/array.cocci
contrib/coccinelle/strbuf.cocci
contrib/completion/git-completion.bash
contrib/remote-helpers/git-remote-bzr
contrib/remote-helpers/git-remote-hg
convert.c
diff.c
dir.c
environment.c
ewah/ewah_io.c
fast-import.c
fetch-pack.c
fetch-pack.h
git-add--interactive.perl
git-compat-util.h
git-filter-branch.sh
git-rebase--interactive.sh
git-send-email.perl
git-stash.sh
git-svn.perl
git.c
hex.c
http-walker.c
http.c
ident.c
line-log.c
ll-merge.c
log-tree.c
log-tree.h
merge-recursive.c
object.h
oidset.c [new file with mode: 0644]
oidset.h [new file with mode: 0644]
pack-objects.h
pathspec.c
pathspec.h
preload-index.c
pretty.c
progress.c
reachable.c
read-cache.c
ref-filter.c
ref-filter.h
reflog-walk.c
refs.c
refs.h
refs/files-backend.c
refs/refs-internal.h
remote.c
remote.h
revision.c
send-pack.c
setup.c
sha1_file.c
sha1_name.c
split-index.c
split-index.h
strbuf.c
strbuf.h
submodule.c
submodule.h
t/gitweb-lib.sh
t/interop/.gitignore [new file with mode: 0644]
t/interop/Makefile [new file with mode: 0644]
t/interop/README [new file with mode: 0644]
t/interop/i0000-basic.sh [new file with mode: 0755]
t/interop/i5500-git-daemon.sh [new file with mode: 0755]
t/interop/interop-lib.sh [new file with mode: 0644]
t/lib-git-daemon.sh
t/lib-httpd/apache.conf
t/perf/p0001-rev-list.sh
t/perf/p7000-filter-branch.sh
t/perf/perf-lib.sh
t/perf/run
t/t0003-attributes.sh
t/t0100-previous.sh
t/t1300-repo-config.sh
t/t1400-update-ref.sh
t/t1500-rev-parse.sh
t/t1501-work-tree.sh
t/t1700-split-index.sh
t/t2027-worktree-list.sh
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3204-branch-name-interpretation.sh [new file with mode: 0755]
t/t3404-rebase-interactive.sh
t/t3701-add-interactive.sh
t/t3903-stash.sh
t/t3905-stash-include-untracked.sh
t/t4035-diff-quiet.sh
t/t4211-line-log.sh
t/t5316-pack-delta-depth.sh [new file with mode: 0755]
t/t5400-send-pack.sh
t/t5500-fetch-pack.sh
t/t5505-remote.sh
t/t5512-ls-remote.sh
t/t5516-fetch-push.sh
t/t5550-http-fetch-dumb.sh
t/t5601-clone.sh
t/t6007-rev-list-cherry-pick-file.sh
t/t6040-tracking-info.sh
t/t6045-merge-rename-delete.sh [new file with mode: 0755]
t/t6120-describe.sh
t/t6132-pathspec-exclude.sh
t/t6135-pathspec-with-attrs.sh [new file with mode: 0755]
t/t6300-for-each-ref.sh
t/t6302-for-each-ref-filter.sh
t/t6500-gc.sh
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t7518-ident-corner-cases.sh [new file with mode: 0755]
t/t7810-grep.sh
t/t9001-send-email.sh
t/t9200-git-cvsexportcommit.sh
t/t9600-cvsimport.sh
t/t9902-completion.sh
tempfile.c
transport-helper.c
transport.c
transport.h
upload-pack.c
urlmatch.c
urlmatch.h
userdiff.c
worktree.c
wrapper.c
ws.c
wt-status.c
xdiff/xemit.c

index b1020b8..833ef3b 100644 (file)
 /git-read-tree
 /git-rebase
 /git-rebase--am
+/git-rebase--helper
 /git-rebase--interactive
 /git-rebase--merge
 /git-receive-pack
index ab59b2f..e06526a 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -177,6 +177,7 @@ Paolo Bonzini <bonzini@gnu.org> <paolo.bonzini@lu.unisi.ch>
 Pascal Obry <pascal@obry.net> <pascal.obry@gmail.com>
 Pascal Obry <pascal@obry.net> <pascal.obry@wanadoo.fr>
 Pat Notz <patnotz@gmail.com> <pknotz@sandia.gov>
+Patrick Steinhardt <ps@pks.im> <patrick.steinhardt@elego.de>
 Paul Mackerras <paulus@samba.org> <paulus@dorrigo.(none)>
 Paul Mackerras <paulus@samba.org> <paulus@pogo.(none)>
 Peter Baumann <waste.manager@gmx.de> <Peter.B.Baumann@stud.informatik.uni-erlangen.de>
index 9c63c8c..591cc57 100644 (file)
@@ -39,6 +39,27 @@ env:
 
 matrix:
   include:
+    - env: Linux32
+      os: linux
+      services:
+        - docker
+      before_install:
+        - docker pull daald/ubuntu32:xenial
+      before_script:
+      script:
+        - >
+          docker run
+          --interactive
+          --env DEFAULT_TEST_TARGET
+          --env GIT_PROVE_OPTS
+          --env GIT_TEST_OPTS
+          --env GIT_TEST_CLONE_2GB
+          --volume "${PWD}:/usr/src/git"
+          daald/ubuntu32:xenial
+          /usr/src/git/ci/run-linux32-build.sh $(id -u $USER)
+        # Use the following command to debug the docker build locally:
+        # $ docker run -itv "${PWD}:/usr/src/git" --entrypoint /bin/bash daald/ubuntu32:xenial
+        # root@container:/# /usr/src/git/ci/run-linux32-build.sh
     - env: Documentation
       os: linux
       compiler: clang
diff --git a/Documentation/RelNotes/2.12.1.txt b/Documentation/RelNotes/2.12.1.txt
new file mode 100644 (file)
index 0000000..a74f7db
--- /dev/null
@@ -0,0 +1,41 @@
+Git v2.12.1 Release Notes
+=========================
+
+Fixes since v2.12
+-----------------
+
+ * Reduce authentication round-trip over HTTP when the server supports
+   just a single authentication method.  This also improves the
+   behaviour when Git is misconfigured to enable http.emptyAuth
+   against a server that does not authenticate without a username
+   (i.e. not using Kerberos etc., which makes http.emptyAuth
+   pointless).
+
+ * Windows port wants to use OpenSSL's implementation of SHA-1
+   routines, so let them.
+
+ * Add 32-bit Linux variant to the set of platforms to be tested with
+   Travis CI.
+
+ * When a redirected http transport gets an error during the
+   redirected request, we ignored the error we got from the server,
+   and ended up giving a not-so-useful error message.
+
+ * The patch subcommand of "git add -i" was meant to have paths
+   selection prompt just like other subcommand, unlike "git add -p"
+   directly jumps to hunk selection.  Recently, this was broken and
+   "add -i" lost the paths selection dialog, but it now has been
+   fixed.
+
+ * Git v2.12 was shipped with an embarrassing breakage where various
+   operations that verify paths given from the user stopped dying when
+   seeing an issue, and instead later triggering segfault.
+
+ * The code to parse "git log -L..." command line was buggy when there
+   are many ranges specified with -L; overrun of the allocated buffer
+   has been fixed.
+
+ * The command-line parsing of "git log -L" copied internal data
+   structures using incorrect size on ILP32 systems.
+
+Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.13.0.txt b/Documentation/RelNotes/2.13.0.txt
new file mode 100644 (file)
index 0000000..5c2348d
--- /dev/null
@@ -0,0 +1,297 @@
+Git 2.13 Release Notes
+======================
+
+Backward compatibility notes.
+
+ * Use of an empty string as a pathspec element that is used for
+   'everything matches' is still warned and Git asks users to use a
+   more explicit '.' for that instead.  The hope is that existing
+   users will not mind this change, and eventually the warning can be
+   turned into a hard error, upgrading the deprecation into removal of
+   this (mis)feature.  That is not scheduled to happen in the upcoming
+   release (yet).
+
+ * The historical argument order "git merge <msg> HEAD <commit>..."
+   has been deprecated for quite some time, and will be removed in a
+   future release.
+
+
+Updates since v2.12
+-------------------
+
+UI, Workflows & Features
+
+ * "git describe" and "git name-rev" have been taught to take more
+   than one refname patterns to restrict the set of refs to base their
+   naming output on, and also learned to take negative patterns to
+   name refs not to be used for naming via their "--exclude" option.
+
+ * Deletion of a branch "foo/bar" could remove .git/refs/heads/foo
+   once there no longer is any other branch whose name begins with
+   "foo/", but we didn't do so so far.  Now we do.
+
+ * When "git merge" detects a path that is renamed in one history
+   while the other history deleted (or modified) it, it now reports
+   both paths to help the user understand what is going on in the two
+   histories being merged.
+
+ * The <url> part in "http.<url>.<variable>" configuration variable
+   can now be spelled with '*' that serves as wildcard.
+   E.g. "http.https://*.example.com.proxy" can be used to specify the
+   proxy used for https://a.example.com, https://b.example.com, etc.,
+   i.e. any host in the example.com domain.
+
+ * "git tag" did not leave useful message when adding a new entry to
+   reflog; this was left unnoticed for a long time because refs/tags/*
+   doesn't keep reflog by default.
+
+ * The "negative" pathspec feature was somewhat more cumbersome to use
+   than necessary in that its short-hand used "!" which needed to be
+   escaped from shells, and it required "exclude from what?" specified.
+
+ * The command line options for ssh invocation needs to be tweaked for
+   some implementations of SSH (e.g. PuTTY plink wants "-P <port>"
+   while OpenSSH wants "-p <port>" to specify port to connect to), and
+   the variant was guessed when GIT_SSH environment variable is used
+   to specify it.  The logic to guess now applies to the command
+   specified by the newer GIT_SSH_COMMAND and also core.sshcommand
+   configuration variable, and comes with an escape hatch for users to
+   deal with misdetected cases.
+
+ * The "--git-path", "--git-common-dir", and "--shared-index-path"
+   options of "git rev-parse" did not produce usable output.  They are
+   now updated to show the path to the correct file, relative to where
+   the caller is.
+
+ * "git diff -W" has been taught to handle the case where a new
+   function is added at the end of the file better.
+
+ * "git update-ref -d" and other operations to delete references did
+   not leave any entry in HEAD's reflog when the reference being
+   deleted was the current branch.  This is not a problem in practice
+   because you do not want to delete the branch you are currently on,
+   but caused renaming of the current branch to something else not to
+   be logged in a useful way.
+
+ * "Cc:" on the trailer part does not have to conform to RFC strictly,
+   unlike in the e-mail header.  "git send-email" has been updated to
+   ignore anything after '>' when picking addresses, to allow non-address
+   cruft like " # stable 4.4" after the address.
+   (merge 9d3343961b jh/send-email-one-cc later to maint).
+
+ * When "git submodule init" decides that the submodule in the working
+   tree is its upstream, it now gives a warning as it is not a very
+   common setup.
+   (merge d1b3b81aab sb/submodule-init-url-selection later to maint).
+
+ * "git stash save" takes a pathspec so that the local changes can be
+   stashed away only partially.
+   (merge 9e140909f6 tg/stash-push later to maint).
+
+ * Documentation for "git ls-files" did not refer to core.quotePath.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The code to list branches in "git branch" has been consolidated
+   with the more generic ref-filter API.
+
+ * Resource usage while enumerating refs from alternate object store
+   has been optimized to help receiving end of "push" that hosts a
+   repository with many "forks".
+
+ * The gitattributes machinery is being taught to work better in a
+   multi-threaded environment.
+
+ * "git rebase -i" starts using the recently updated "sequencer" code.
+
+ * Code and design clean-up for the refs API.
+
+ * The preload-index code has been taught not to bother with the index
+   entries that are paths that are not checked out by "sparse checkout".
+
+ * Some warning() messages from "git clean" were updated to show the
+   errno from failed system calls.
+
+ * The "parse_config_key()" API function has been cleaned up.
+   (merge ad8c7cdadd jk/parse-config-key-cleanup later to maint).
+
+ * A test that creates a confusing branch whose name is HEAD has been
+   corrected not to do so.
+   (merge f0252ca23c jk/t6300-cleanup later to maint).
+
+ * The code that parses header fields in the commit object has been
+   updated for (micro)performance and code hygiene.
+   (merge b072504ce1 rs/commit-parsing-optim later to maint).
+
+ * An helper function to make it easier to append the result from
+   real_path() to a strbuf has been added.
+   (merge 33ad9ddd0b rs/strbuf-add-real-path later to maint).
+
+ * Reduce authentication round-trip over HTTP when the server supports
+   just a single authentication method.  This also improves the
+   behaviour when Git is misconfigured to enable http.emptyAuth
+   against a server that does not authenticate without a username
+   (i.e. not using Kerberos etc., which makes http.emptyAuth
+   pointless).
+   (merge 40a18fc77c jk/http-auth later to maint).
+
+ * Windows port wants to use OpenSSL's implementation of SHA-1
+   routines, so let them.
+   (merge 2cfc70f0de jh/mingw-openssl-sha1 later to maint).
+
+ * The t/perf performance test suite was not prepared to test not so
+   old versions of Git, but now it covers versions of Git that are not
+   so ancient.
+   (merge 28e1fb5466 jt/perf-updates later to maint).
+
+ * Add 32-bit Linux variant to the set of platforms to be tested with
+   Travis CI.
+   (merge 88dedd5e72 js/travis-32bit-linux later to maint).
+
+ * "git branch --list" takes the "--abbrev" and "--no-abbrev" options
+   to control the output of the object name in its "-v"(erbose)
+   output, but a recent update started ignoring them; fix it before
+   the breakage reaches to any released version.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.12
+-----------------
+
+Unless otherwise noted, all the fixes since v2.9 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * "git repack --depth=<n>" for a long time busted the specified depth
+   when reusing delta from existing packs.  This has been corrected.
+   (merge 42b766d765 jk/delta-chain-limit later to maint).
+
+ * The code to parse the command line "git grep <patterns>... <rev>
+   [[--] <pathspec>...]" has been cleaned up, and a handful of bugs
+   have been fixed (e.g. we used to check "--" if it is a rev).
+   (merge 131f3c96d2 jk/grep-no-index-fix later to maint).
+
+ * "git ls-remote" and "git archive --remote" are designed to work
+   without being in a directory under Git's control.  However, recent
+   updates revealed that we randomly look into a directory called
+   .git/ without actually doing necessary set-up when working in a
+   repository.  Stop doing so.
+   (merge 4b0c3c7735 jn/remote-helpers-with-git-dir later to maint).
+
+ * "git show-branch" expected there were only very short branch names
+   in the repository and used a fixed-length buffer to hold them
+   without checking for overflow.
+   (merge d3cc5f4c44 jk/show-branch-lift-name-len-limit later to maint).
+
+ * A caller of tempfile API that uses stdio interface to write to
+   files may ignore errors while writing, which is detected when
+   tempfile is closed (with a call to ferror()).  By that time, the
+   original errno that may have told us what went wrong is likely to
+   be long gone and was overwritten by an irrelevant value.
+   close_tempfile() now resets errno to EIO to make errno at least
+   predictable.
+   (merge 7e8c9355b7 jk/tempfile-ferror-fclose-confusion later to maint).
+
+ * "git remote rm X", when a branch has remote X configured as the
+   value of its branch.*.remote, tried to remove branch.*.remote and
+   branch.*.merge and failed if either is unset.
+   (merge 20690b2139 rl/remote-allow-missing-branch-name-merge later to maint).
+
+ * A "gc.log" file left by a backgrounded "gc --auto" disables further
+   automatic gc; it has been taught to run at least once a day (by
+   default) by ignoring a stale "gc.log" file that is too old.
+   (merge a831c06a2b dt/gc-ignore-old-gc-logs later to maint).
+
+ * The code to parse "git -c VAR=VAL cmd" and set configuration
+   variable for the duration of cmd had two small bugs, which have
+   been fixed.
+   (merge 1274a155af jc/config-case-cmdline-take-2 later to maint).
+
+ * user.email that consists of only cruft chars should consistently
+   error out, but didn't.
+   (merge 94425552f3 jk/ident-empty later to maint).
+
+ * "git upload-pack", which is a counter-part of "git fetch", did not
+   report a request for a ref that was not advertised as invalid.
+   This is generally not a problem (because "git fetch" will stop
+   before making such a request), but is the right thing to do.
+   (merge bdb31eada7 jt/upload-pack-error-report later to maint).
+
+ * A leak in a codepath to read from a packed object in (rare) cases
+   has been plugged.
+   (merge 886ddf4777 rs/sha1-file-plug-fallback-base-leak later to maint).
+
+ * When a redirected http transport gets an error during the
+   redirected request, we ignored the error we got from the server,
+   and ended up giving a not-so-useful error message.
+   (merge 8e27391a5f jt/http-base-url-update-upon-redirect later to maint).
+
+ * The patch subcommand of "git add -i" was meant to have paths
+   selection prompt just like other subcommand, unlike "git add -p"
+   directly jumps to hunk selection.  Recently, this was broken and
+   "add -i" lost the paths selection dialog, but it now has been
+   fixed.
+   (merge c852bd54bd jk/add-i-patch-do-prompt later to maint).
+
+ * Git v2.12 was shipped with an embarrassing breakage where various
+   operations that verify paths given from the user stopped dying when
+   seeing an issue, and instead later triggering segfault.
+   (merge ce83eadd9a js/realpath-pathdup-fix later to maint).
+
+ * There is no need for Python only to give a few messages to the
+   standard error stream, but we somehow did.
+   (merge b8686c661d ss/remote-bzr-hg-placeholder-wo-python later to maint).
+
+ * The code to parse "git log -L..." command line was buggy when there
+   are many ranges specified with -L; overrun of the allocated buffer
+   has been fixed.
+   (merge aaae0bf787 ax/line-log-range-merge-fix later to maint).
+
+ * The command-line parsing of "git log -L" copied internal data
+   structures using incorrect size on ILP32 systems.
+   (merge 07f546cda5 vn/line-log-memcpy-size-fix later to maint).
+
+ * "git diff --quiet" relies on the size field in diff_filespec to be
+   correctly populated, but diff_populate_filespec() helper function
+   made an incorrect short-cut when asked only to populate the size
+   field for paths that need to go through convert_to_git() (e.g. CRLF
+   conversion).
+   (merge 12426e114b jc/diff-populate-filespec-size-only-fix later to maint).
+
+ * A few tests were run conditionally under (rare) conditions where
+   they cannot be run (like running cvs tests under 'root' account).
+   (merge c6507484a2 ab/cond-skip-tests later to maint).
+
+ * "git branch @" created refs/heads/@ as a branch, and in general the
+   code that handled @{-1} and @{upstream} was a bit too loose in
+   disambiguating.
+   (merge fd4692ff70 jk/interpret-branch-name later to maint).
+
+ * "git fetch" that requests a commit by object name, when the other
+   side does not allow such an request, failed without much
+   explanation.
+   (merge d56583ded6 mm/fetch-show-error-message-on-unadvertised-object later to maint).
+
+ * "git filter-branch --prune-empty" drops a single-parent commit that
+   becomes a no-op, but did not drop a root commit whose tree is empty.
+   (merge 32da7467eb dp/filter-branch-prune-empty later to maint).
+
+ * Recent versions of Git treats http alternates (used in dumb http
+   transport) just like HTTP redirects and requires the client to
+   enable following it, due to security concerns.  But we forgot to
+   give a warning when we decide not to honor the alternates.
+   (merge 5cae73d5d2 ew/http-alternates-as-redirects-warning later to maint).
+
+ * "git push" had a handful of codepaths that could lead to a deadlock
+   when unexpected error happened, which has been fixed.
+   (merge d1a13d3fcb jk/push-deadlock-regression-fix later to maint).
+
+ * Other minor doc, test and build updates and code cleanups.
+   (merge 2cfa83574c mm/two-more-xstrfmt later to maint).
+   (merge b803ae4427 ps/docs-diffcore later to maint).
+   (merge bcd886d897 ew/markdown-url-in-readme later to maint).
+   (merge b2d593a779 rj/remove-unused-mktemp later to maint).
+   (merge 3255e512a8 jk/ewah-use-right-type-in-sizeof later to maint).
index 015346c..eccc012 100644 (file)
@@ -334,6 +334,10 @@ core.trustctime::
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
 
+core.splitIndex::
+       If true, the split-index feature of the index will be used.
+       See linkgit:git-update-index[1]. False by default.
+
 core.untrackedCache::
        Determines what to do about the untracked cache feature of the
        index. It will be kept, if this variable is unset or set to
@@ -350,16 +354,19 @@ core.checkStat::
        all fields, including the sub-second part of mtime and ctime.
 
 core.quotePath::
-       The commands that output paths (e.g. 'ls-files',
-       'diff'), when not given the `-z` option, will quote
-       "unusual" characters in the pathname by enclosing the
-       pathname in a double-quote pair and with backslashes the
-       same way strings in C source code are quoted.  If this
-       variable is set to false, the bytes higher than 0x80 are
-       not quoted but output as verbatim.  Note that double
-       quote, backslash and control characters are always
-       quoted without `-z` regardless of the setting of this
-       variable.
+       Commands that output paths (e.g. 'ls-files', 'diff'), will
+       quote "unusual" characters in the pathname by enclosing the
+       pathname in double-quotes and escaping those characters with
+       backslashes in the same way C escapes control characters (e.g.
+       `\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with
+       values larger than 0x80 (e.g. octal `\302\265` for "micro" in
+       UTF-8).  If this variable is set to false, bytes higher than
+       0x80 are not considered "unusual" any more. Double-quotes,
+       backslash and control characters are always escaped regardless
+       of the setting of this variable.  A simple space character is
+       not considered "unusual".  Many commands can output pathnames
+       completely verbatim using the `-z` option. The default value
+       is true.
 
 core.eol::
        Sets the line ending type to use in the working directory for
@@ -1402,6 +1409,12 @@ gc.autoDetach::
        Make `git gc --auto` return immediately and run in background
        if the system supports it. Default is true.
 
+gc.logExpiry::
+       If the file gc.log exists, then `git gc --auto` won't run
+       unless that file is more than 'gc.logExpiry' old.  Default is
+       "1.day".  See `gc.pruneExpire` for more ways to specify its
+       value.
+
 gc.packRefs::
        Running `git pack-refs` in a repository renders it
        unclonable by Git versions prior to 1.5.1.2 over dumb
@@ -1919,7 +1932,10 @@ http.<url>.*::
   must match exactly between the config key and the URL.
 
 . Host/domain name (e.g., `example.com` in `https://example.com/`).
-  This field must match exactly between the config key and the URL.
+  This field must match between the config key and the URL. It is
+  possible to specify a `*` as part of the host name to match all subdomains
+  at this level. `https://*.example.com/` for example would match
+  `https://foo.example.com/`, but not `https://foo.bar.example.com/`.
 
 . Port number (e.g., `8080` in `http://example.com:8080/`).
   This field must match exactly between the config key and the URL.
@@ -1954,6 +1970,17 @@ Environment variable settings always override any matches.  The URLs that are
 matched against are those given directly to Git commands.  This means any URLs
 visited as a result of a redirection do not participate in matching.
 
+ssh.variant::
+       Depending on the value of the environment variables `GIT_SSH` or
+       `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git
+       auto-detects whether to adjust its command-line parameters for use
+       with plink or tortoiseplink, as opposed to the default (OpenSSH).
++
+The config variable `ssh.variant` can be set to override this auto-detection;
+valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value
+will be treated as normal ssh. This setting can be overridden via the
+environment variable `GIT_SSH_VARIANT`.
+
 i18n.commitEncoding::
        Character encoding the commit messages are stored in; Git itself
        does not care per se, but this information is necessary e.g. when
@@ -2827,6 +2854,31 @@ showbranch.default::
        The default set of branches for linkgit:git-show-branch[1].
        See linkgit:git-show-branch[1].
 
+splitIndex.maxPercentChange::
+       When the split index feature is used, this specifies the
+       percent of entries the split index can contain compared to the
+       total number of entries in both the split index and the shared
+       index before a new shared index is written.
+       The value should be between 0 and 100. If the value is 0 then
+       a new shared index is always written, if it is 100 a new
+       shared index is never written.
+       By default the value is 20, so a new shared index is written
+       if the number of entries in the split index would be greater
+       than 20 percent of the total number of entries.
+       See linkgit:git-update-index[1].
+
+splitIndex.sharedIndexExpire::
+       When the split index feature is used, shared index files that
+       were not modified since the time this variable specifies will
+       be removed when a new shared index file is created. The value
+       "now" expires all entries immediately, and "never" suppresses
+       expiration altogether.
+       The default value is "2.weeks.ago".
+       Note that a shared index file is considered modified (for the
+       purpose of expiration) each time a new split-index file is
+       either created based on it or read from it.
+       See linkgit:git-update-index[1].
+
 status.relativePaths::
        By default, linkgit:git-status[1] shows paths relative to the
        current directory. Setting this variable to `false` shows paths
index cf52626..706916c 100644 (file)
@@ -78,9 +78,10 @@ Example:
 :100644 100644 5be4a4...... 000000...... M file.c
 ------------------------------------------------
 
-When `-z` option is not used, TAB, LF, and backslash characters
-in pathnames are represented as `\t`, `\n`, and `\\`,
-respectively.
+Without the `-z` option, pathnames with "unusual" characters are
+quoted as explained for the configuration variable `core.quotePath`
+(see linkgit:git-config[1]).  Using `-z` the filename is output
+verbatim and the line is terminated by a NUL byte.
 
 diff format for merges
 ----------------------
index d2a7ff5..231105c 100644 (file)
@@ -53,10 +53,9 @@ The index line includes the SHA-1 checksum before and after the change.
 The <mode> is included if the file mode does not change; otherwise,
 separate lines indicate the old and the new mode.
 
-3.  TAB, LF, double quote and backslash characters in pathnames
-    are represented as `\t`, `\n`, `\"` and `\\`, respectively.
-    If there is need for such substitution then the whole
-    pathname is put in double quotes.
+3.  Pathnames with "unusual" characters are quoted as explained for
+    the configuration variable `core.quotePath` (see
+    linkgit:git-config[1]).
 
 4.  All the `file1` files in the output refer to files before the
     commit, and all the `file2` files refer to files after the commit.
index d91ddbd..89cc0f4 100644 (file)
@@ -192,10 +192,9 @@ ifndef::git-log[]
        given, do not munge pathnames and use NULs as output field terminators.
 endif::git-log[]
 +
-Without this option, each pathname output will have TAB, LF, double quotes,
-and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`,
-respectively, and the pathname will be enclosed in double quotes if
-any of those replacements occurred.
+Without this option, pathnames with "unusual" characters are quoted as
+explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).
 
 --name-only::
        Show only names of changed files.
index 8ddb207..631cbd8 100644 (file)
@@ -108,10 +108,9 @@ the information is read from the current index instead.
        When `--numstat` has been given, do not munge pathnames,
        but use a NUL-terminated machine-readable format.
 +
-Without this option, each pathname output will have TAB, LF, double quotes,
-and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`,
-respectively, and the pathname will be enclosed in double quotes if
-any of those replacements occurred.
+Without this option, pathnames with "unusual" characters are quoted as
+explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).
 
 -p<n>::
        Remove <n> leading slashes from traditional diff paths. The
index 28d46cc..092f1bc 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
        [--column[=<options>] | --no-column]
        [(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>]
-       [--points-at <object>] [<pattern>...]
+       [--points-at <object>] [--format=<format>] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
@@ -253,6 +253,11 @@ start-point is either a local or remote-tracking branch.
 --points-at <object>::
        Only list branches of the given object.
 
+--format <format>::
+       A string that interpolates `%(fieldname)` from the object
+       pointed at by a ref being shown.  The format is the same as
+       that of linkgit:git-for-each-ref[1].
+
 Examples
 --------
 
index 4f8f20a..25dcdcc 100644 (file)
@@ -117,9 +117,12 @@ OPTIONS
 
 -z::
 --null::
-       When showing `short` or `porcelain` status output, terminate
-       entries in the status output with NUL, instead of LF. If no
-       format is given, implies the `--porcelain` output format.
+       When showing `short` or `porcelain` status output, print the
+       filename verbatim and terminate the entries with NUL, instead of LF.
+       If no format is given, implies the `--porcelain` output format.
+       Without the `-z` option, filenames with "unusual" characters are
+       quoted as explained for the configuration variable `core.quotePath`
+       (see linkgit:git-config[1]).
 
 -F <file>::
 --file=<file>::
index e4ac448..8755f3a 100644 (file)
@@ -83,7 +83,20 @@ OPTIONS
 --match <pattern>::
        Only consider tags matching the given `glob(7)` pattern,
        excluding the "refs/tags/" prefix.  This can be used to avoid
-       leaking private tags from the repository.
+       leaking private tags from the repository. If given multiple times, a
+       list of patterns will be accumulated, and tags matching any of the
+       patterns will be considered. Use `--no-match` to clear and reset the
+       list of patterns.
+
+--exclude <pattern>::
+       Do not consider tags matching the given `glob(7)` pattern, excluding
+       the "refs/tags/" prefix. This can be used to narrow the tag space and
+       find only tags matching some meaningful criteria. If given multiple
+       times, a list of patterns will be accumulated and tags matching any
+       of the patterns will be excluded. When combined with --match a tag will
+       be considered when it matches at least one --match pattern and does not
+       match any of the --exclude patterns. Use `--no-exclude` to clear and
+       reset the list of patterns.
 
 --always::
        Show uniquely abbreviated commit object as fallback.
index 0a09698..6e4bb02 100644 (file)
@@ -167,14 +167,12 @@ to other tags will be rewritten to point to the underlying commit.
        project root. Implies <<Remap_to_ancestor>>.
 
 --prune-empty::
-       Some kind of filters will generate empty commits, that left the tree
-       untouched.  This switch allow git-filter-branch to ignore such
-       commits.  Though, this switch only applies for commits that have one
-       and only one parent, it will hence keep merges points. Also, this
-       option is not compatible with the use of `--commit-filter`. Though you
-       just need to use the function 'git_commit_non_empty_tree "$@"' instead
-       of the `git commit-tree "$@"` idiom in your commit filter to make that
-       happen.
+       Some filters will generate empty commits that leave the tree untouched.
+       This option instructs git-filter-branch to remove such commits if they
+       have exactly one or zero non-pruned parents; merge commits will
+       therefore remain intact.  This option cannot be used together with
+       `--commit-filter`, though the same effect can be achieved by using the
+       provided `git_commit_non_empty_tree` function in a commit filter.
 
 --original <namespace>::
        Use this option to set the namespace where the original commits
index abe13f3..111e1be 100644 (file)
@@ -95,11 +95,20 @@ refname::
        The name of the ref (the part after $GIT_DIR/).
        For a non-ambiguous short name of the ref append `:short`.
        The option core.warnAmbiguousRefs is used to select the strict
-       abbreviation mode. If `strip=<N>` is appended, strips `<N>`
-       slash-separated path components from the front of the refname
-       (e.g., `%(refname:strip=2)` turns `refs/tags/foo` into `foo`.
-       `<N>` must be a positive integer.  If a displayed ref has fewer
-       components than `<N>`, the command aborts with an error.
+       abbreviation mode. If `lstrip=<N>` (`rstrip=<N>`) is appended, strips `<N>`
+       slash-separated path components from the front (back) of the refname
+       (e.g. `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo` and
+       `%(refname:rstrip=2)` turns `refs/tags/foo` into `refs`).
+       If `<N>` is a negative number, strip as many path components as
+       necessary from the specified end to leave `-<N>` path components
+       (e.g. `%(refname:lstrip=-2)` turns
+       `refs/tags/foo` into `tags/foo` and `%(refname:rstrip=-1)`
+       turns `refs/tags/foo` into `refs`). When the ref does not have
+       enough components, the result becomes an empty string if
+       stripping with positive <N>, or it becomes the full refname if
+       stripping with negative <N>.  Neither is an error.
++
+`strip` can be used as a synomym to `lstrip`.
 
 objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
@@ -110,21 +119,31 @@ objectsize::
 objectname::
        The object name (aka SHA-1).
        For a non-ambiguous abbreviation of the object name append `:short`.
+       For an abbreviation of the object name with desired length append
+       `:short=<length>`, where the minimum length is MINIMUM_ABBREV. The
+       length may be exceeded to ensure unique object names.
 
 upstream::
        The name of a local ref which can be considered ``upstream''
-       from the displayed ref. Respects `:short` in the same way as
-       `refname` above.  Additionally respects `:track` to show
-       "[ahead N, behind M]" and `:trackshort` to show the terse
-       version: ">" (ahead), "<" (behind), "<>" (ahead and behind),
-       or "=" (in sync).  Has no effect if the ref does not have
-       tracking information associated with it.
+       from the displayed ref. Respects `:short`, `:lstrip` and
+       `:rstrip` in the same way as `refname` above.  Additionally
+       respects `:track` to show "[ahead N, behind M]" and
+       `:trackshort` to show the terse version: ">" (ahead), "<"
+       (behind), "<>" (ahead and behind), or "=" (in sync). `:track`
+       also prints "[gone]" whenever unknown upstream ref is
+       encountered. Append `:track,nobracket` to show tracking
+       information without brackets (i.e "ahead N, behind M").  Has
+       no effect if the ref does not have tracking information
+       associated with it.  All the options apart from `nobracket`
+       are mutually exclusive, but if used together the last option
+       is selected.
 
 push::
-       The name of a local ref which represents the `@{push}` location
-       for the displayed ref. Respects `:short`, `:track`, and
-       `:trackshort` options as `upstream` does. Produces an empty
-       string if no `@{push}` ref is configured.
+       The name of a local ref which represents the `@{push}`
+       location for the displayed ref. Respects `:short`, `:lstrip`,
+       `:rstrip`, `:track`, and `:trackshort` options as `upstream`
+       does. Produces an empty string if no `@{push}` ref is
+       configured.
 
 HEAD::
        '*' if HEAD matches current ref (the checked out branch), ' '
@@ -149,6 +168,25 @@ align::
        quoted, but if nested then only the topmost level performs
        quoting.
 
+if::
+       Used as %(if)...%(then)...%(end) or
+       %(if)...%(then)...%(else)...%(end).  If there is an atom with
+       value or string literal after the %(if) then everything after
+       the %(then) is printed, else if the %(else) atom is used, then
+       everything after %(else) is printed. We ignore space when
+       evaluating the string before %(then), this is useful when we
+       use the %(HEAD) atom which prints either "*" or " " and we
+       want to apply the 'if' condition only on the 'HEAD' ref.
+       Append ":equals=<string>" or ":notequals=<string>" to compare
+       the value between the %(if:...) and %(then) atoms with the
+       given string.
+
+symref::
+       The ref which the given symbolic ref refers to. If not a
+       symbolic ref, nothing is printed. Respects the `:short`,
+       `:lstrip` and `:rstrip` options in the same way as `refname`
+       above.
+
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
@@ -186,6 +224,14 @@ As a special case for the date-type fields, you may specify a format for
 the date by adding `:` followed by date format name (see the
 values the `--date` option to linkgit:git-rev-list[1] takes).
 
+Some atoms like %(align) and %(if) always require a matching %(end).
+We call them "opening atoms" and sometimes denote them as %($open).
+
+When a scripting language specific quoting is in effect, everything
+between a top-level opening atom and its matching %(end) is evaluated
+according to the semantics of the opening atom and only its result
+from the top-level is quoted.
+
 
 EXAMPLES
 --------
@@ -273,6 +319,22 @@ eval=`git for-each-ref --shell --format="$fmt" \
 eval "$eval"
 ------------
 
+
+An example to show the usage of %(if)...%(then)...%(else)...%(end).
+This prefixes the current branch with a star.
+
+------------
+git for-each-ref --format="%(if)%(HEAD)%(then)* %(else)  %(end)%(refname:short)" refs/heads/
+------------
+
+
+An example to show the usage of %(if)...%(then)...%(end).
+This prints the authorname, if present.
+
+------------
+git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)"
+------------
+
 SEE ALSO
 --------
 linkgit:git-show-ref[1]
index 446209e..1cab703 100644 (file)
@@ -77,7 +77,8 @@ OPTIONS
        succeed.
 
 -z::
-       \0 line termination on output.
+       \0 line termination on output and do not quote filenames.
+       See OUTPUT below for more information.
 
 -x <pattern>::
 --exclude=<pattern>::
@@ -196,9 +197,10 @@ the index records up to three such pairs; one from tree O in stage
 the user (or the porcelain) to see what should eventually be recorded at the
 path. (see linkgit:git-read-tree[1] for more information on state)
 
-When `-z` option is not used, TAB, LF, and backslash characters
-in pathnames are represented as `\t`, `\n`, and `\\`,
-respectively.
+Without the `-z` option, pathnames with "unusual" characters are
+quoted as explained for the configuration variable `core.quotePath`
+(see linkgit:git-config[1]).  Using `-z` the filename is output
+verbatim and the line is terminated by a NUL byte.
 
 
 Exclude Patterns
index dbc91f9..9dee7be 100644 (file)
@@ -53,7 +53,8 @@ OPTIONS
        Show object size of blob (file) entries.
 
 -z::
-       \0 line termination on output.
+       \0 line termination on output and do not quote filenames.
+       See OUTPUT FORMAT below for more information.
 
 --name-only::
 --name-status::
@@ -82,8 +83,6 @@ Output Format
 -------------
         <mode> SP <type> SP <object> TAB <file>
 
-Unless the `-z` option is used, TAB, LF, and backslash characters
-in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
 This output format is compatible with what `--index-info --stdin` of
 'git update-index' expects.
 
@@ -95,6 +94,11 @@ Object size identified by <object> is given in bytes, and right-justified
 with minimum width of 7 characters.  Object size is given only for blobs
 (file) entries; for other entries `-` character is used in place of size.
 
+Without the `-z` option, pathnames with "unusual" characters are
+quoted as explained for the configuration variable `core.quotePath`
+(see linkgit:git-config[1]).  Using `-z` the filename is output
+verbatim and the line is terminated by a NUL byte.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index ca28fb8..e8e68f5 100644 (file)
@@ -26,7 +26,18 @@ OPTIONS
 
 --refs=<pattern>::
        Only use refs whose names match a given shell pattern.  The pattern
-       can be one of branch name, tag name or fully qualified ref name.
+       can be one of branch name, tag name or fully qualified ref name. If
+       given multiple times, use refs whose names match any of the given shell
+       patterns. Use `--no-refs` to clear any previous ref patterns given.
+
+--exclude=<pattern>::
+       Do not use any ref whose name matches a given shell pattern. The
+       pattern can be one of branch name, tag name or fully qualified ref
+       name. If given multiple times, a ref will be excluded when it matches
+       any of the given patterns. When used together with --refs, a ref will
+       be used as a match only when it matches at least one --refs pattern and
+       does not match any --exclude patterns. Use `--no-exclude` to clear the
+       list of exclude patterns.
 
 --all::
        List all commits reachable from all refs
index 7241e96..c40c470 100644 (file)
@@ -217,6 +217,10 @@ If `$GIT_DIR` is not defined and the current directory
 is not detected to lie in a Git repository or work tree
 print a message to stderr and exit with nonzero status.
 
+--absolute-git-dir::
+       Like `--git-dir`, but its output is always the canonicalized
+       absolute path.
+
 --git-common-dir::
        Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
 
@@ -257,6 +261,12 @@ print a message to stderr and exit with nonzero status.
 --show-toplevel::
        Show the absolute path of the top-level directory.
 
+--show-superproject-working-tree
+       Show the absolute path of the root of the superproject's
+       working tree (if exists) that uses the current repository as
+       its submodule.  Outputs nothing if the current repository is
+       not used as a submodule by any project.
+
 --shared-index-path::
        Show the path to the shared index file in split index mode, or
        empty if not in split-index mode.
index 2e9e344..70191d0 100644 (file)
@@ -13,8 +13,11 @@ SYNOPSIS
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
-'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
-            [-u|--include-untracked] [-a|--all] [<message>]]
+'git stash' save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
+            [-u|--include-untracked] [-a|--all] [<message>]
+'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
+            [-u|--include-untracked] [-a|--all] [-m|--message <message>]]
+            [--] [<pathspec>...]]
 'git stash' clear
 'git stash' create [<message>]
 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
@@ -46,14 +49,24 @@ OPTIONS
 -------
 
 save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
+push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]::
 
        Save your local modifications to a new 'stash' and roll them
        back to HEAD (in the working tree and in the index).
        The <message> part is optional and gives
-       the description along with the stashed state.  For quickly making
-       a snapshot, you can omit _both_ "save" and <message>, but giving
-       only <message> does not trigger this action to prevent a misspelled
-       subcommand from making an unwanted stash.
+       the description along with the stashed state.
++
+For quickly making a snapshot, you can omit "push".  In this mode,
+non-option arguments are not allowed to prevent a misspelled
+subcommand from making an unwanted stash.  The two exceptions to this
+are `stash -p` which acts as alias for `stash push -p` and pathspecs,
+which are allowed after a double hyphen `--` for disambiguation.
++
+When pathspec is given to 'git stash push', the new stash records the
+modified states only for the files that match the pathspec.  The index
+entries and working tree files are then rolled back to the state in
+HEAD only for these files, too, leaving files that do not match the
+pathspec intact.
 +
 If the `--keep-index` option is used, all changes already added to the
 index are left intact.
index 725065e..ba87365 100644 (file)
@@ -322,10 +322,9 @@ When the `-z` option is given, pathnames are printed as is and
 without any quoting and lines are terminated with a NUL (ASCII 0x00)
 byte.
 
-Otherwise, all pathnames will be "C-quoted" if they contain any tab,
-linefeed, double quote, or backslash characters. In C-quoting, these
-characters will be replaced with the corresponding C-style escape
-sequences and the resulting pathname will be double quoted.
+Without the `-z` option, pathnames with "unusual" characters are
+quoted as explained for the configuration variable `core.quotePath`
+(see linkgit:git-config[1]).
 
 
 CONFIGURATION
index 8acc72e..e05d0cd 100644 (file)
@@ -73,13 +73,17 @@ configuration entries unless `--name` is used to specify a logical name.
 +
 <repository> is the URL of the new submodule's origin repository.
 This may be either an absolute URL, or (if it begins with ./
-or ../), the location relative to the superproject's origin
+or ../), the location relative to the superproject's default remote
 repository (Please note that to specify a repository 'foo.git'
 which is located right next to a superproject 'bar.git', you'll
 have to use '../foo.git' instead of './foo.git' - as one might expect
 when following the rules for relative URLs - because the evaluation
 of relative URLs in Git is identical to that of relative directories).
-If the superproject doesn't have an origin configured
++
+The default remote is the remote of the remote tracking branch
+of the current branch. If no such remote tracking branch exists or
+the HEAD is detached, "origin" is assumed to be the default remote.
+If the superproject doesn't have a default remote configured
 the superproject is its own authoritative upstream and the current
 working directory is used instead.
 +
@@ -118,18 +122,24 @@ too (and can also report changes to a submodule's work tree).
 
 init [--] [<path>...]::
        Initialize the submodules recorded in the index (which were
-       added and committed elsewhere) by copying submodule
-       names and urls from .gitmodules to .git/config.
-       Optional <path> arguments limit which submodules will be initialized.
-       It will also copy the value of `submodule.$name.update` into
-       .git/config.
-       The key used in .git/config is `submodule.$name.url`.
-       This command does not alter existing information in .git/config.
-       You can then customize the submodule clone URLs in .git/config
-       for your local setup and proceed to `git submodule update`;
-       you can also just use `git submodule update --init` without
-       the explicit 'init' step if you do not intend to customize
-       any submodule locations.
+       added and committed elsewhere) by setting `submodule.$name.url`
+       in .git/config. It uses the same setting from .gitmodules as
+       a template. If the URL is relative, it will be resolved using
+       the default remote. If there is no default remote, the current
+       repository will be assumed to be upstream.
++
+Optional <path> arguments limit which submodules will be initialized.
+If no path is specified, all submodules are initialized.
++
+When present, it will also copy the value of `submodule.$name.update`.
+This command does not alter existing information in .git/config.
+You can then customize the submodule clone URLs in .git/config
+for your local setup and proceed to `git submodule update`;
+you can also just use `git submodule update --init` without
+the explicit 'init' step if you do not intend to customize
+any submodule locations.
++
+See the add subcommand for the defintion of default remote.
 
 deinit [-f|--force] (--all|[--] <path>...)::
        Unregister the given submodules, i.e. remove the whole
index 7386c93..1579abf 100644 (file)
@@ -163,14 +163,16 @@ may not support it yet.
 
 --split-index::
 --no-split-index::
-       Enable or disable split index mode. If enabled, the index is
-       split into two files, $GIT_DIR/index and $GIT_DIR/sharedindex.<SHA-1>.
-       Changes are accumulated in $GIT_DIR/index while the shared
-       index file contains all index entries stays unchanged. If
-       split-index mode is already enabled and `--split-index` is
-       given again, all changes in $GIT_DIR/index are pushed back to
-       the shared index file. This mode is designed for very large
-       indexes that take a significant amount of time to read or write.
+       Enable or disable split index mode. If split-index mode is
+       already enabled and `--split-index` is given again, all
+       changes in $GIT_DIR/index are pushed back to the shared index
+       file.
++
+These options take effect whatever the value of the `core.splitIndex`
+configuration variable (see linkgit:git-config[1]). But a warning is
+emitted when the change goes against the configured value, as the
+configured value will take effect next time the index is read and this
+will remove the intended effect of the option.
 
 --untracked-cache::
 --no-untracked-cache::
@@ -388,6 +390,31 @@ Although this bit looks similar to assume-unchanged bit, its goal is
 different from assume-unchanged bit's. Skip-worktree also takes
 precedence over assume-unchanged bit when both are set.
 
+Split index
+-----------
+
+This mode is designed for repositories with very large indexes, and
+aims at reducing the time it takes to repeatedly write these indexes.
+
+In this mode, the index is split into two files, $GIT_DIR/index and
+$GIT_DIR/sharedindex.<SHA-1>. Changes are accumulated in
+$GIT_DIR/index, the split index, while the shared index file contains
+all index entries and stays unchanged.
+
+All changes in the split index are pushed back to the shared index
+file when the number of entries in the split index reaches a level
+specified by the splitIndex.maxPercentChange config variable (see
+linkgit:git-config[1]).
+
+Each time a new shared index file is created, the old shared index
+files are deleted if their modification time is older than what is
+specified by the splitIndex.sharedIndexExpire config variable (see
+linkgit:git-config[1]).
+
+To avoid deleting a shared index file that is still used, its
+modification time is updated to the current time everytime a new split
+index based on the shared index file is either created or read from.
+
 Untracked cache
 ---------------
 
index aa895da..df0941d 100644 (file)
@@ -1025,6 +1025,12 @@ Usually it is easier to configure any desired options through your
 personal `.ssh/config` file.  Please consult your ssh documentation
 for further details.
 
+`GIT_SSH_VARIANT`::
+       If this environment variable is set, it overrides Git's autodetection
+       whether `GIT_SSH`/`GIT_SSH_COMMAND`/`core.sshCommand` refer to OpenSSH,
+       plink or tortoiseplink. This variable overrides the config setting
+       `ssh.variant` that serves the same purpose.
+
 `GIT_ASKPASS`::
        If this environment variable is set, then Git commands which need to
        acquire passwords or passphrases (e.g. for HTTP or IMAP authentication)
index e0b66c1..a53d093 100644 (file)
@@ -21,9 +21,11 @@ Each line in `gitattributes` file is of form:
        pattern attr1 attr2 ...
 
 That is, a pattern followed by an attributes list,
-separated by whitespaces.  When the pattern matches the
-path in question, the attributes listed on the line are given to
-the path.
+separated by whitespaces. Leading and trailing whitespaces are
+ignored. Lines that begin with '#' are ignored. Patterns
+that begin with a double quote are quoted in C style.
+When the pattern matches the path in question, the attributes
+listed on the line are given to the path.
 
 Each attribute can be in one of these states for a given path:
 
@@ -86,7 +88,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead.
 Attributes for all users on a system should be placed in the
 `$(prefix)/etc/gitattributes` file.
 
-Sometimes you would need to override an setting of an attribute
+Sometimes you would need to override a setting of an attribute
 for a path to `Unspecified` state.  This can be done by listing
 the name of the attribute prefixed with an exclamation point `!`.
 
index 46bc6d0..c0a60f3 100644 (file)
@@ -84,8 +84,8 @@ format sections of the manual for 'git diff-{asterisk}' commands) or
 diff-patch format.
 
 
-diffcore-break: For Splitting Up "Complete Rewrites"
-----------------------------------------------------
+diffcore-break: For Splitting Up Complete Rewrites
+--------------------------------------------------
 
 The second transformation in the chain is diffcore-break, and is
 controlled by the -B option to the 'git diff-{asterisk}' commands.  This is
@@ -119,7 +119,7 @@ the original is used), and can be customized by giving a number
 after "-B" option (e.g. "-B75" to tell it to use 75%).
 
 
-diffcore-rename: For Detection Renames and Copies
+diffcore-rename: For Detecting Renames and Copies
 -------------------------------------------------
 
 This transformation is used to detect renames and copies, and is
@@ -177,8 +177,8 @@ the expense of making it slower.  Without `--find-copies-harder`,
 copied happened to have been modified in the same changeset.
 
 
-diffcore-merge-broken: For Putting "Complete Rewrites" Back Together
---------------------------------------------------------------------
+diffcore-merge-broken: For Putting Complete Rewrites Back Together
+------------------------------------------------------------------
 
 This transformation is used to merge filepairs broken by
 diffcore-break, and not transformed into rename/copy by
index 8ad29e6..6e991c2 100644 (file)
@@ -384,10 +384,33 @@ full pathname may have special meaning:
 +
 Glob magic is incompatible with literal magic.
 
+attr;;
+After `attr:` comes a space separated list of "attribute
+requirements", all of which must be met in order for the
+path to be considered a match; this is in addition to the
+usual non-magic pathspec pattern matching.
+See linkgit:gitattributes[5].
++
+Each of the attribute requirements for the path takes one of
+these forms:
+
+- "`ATTR`" requires that the attribute `ATTR` be set.
+
+- "`-ATTR`" requires that the attribute `ATTR` be unset.
+
+- "`ATTR=VALUE`" requires that the attribute `ATTR` be
+  set to the string `VALUE`.
+
+- "`!ATTR`" requires that the attribute `ATTR` be
+  unspecified.
++
+
 exclude;;
        After a path matches any non-exclude pathspec, it will be run
-       through all exclude pathspec (magic signature: `!`). If it
-       matches, the path is ignored.
+       through all exclude pathspec (magic signature: `!` or its
+       synonym `^`). If it matches, the path is ignored.  When there
+       is no non-exclude pathspec, the exclusion is applied to the
+       result set as if invoked without any pathspec.
 --
 
 [[def_parent]]parent::
index 2602668..e7cbb7c 100644 (file)
@@ -16,10 +16,15 @@ Data Structure
        of no interest to the calling programs.  The name of the
        attribute can be retrieved by calling `git_attr_name()`.
 
-`struct git_attr_check`::
+`struct attr_check_item`::
 
-       This structure represents a set of attributes to check in a call
-       to `git_check_attr()` function, and receives the results.
+       This structure represents one attribute and its value.
+
+`struct attr_check`::
+
+       This structure represents a collection of `attr_check_item`.
+       It is passed to `git_check_attr()` function, specifying the
+       attributes to check, and receives their values.
 
 
 Attribute Values
@@ -27,7 +32,7 @@ Attribute Values
 
 An attribute for a path can be in one of four states: Set, Unset,
 Unspecified or set to a string, and `.value` member of `struct
-git_attr_check` records it.  There are three macros to check these:
+attr_check_item` records it.  There are three macros to check these:
 
 `ATTR_TRUE()`::
 
@@ -48,49 +53,51 @@ value of the attribute for the path.
 Querying Specific Attributes
 ----------------------------
 
-* Prepare an array of `struct git_attr_check` to define the list of
-  attributes you would want to check.  To populate this array, you would
-  need to define necessary attributes by calling `git_attr()` function.
+* Prepare `struct attr_check` using attr_check_initl()
+  function, enumerating the names of attributes whose values you are
+  interested in, terminated with a NULL pointer.  Alternatively, an
+  empty `struct attr_check` can be prepared by calling
+  `attr_check_alloc()` function and then attributes you want to
+  ask about can be added to it with `attr_check_append()`
+  function.
 
 * Call `git_check_attr()` to check the attributes for the path.
 
-* Inspect `git_attr_check` structure to see how each of the attribute in
-  the array is defined for the path.
+* Inspect `attr_check` structure to see how each of the
+  attribute in the array is defined for the path.
 
 
 Example
 -------
 
-To see how attributes "crlf" and "indent" are set for different paths.
+To see how attributes "crlf" and "ident" are set for different paths.
 
-. Prepare an array of `struct git_attr_check` with two elements (because
-  we are checking two attributes).  Initialize their `attr` member with
-  pointers to `struct git_attr` obtained by calling `git_attr()`:
+. Prepare a `struct attr_check` with two elements (because
+  we are checking two attributes):
 
 ------------
-static struct git_attr_check check[2];
+static struct attr_check *check;
 static void setup_check(void)
 {
-       if (check[0].attr)
+       if (check)
                return; /* already done */
-       check[0].attr = git_attr("crlf");
-       check[1].attr = git_attr("ident");
+       check = attr_check_initl("crlf", "ident", NULL);
 }
 ------------
 
-. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:
+. Call `git_check_attr()` with the prepared `struct attr_check`:
 
 ------------
        const char *path;
 
        setup_check();
-       git_check_attr(path, ARRAY_SIZE(check), check);
+       git_check_attr(path, check);
 ------------
 
-. Act on `.value` member of the result, left in `check[]`:
+. Act on `.value` member of the result, left in `check->items[]`:
 
 ------------
-       const char *value = check[0].value;
+       const char *value = check->items[0].value;
 
        if (ATTR_TRUE(value)) {
                The attribute is Set, by listing only the name of the
@@ -109,20 +116,39 @@ static void setup_check(void)
        }
 ------------
 
+To see how attributes in argv[] are set for different paths, only
+the first step in the above would be different.
+
+------------
+static struct attr_check *check;
+static void setup_check(const char **argv)
+{
+       check = attr_check_alloc();
+       while (*argv) {
+               struct git_attr *attr = git_attr(*argv);
+               attr_check_append(check, attr);
+               argv++;
+       }
+}
+------------
+
 
 Querying All Attributes
 -----------------------
 
 To get the values of all attributes associated with a file:
 
-* Call `git_all_attrs()`, which returns an array of `git_attr_check`
-  structures.
+* Prepare an empty `attr_check` structure by calling
+  `attr_check_alloc()`.
+
+* Call `git_all_attrs()`, which populates the `attr_check`
+  with the attributes attached to the path.
 
-* Iterate over the `git_attr_check` array to examine the attribute
-  names and values.  The name of the attribute described by a
-  `git_attr_check` object can be retrieved via
-  `git_attr_name(check[i].attr)`.  (Please note that no items will be
-  returned for unset attributes, so `ATTR_UNSET()` will return false
-  for all returned `git_array_check` objects.)
+* Iterate over the `attr_check.items[]` array to examine
+  the attribute names and values.  The name of the attribute
+  described by a  `attr_check.items[]` object can be retrieved via
+  `git_attr_name(check->items[i].attr)`.  (Please note that no items
+  will be returned for unset attributes, so `ATTR_UNSET()` will return
+  false for all returned `attr_check.items[]` objects.)
 
-* Free the `git_array_check` array.
+* Free the `attr_check` struct by calling `attr_check_free()`.
index 27bd701..36768b4 100644 (file)
@@ -168,6 +168,11 @@ There are some macros to easily define options:
        Introduce an option with string argument.
        The string argument is put into `str_var`.
 
+`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`::
+       Introduce an option with string argument.
+       The string argument is stored as an element in `string_list`.
+       Use of `--no-option` will clear the list of preceding values.
+
 `OPT_INTEGER(short, long, &int_var, description)`::
        Introduce an option with integer argument.
        The integer is put into `int_var`.
index 6a208e9..817d1cf 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.12.0
+DEF_VER=v2.12.GIT
 
 LF='
 '
index 8e4081e..ba524f3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -102,8 +102,6 @@ all::
 #
 # Define MKDIR_WO_TRAILING_SLASH if your mkdir() can't deal with trailing slash.
 #
-# Define NO_MKSTEMPS if you don't have mkstemps in the C library.
-#
 # Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
 # in the C library.
 #
@@ -781,6 +779,7 @@ LIB_OBJS += notes-cache.o
 LIB_OBJS += notes-merge.o
 LIB_OBJS += notes-utils.o
 LIB_OBJS += object.o
+LIB_OBJS += oidset.o
 LIB_OBJS += pack-bitmap.o
 LIB_OBJS += pack-bitmap-write.o
 LIB_OBJS += pack-check.o
@@ -932,6 +931,7 @@ BUILTIN_OBJS += builtin/prune.o
 BUILTIN_OBJS += builtin/pull.o
 BUILTIN_OBJS += builtin/push.o
 BUILTIN_OBJS += builtin/read-tree.o
+BUILTIN_OBJS += builtin/rebase--helper.o
 BUILTIN_OBJS += builtin/receive-pack.o
 BUILTIN_OBJS += builtin/reflog.o
 BUILTIN_OBJS += builtin/remote.o
@@ -1280,9 +1280,6 @@ ifdef MKDIR_WO_TRAILING_SLASH
        COMPAT_CFLAGS += -DMKDIR_WO_TRAILING_SLASH
        COMPAT_OBJS += compat/mkdir.o
 endif
-ifdef NO_MKSTEMPS
-       COMPAT_CFLAGS += -DNO_MKSTEMPS
-endif
 ifdef NO_UNSETENV
        COMPAT_CFLAGS += -DNO_UNSETENV
        COMPAT_OBJS += compat/unsetenv.o
@@ -2254,6 +2251,9 @@ endif
 ifdef GIT_PERF_MAKE_OPTS
        @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@+
 endif
+ifdef GIT_INTEROP_MAKE_OPTS
+       @echo GIT_INTEROP_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_INTEROP_MAKE_OPTS)))'\' >>$@+
+endif
 ifdef TEST_GIT_INDEX_VERSION
        @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@+
 endif
index c0cd558..f17af66 100644 (file)
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Torvalds with help of a group of hackers around the net.
 
 Please read the file [INSTALL][] for installation instructions.
 
-Many Git online resources are accessible from http://git-scm.com/
+Many Git online resources are accessible from <https://git-scm.com/>
 including full documentation and Git related tools.
 
 See [Documentation/gittutorial.txt][] to get started, then see
@@ -33,8 +33,8 @@ requests, comments and patches to git@vger.kernel.org (read
 [Documentation/SubmittingPatches][] for instructions on patch submission).
 To subscribe to the list, send an email with just "subscribe git" in
 the body to majordomo@vger.kernel.org. The mailing list archives are
-available at https://public-inbox.org/git,
-http://marc.info/?l=git and other archival sites.
+available at <https://public-inbox.org/git/>,
+<http://marc.info/?l=git> and other archival sites.
 
 The maintainer frequently sends the "What's cooking" reports that
 list the current status of various development topics to the mailing
index d09c3d5..125bf78 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.12.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.13.0.txt
\ No newline at end of file
index 2f0c26e..b02e068 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -214,12 +214,12 @@ const char *real_path_if_valid(const char *path)
        return strbuf_realpath(&realpath, path, 0);
 }
 
-char *real_pathdup(const char *path)
+char *real_pathdup(const char *path, int die_on_error)
 {
        struct strbuf realpath = STRBUF_INIT;
        char *retval = NULL;
 
-       if (strbuf_realpath(&realpath, path, 0))
+       if (strbuf_realpath(&realpath, path, die_on_error))
                retval = strbuf_detach(&realpath, NULL);
 
        strbuf_release(&realpath);
index 01751e5..60b8891 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -87,19 +87,6 @@ void *sha1_file_to_archive(const struct archiver_args *args,
        return buffer;
 }
 
-static void setup_archive_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_export_ignore;
-       static struct git_attr *attr_export_subst;
-
-       if (!attr_export_ignore) {
-               attr_export_ignore = git_attr("export-ignore");
-               attr_export_subst = git_attr("export-subst");
-       }
-       check[0].attr = attr_export_ignore;
-       check[1].attr = attr_export_subst;
-}
-
 struct directory {
        struct directory *up;
        struct object_id oid;
@@ -120,10 +107,10 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
                void *context)
 {
        static struct strbuf path = STRBUF_INIT;
+       static struct attr_check *check;
        struct archiver_context *c = context;
        struct archiver_args *args = c->args;
        write_archive_entry_fn_t write_entry = c->write_entry;
-       struct git_attr_check check[2];
        const char *path_without_prefix;
        int err;
 
@@ -137,11 +124,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
                strbuf_addch(&path, '/');
        path_without_prefix = path.buf + args->baselen;
 
-       setup_archive_check(check);
-       if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
-               if (ATTR_TRUE(check[0].value))
+       if (!check)
+               check = attr_check_initl("export-ignore", "export-subst", NULL);
+       if (!git_check_attr(path_without_prefix, check)) {
+               if (ATTR_TRUE(check->items[0].value))
                        return 0;
-               args->convert = ATTR_TRUE(check[1].value);
+               args->convert = ATTR_TRUE(check->items[1].value);
        }
 
        if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
diff --git a/attr.c b/attr.c
index 1fcf042..7e21344 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -13,6 +13,8 @@
 #include "attr.h"
 #include "dir.h"
 #include "utf8.h"
+#include "quote.h"
+#include "thread-utils.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -22,99 +24,234 @@ static const char git_attr__unknown[] = "(builtin)unknown";
 #define ATTR__UNSET NULL
 #define ATTR__UNKNOWN git_attr__unknown
 
-/* This is a randomly chosen prime. */
-#define HASHSIZE 257
-
 #ifndef DEBUG_ATTR
 #define DEBUG_ATTR 0
 #endif
 
 struct git_attr {
-       struct git_attr *next;
-       unsigned h;
-       int attr_nr;
-       int maybe_macro;
-       int maybe_real;
-       char name[FLEX_ARRAY];
+       int attr_nr; /* unique attribute number */
+       char name[FLEX_ARRAY]; /* attribute name */
 };
-static int attr_nr;
-static int cannot_trust_maybe_real;
-
-static struct git_attr_check *check_all_attr;
-static struct git_attr *(git_attr_hash[HASHSIZE]);
 
-char *git_attr_name(struct git_attr *attr)
+const char *git_attr_name(const struct git_attr *attr)
 {
        return attr->name;
 }
 
-static unsigned hash_name(const char *name, int namelen)
+struct attr_hashmap {
+       struct hashmap map;
+#ifndef NO_PTHREADS
+       pthread_mutex_t mutex;
+#endif
+};
+
+static inline void hashmap_lock(struct attr_hashmap *map)
 {
-       unsigned val = 0, c;
+#ifndef NO_PTHREADS
+       pthread_mutex_lock(&map->mutex);
+#endif
+}
 
-       while (namelen--) {
-               c = *name++;
-               val = ((val << 7) | (val >> 22)) ^ c;
+static inline void hashmap_unlock(struct attr_hashmap *map)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_unlock(&map->mutex);
+#endif
+}
+
+/*
+ * The global dictionary of all interned attributes.  This
+ * is a singleton object which is shared between threads.
+ * Access to this dictionary must be surrounded with a mutex.
+ */
+static struct attr_hashmap g_attr_hashmap;
+
+/* The container for objects stored in "struct attr_hashmap" */
+struct attr_hash_entry {
+       struct hashmap_entry ent; /* must be the first member! */
+       const char *key; /* the key; memory should be owned by value */
+       size_t keylen; /* length of the key */
+       void *value; /* the stored value */
+};
+
+/* attr_hashmap comparison function */
+static int attr_hash_entry_cmp(const struct attr_hash_entry *a,
+                              const struct attr_hash_entry *b,
+                              void *unused)
+{
+       return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);
+}
+
+/* Initialize an 'attr_hashmap' object */
+static void attr_hashmap_init(struct attr_hashmap *map)
+{
+       hashmap_init(&map->map, (hashmap_cmp_fn) attr_hash_entry_cmp, 0);
+}
+
+/*
+ * Retrieve the 'value' stored in a hashmap given the provided 'key'.
+ * If there is no matching entry, return NULL.
+ */
+static void *attr_hashmap_get(struct attr_hashmap *map,
+                             const char *key, size_t keylen)
+{
+       struct attr_hash_entry k;
+       struct attr_hash_entry *e;
+
+       if (!map->map.tablesize)
+               attr_hashmap_init(map);
+
+       hashmap_entry_init(&k, memhash(key, keylen));
+       k.key = key;
+       k.keylen = keylen;
+       e = hashmap_get(&map->map, &k, NULL);
+
+       return e ? e->value : NULL;
+}
+
+/* Add 'value' to a hashmap based on the provided 'key'. */
+static void attr_hashmap_add(struct attr_hashmap *map,
+                            const char *key, size_t keylen,
+                            void *value)
+{
+       struct attr_hash_entry *e;
+
+       if (!map->map.tablesize)
+               attr_hashmap_init(map);
+
+       e = xmalloc(sizeof(struct attr_hash_entry));
+       hashmap_entry_init(e, memhash(key, keylen));
+       e->key = key;
+       e->keylen = keylen;
+       e->value = value;
+
+       hashmap_add(&map->map, e);
+}
+
+struct all_attrs_item {
+       const struct git_attr *attr;
+       const char *value;
+       /*
+        * If 'macro' is non-NULL, indicates that 'attr' is a macro based on
+        * the current attribute stack and contains a pointer to the match_attr
+        * definition of the macro
+        */
+       const struct match_attr *macro;
+};
+
+/*
+ * Reallocate and reinitialize the array of all attributes (which is used in
+ * the attribute collection process) in 'check' based on the global dictionary
+ * of attributes.
+ */
+static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
+{
+       int i;
+
+       hashmap_lock(map);
+
+       if (map->map.size < check->all_attrs_nr)
+               die("BUG: interned attributes shouldn't be deleted");
+
+       /*
+        * If the number of attributes in the global dictionary has increased
+        * (or this attr_check instance doesn't have an initialized all_attrs
+        * field), reallocate the provided attr_check instance's all_attrs
+        * field and fill each entry with its corresponding git_attr.
+        */
+       if (map->map.size != check->all_attrs_nr) {
+               struct attr_hash_entry *e;
+               struct hashmap_iter iter;
+               hashmap_iter_init(&map->map, &iter);
+
+               REALLOC_ARRAY(check->all_attrs, map->map.size);
+               check->all_attrs_nr = map->map.size;
+
+               while ((e = hashmap_iter_next(&iter))) {
+                       const struct git_attr *a = e->value;
+                       check->all_attrs[a->attr_nr].attr = a;
+               }
+       }
+
+       hashmap_unlock(map);
+
+       /*
+        * Re-initialize every entry in check->all_attrs.
+        * This re-initialization can live outside of the locked region since
+        * the attribute dictionary is no longer being accessed.
+        */
+       for (i = 0; i < check->all_attrs_nr; i++) {
+               check->all_attrs[i].value = ATTR__UNKNOWN;
+               check->all_attrs[i].macro = NULL;
        }
-       return val;
 }
 
-static int invalid_attr_name(const char *name, int namelen)
+static int attr_name_valid(const char *name, size_t namelen)
 {
        /*
         * Attribute name cannot begin with '-' and must consist of
         * characters from [-A-Za-z0-9_.].
         */
        if (namelen <= 0 || *name == '-')
-               return -1;
+               return 0;
        while (namelen--) {
                char ch = *name++;
                if (! (ch == '-' || ch == '.' || ch == '_' ||
                       ('0' <= ch && ch <= '9') ||
                       ('a' <= ch && ch <= 'z') ||
                       ('A' <= ch && ch <= 'Z')) )
-                       return -1;
+                       return 0;
        }
-       return 0;
+       return 1;
+}
+
+static void report_invalid_attr(const char *name, size_t len,
+                               const char *src, int lineno)
+{
+       struct strbuf err = STRBUF_INIT;
+       strbuf_addf(&err, _("%.*s is not a valid attribute name"),
+                   (int) len, name);
+       fprintf(stderr, "%s: %s:%d\n", err.buf, src, lineno);
+       strbuf_release(&err);
 }
 
-static struct git_attr *git_attr_internal(const char *name, int len)
+/*
+ * Given a 'name', lookup and return the corresponding attribute in the global
+ * dictionary.  If no entry is found, create a new attribute and store it in
+ * the dictionary.
+ */
+static const struct git_attr *git_attr_internal(const char *name, int namelen)
 {
-       unsigned hval = hash_name(name, len);
-       unsigned pos = hval % HASHSIZE;
        struct git_attr *a;
 
-       for (a = git_attr_hash[pos]; a; a = a->next) {
-               if (a->h == hval &&
-                   !memcmp(a->name, name, len) && !a->name[len])
-                       return a;
+       if (!attr_name_valid(name, namelen))
+               return NULL;
+
+       hashmap_lock(&g_attr_hashmap);
+
+       a = attr_hashmap_get(&g_attr_hashmap, name, namelen);
+
+       if (!a) {
+               FLEX_ALLOC_MEM(a, name, name, namelen);
+               a->attr_nr = g_attr_hashmap.map.size;
+
+               attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a);
+               assert(a->attr_nr == (g_attr_hashmap.map.size - 1));
        }
 
-       if (invalid_attr_name(name, len))
-               return NULL;
+       hashmap_unlock(&g_attr_hashmap);
 
-       FLEX_ALLOC_MEM(a, name, name, len);
-       a->h = hval;
-       a->next = git_attr_hash[pos];
-       a->attr_nr = attr_nr++;
-       a->maybe_macro = 0;
-       a->maybe_real = 0;
-       git_attr_hash[pos] = a;
-
-       REALLOC_ARRAY(check_all_attr, attr_nr);
-       check_all_attr[a->attr_nr].attr = a;
-       check_all_attr[a->attr_nr].value = ATTR__UNKNOWN;
        return a;
 }
 
-struct git_attr *git_attr(const char *name)
+const struct git_attr *git_attr(const char *name)
 {
        return git_attr_internal(name, strlen(name));
 }
 
 /* What does a matched pattern decide? */
 struct attr_state {
-       struct git_attr *attr;
+       const struct git_attr *attr;
        const char *setto;
 };
 
@@ -131,9 +268,8 @@ struct pattern {
  * If is_macro is true, then u.attr is a pointer to the git_attr being
  * defined.
  *
- * If is_macro is false, then u.pattern points at the filename pattern
- * to which the rule applies.  (The memory pointed to is part of the
- * memory block allocated for the match_attr instance.)
+ * If is_macro is false, then u.pat is the filename pattern to which the
+ * rule applies.
  *
  * In either case, num_attr is the number of attributes affected by
  * this rule, and state is an array listing them.  The attributes are
@@ -142,7 +278,7 @@ struct pattern {
 struct match_attr {
        union {
                struct pattern pat;
-               struct git_attr *attr;
+               const struct git_attr *attr;
        } u;
        char is_macro;
        unsigned num_attr;
@@ -177,13 +313,17 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                        cp++;
                        len--;
                }
-               if (invalid_attr_name(cp, len)) {
-                       fprintf(stderr,
-                               "%.*s is not a valid attribute name: %s:%d\n",
-                               len, cp, src, lineno);
+               if (!attr_name_valid(cp, len)) {
+                       report_invalid_attr(cp, len, src, lineno);
                        return NULL;
                }
        } else {
+               /*
+                * As this function is always called twice, once with
+                * e == NULL in the first pass and then e != NULL in
+                * the second pass, no need for attr_name_valid()
+                * check here.
+                */
                if (*cp == '-' || *cp == '!') {
                        e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
                        cp++;
@@ -207,41 +347,47 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
        const char *cp, *name, *states;
        struct match_attr *res = NULL;
        int is_macro;
+       struct strbuf pattern = STRBUF_INIT;
 
        cp = line + strspn(line, blank);
        if (!*cp || *cp == '#')
                return NULL;
        name = cp;
-       namelen = strcspn(name, blank);
+
+       if (*cp == '"' && !unquote_c_style(&pattern, name, &states)) {
+               name = pattern.buf;
+               namelen = pattern.len;
+       } else {
+               namelen = strcspn(name, blank);
+               states = name + namelen;
+       }
+
        if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&
            starts_with(name, ATTRIBUTE_MACRO_PREFIX)) {
                if (!macro_ok) {
                        fprintf(stderr, "%s not allowed: %s:%d\n",
                                name, src, lineno);
-                       return NULL;
+                       goto fail_return;
                }
                is_macro = 1;
                name += strlen(ATTRIBUTE_MACRO_PREFIX);
                name += strspn(name, blank);
                namelen = strcspn(name, blank);
-               if (invalid_attr_name(name, namelen)) {
-                       fprintf(stderr,
-                               "%.*s is not a valid attribute name: %s:%d\n",
-                               namelen, name, src, lineno);
-                       return NULL;
+               if (!attr_name_valid(name, namelen)) {
+                       report_invalid_attr(name, namelen, src, lineno);
+                       goto fail_return;
                }
        }
        else
                is_macro = 0;
 
-       states = name + namelen;
        states += strspn(states, blank);
 
        /* First pass to count the attr_states */
        for (cp = states, num_attr = 0; *cp; num_attr++) {
                cp = parse_attr(src, lineno, cp, NULL);
                if (!cp)
-                       return NULL;
+                       goto fail_return;
        }
 
        res = xcalloc(1,
@@ -250,7 +396,6 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                      (is_macro ? 0 : namelen + 1));
        if (is_macro) {
                res->u.attr = git_attr_internal(name, namelen);
-               res->u.attr->maybe_macro = 1;
        } else {
                char *p = (char *)&(res->state[num_attr]);
                memcpy(p, name, namelen);
@@ -262,7 +407,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                if (res->u.pat.flags & EXC_FLAG_NEGATIVE) {
                        warning(_("Negative patterns are ignored in git attributes\n"
                                  "Use '\\!' for literal leading exclamation."));
-                       return NULL;
+                       goto fail_return;
                }
        }
        res->is_macro = is_macro;
@@ -271,13 +416,15 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
        /* Second pass to fill the attr_states */
        for (cp = states, i = 0; *cp; i++) {
                cp = parse_attr(src, lineno, cp, &(res->state[i]));
-               if (!is_macro)
-                       res->state[i].attr->maybe_real = 1;
-               if (res->state[i].attr->maybe_macro)
-                       cannot_trust_maybe_real = 1;
        }
 
+       strbuf_release(&pattern);
        return res;
+
+fail_return:
+       strbuf_release(&pattern);
+       free(res);
+       return NULL;
 }
 
 /*
@@ -295,19 +442,19 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
  * directory (again, reading the file from top to bottom) down to the
  * current directory, and then scan the list backwards to find the first match.
  * This is exactly the same as what is_excluded() does in dir.c to deal with
- * .gitignore
+ * .gitignore file and info/excludes file as a fallback.
  */
 
-static struct attr_stack {
+struct attr_stack {
        struct attr_stack *prev;
        char *origin;
        size_t originlen;
        unsigned num_matches;
        unsigned alloc;
        struct match_attr **attrs;
-} *attr_stack;
+};
 
-static void free_attr_elem(struct attr_stack *e)
+static void attr_stack_free(struct attr_stack *e)
 {
        int i;
        free(e->origin);
@@ -330,6 +477,190 @@ static void free_attr_elem(struct attr_stack *e)
        free(e);
 }
 
+static void drop_attr_stack(struct attr_stack **stack)
+{
+       while (*stack) {
+               struct attr_stack *elem = *stack;
+               *stack = elem->prev;
+               attr_stack_free(elem);
+       }
+}
+
+/* List of all attr_check structs; access should be surrounded by mutex */
+static struct check_vector {
+       size_t nr;
+       size_t alloc;
+       struct attr_check **checks;
+#ifndef NO_PTHREADS
+       pthread_mutex_t mutex;
+#endif
+} check_vector;
+
+static inline void vector_lock(void)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_lock(&check_vector.mutex);
+#endif
+}
+
+static inline void vector_unlock(void)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_unlock(&check_vector.mutex);
+#endif
+}
+
+static void check_vector_add(struct attr_check *c)
+{
+       vector_lock();
+
+       ALLOC_GROW(check_vector.checks,
+                  check_vector.nr + 1,
+                  check_vector.alloc);
+       check_vector.checks[check_vector.nr++] = c;
+
+       vector_unlock();
+}
+
+static void check_vector_remove(struct attr_check *check)
+{
+       int i;
+
+       vector_lock();
+
+       /* Find entry */
+       for (i = 0; i < check_vector.nr; i++)
+               if (check_vector.checks[i] == check)
+                       break;
+
+       if (i >= check_vector.nr)
+               die("BUG: no entry found");
+
+       /* shift entries over */
+       for (; i < check_vector.nr - 1; i++)
+               check_vector.checks[i] = check_vector.checks[i + 1];
+
+       check_vector.nr--;
+
+       vector_unlock();
+}
+
+/* Iterate through all attr_check instances and drop their stacks */
+static void drop_all_attr_stacks(void)
+{
+       int i;
+
+       vector_lock();
+
+       for (i = 0; i < check_vector.nr; i++) {
+               drop_attr_stack(&check_vector.checks[i]->stack);
+       }
+
+       vector_unlock();
+}
+
+struct attr_check *attr_check_alloc(void)
+{
+       struct attr_check *c = xcalloc(1, sizeof(struct attr_check));
+
+       /* save pointer to the check struct */
+       check_vector_add(c);
+
+       return c;
+}
+
+struct attr_check *attr_check_initl(const char *one, ...)
+{
+       struct attr_check *check;
+       int cnt;
+       va_list params;
+       const char *param;
+
+       va_start(params, one);
+       for (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)
+               ;
+       va_end(params);
+
+       check = attr_check_alloc();
+       check->nr = cnt;
+       check->alloc = cnt;
+       check->items = xcalloc(cnt, sizeof(struct attr_check_item));
+
+       check->items[0].attr = git_attr(one);
+       va_start(params, one);
+       for (cnt = 1; cnt < check->nr; cnt++) {
+               const struct git_attr *attr;
+               param = va_arg(params, const char *);
+               if (!param)
+                       die("BUG: counted %d != ended at %d",
+                           check->nr, cnt);
+               attr = git_attr(param);
+               if (!attr)
+                       die("BUG: %s: not a valid attribute name", param);
+               check->items[cnt].attr = attr;
+       }
+       va_end(params);
+       return check;
+}
+
+struct attr_check *attr_check_dup(const struct attr_check *check)
+{
+       struct attr_check *ret;
+
+       if (!check)
+               return NULL;
+
+       ret = attr_check_alloc();
+
+       ret->nr = check->nr;
+       ret->alloc = check->alloc;
+       ALLOC_ARRAY(ret->items, ret->nr);
+       COPY_ARRAY(ret->items, check->items, ret->nr);
+
+       return ret;
+}
+
+struct attr_check_item *attr_check_append(struct attr_check *check,
+                                         const struct git_attr *attr)
+{
+       struct attr_check_item *item;
+
+       ALLOC_GROW(check->items, check->nr + 1, check->alloc);
+       item = &check->items[check->nr++];
+       item->attr = attr;
+       return item;
+}
+
+void attr_check_reset(struct attr_check *check)
+{
+       check->nr = 0;
+}
+
+void attr_check_clear(struct attr_check *check)
+{
+       free(check->items);
+       check->items = NULL;
+       check->alloc = 0;
+       check->nr = 0;
+
+       free(check->all_attrs);
+       check->all_attrs = NULL;
+       check->all_attrs_nr = 0;
+
+       drop_attr_stack(&check->stack);
+}
+
+void attr_check_free(struct attr_check *check)
+{
+       if (check) {
+               /* Remove check from the check vector */
+               check_vector_remove(check);
+
+               attr_check_clear(check);
+               free(check);
+       }
+}
+
 static const char *builtin_attr[] = {
        "[attr]binary -diff -merge -text",
        NULL,
@@ -362,9 +693,31 @@ static struct attr_stack *read_attr_from_array(const char **list)
        return res;
 }
 
+/*
+ * Callers into the attribute system assume there is a single, system-wide
+ * global state where attributes are read from and when the state is flipped by
+ * calling git_attr_set_direction(), the stack frames that have been
+ * constructed need to be discarded so so that subsequent calls into the
+ * attribute system will lazily read from the right place.  Since changing
+ * direction causes a global paradigm shift, it should not ever be called while
+ * another thread could potentially be calling into the attribute system.
+ */
 static enum git_attr_direction direction;
 static struct index_state *use_index;
 
+void git_attr_set_direction(enum git_attr_direction new_direction,
+                           struct index_state *istate)
+{
+       if (is_bare_repository() && new_direction != GIT_ATTR_INDEX)
+               die("BUG: non-INDEX attr direction in a bare repo");
+
+       if (new_direction != direction)
+               drop_all_attr_stacks();
+
+       direction = new_direction;
+       use_index = istate;
+}
+
 static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
 {
        FILE *fp = fopen(path, "r");
@@ -402,8 +755,8 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
        for (sp = buf; *sp; ) {
                char *ep;
                int more;
-               for (ep = sp; *ep && *ep != '\n'; ep++)
-                       ;
+
+               ep = strchrnul(sp, '\n');
                more = (*ep == '\n');
                *ep = '\0';
                handle_attr_line(res, sp, path, ++lineno, macro_ok);
@@ -415,25 +768,28 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
 
 static struct attr_stack *read_attr(const char *path, int macro_ok)
 {
-       struct attr_stack *res;
+       struct attr_stack *res = NULL;
 
-       if (direction == GIT_ATTR_CHECKOUT) {
+       if (direction == GIT_ATTR_INDEX) {
                res = read_attr_from_index(path, macro_ok);
-               if (!res)
-                       res = read_attr_from_file(path, macro_ok);
-       }
-       else if (direction == GIT_ATTR_CHECKIN) {
-               res = read_attr_from_file(path, macro_ok);
-               if (!res)
-                       /*
-                        * There is no checked out .gitattributes file there, but
-                        * we might have it in the index.  We allow operation in a
-                        * sparsely checked out work tree, so read from it.
-                        */
+       } else if (!is_bare_repository()) {
+               if (direction == GIT_ATTR_CHECKOUT) {
                        res = read_attr_from_index(path, macro_ok);
+                       if (!res)
+                               res = read_attr_from_file(path, macro_ok);
+               } else if (direction == GIT_ATTR_CHECKIN) {
+                       res = read_attr_from_file(path, macro_ok);
+                       if (!res)
+                               /*
+                                * There is no checked out .gitattributes file
+                                * there, but we might have it in the index.
+                                * We allow operation in a sparsely checked out
+                                * work tree, so read from it.
+                                */
+                               res = read_attr_from_index(path, macro_ok);
+               }
        }
-       else
-               res = read_attr_from_index(path, macro_ok);
+
        if (!res)
                res = xcalloc(1, sizeof(*res));
        return res;
@@ -464,16 +820,7 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr
 #define debug_push(a) do { ; } while (0)
 #define debug_pop(a) do { ; } while (0)
 #define debug_set(a,b,c,d) do { ; } while (0)
-#endif
-
-static void drop_attr_stack(void)
-{
-       while (attr_stack) {
-               struct attr_stack *elem = attr_stack;
-               attr_stack = elem->prev;
-               free_attr_elem(elem);
-       }
-}
+#endif /* DEBUG_ATTR */
 
 static const char *git_etc_gitattributes(void)
 {
@@ -483,6 +830,14 @@ static const char *git_etc_gitattributes(void)
        return system_wide;
 }
 
+static const char *get_home_gitattributes(void)
+{
+       if (!git_attributes_file)
+               git_attributes_file = xdg_config_home("attributes");
+
+       return git_attributes_file;
+}
+
 static int git_attr_system(void)
 {
        return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
@@ -490,64 +845,60 @@ static int git_attr_system(void)
 
 static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)
 
-static void bootstrap_attr_stack(void)
+static void push_stack(struct attr_stack **attr_stack_p,
+                      struct attr_stack *elem, char *origin, size_t originlen)
 {
-       struct attr_stack *elem;
+       if (elem) {
+               elem->origin = origin;
+               if (origin)
+                       elem->originlen = originlen;
+               elem->prev = *attr_stack_p;
+               *attr_stack_p = elem;
+       }
+}
 
-       if (attr_stack)
+static void bootstrap_attr_stack(struct attr_stack **stack)
+{
+       struct attr_stack *e;
+
+       if (*stack)
                return;
 
-       elem = read_attr_from_array(builtin_attr);
-       elem->origin = NULL;
-       elem->prev = attr_stack;
-       attr_stack = elem;
+       /* builtin frame */
+       e = read_attr_from_array(builtin_attr);
+       push_stack(stack, e, NULL, 0);
 
+       /* system-wide frame */
        if (git_attr_system()) {
-               elem = read_attr_from_file(git_etc_gitattributes(), 1);
-               if (elem) {
-                       elem->origin = NULL;
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-               }
+               e = read_attr_from_file(git_etc_gitattributes(), 1);
+               push_stack(stack, e, NULL, 0);
        }
 
-       if (!git_attributes_file)
-               git_attributes_file = xdg_config_home("attributes");
-       if (git_attributes_file) {
-               elem = read_attr_from_file(git_attributes_file, 1);
-               if (elem) {
-                       elem->origin = NULL;
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-               }
+       /* home directory */
+       if (get_home_gitattributes()) {
+               e = read_attr_from_file(get_home_gitattributes(), 1);
+               push_stack(stack, e, NULL, 0);
        }
 
-       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-               elem = read_attr(GITATTRIBUTES_FILE, 1);
-               elem->origin = xstrdup("");
-               elem->originlen = 0;
-               elem->prev = attr_stack;
-               attr_stack = elem;
-               debug_push(elem);
-       }
+       /* root directory */
+       e = read_attr(GITATTRIBUTES_FILE, 1);
+       push_stack(stack, e, xstrdup(""), 0);
 
+       /* info frame */
        if (startup_info->have_repository)
-               elem = read_attr_from_file(git_path_info_attributes(), 1);
+               e = read_attr_from_file(git_path_info_attributes(), 1);
        else
-               elem = NULL;
-
-       if (!elem)
-               elem = xcalloc(1, sizeof(*elem));
-       elem->origin = NULL;
-       elem->prev = attr_stack;
-       attr_stack = elem;
+               e = NULL;
+       if (!e)
+               e = xcalloc(1, sizeof(struct attr_stack));
+       push_stack(stack, e, NULL, 0);
 }
 
-static void prepare_attr_stack(const char *path, int dirlen)
+static void prepare_attr_stack(const char *path, int dirlen,
+                              struct attr_stack **stack)
 {
-       struct attr_stack *elem, *info;
-       int len;
-       const char *cp;
+       struct attr_stack *info;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        /*
         * At the bottom of the attribute stack is the built-in
@@ -564,13 +915,13 @@ static void prepare_attr_stack(const char *path, int dirlen)
         * .gitattributes in deeper directories to shallower ones,
         * and finally use the built-in set as the default.
         */
-       bootstrap_attr_stack();
+       bootstrap_attr_stack(stack);
 
        /*
         * Pop the "info" one that is always at the top of the stack.
         */
-       info = attr_stack;
-       attr_stack = info->prev;
+       info = *stack;
+       *stack = info->prev;
 
        /*
         * Pop the ones from directories that are not the prefix of
@@ -578,59 +929,63 @@ static void prepare_attr_stack(const char *path, int dirlen)
         * the root one (whose origin is an empty string "") or the builtin
         * one (whose origin is NULL) without popping it.
         */
-       while (attr_stack->origin) {
-               int namelen = strlen(attr_stack->origin);
+       while ((*stack)->origin) {
+               int namelen = (*stack)->originlen;
+               struct attr_stack *elem;
 
-               elem = attr_stack;
+               elem = *stack;
                if (namelen <= dirlen &&
                    !strncmp(elem->origin, path, namelen) &&
                    (!namelen || path[namelen] == '/'))
                        break;
 
                debug_pop(elem);
-               attr_stack = elem->prev;
-               free_attr_elem(elem);
+               *stack = elem->prev;
+               attr_stack_free(elem);
        }
 
        /*
-        * Read from parent directories and push them down
+        * bootstrap_attr_stack() should have added, and the
+        * above loop should have stopped before popping, the
+        * root element whose attr_stack->origin is set to an
+        * empty string.
         */
-       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-               /*
-                * bootstrap_attr_stack() should have added, and the
-                * above loop should have stopped before popping, the
-                * root element whose attr_stack->origin is set to an
-                * empty string.
-                */
-               struct strbuf pathbuf = STRBUF_INIT;
-
-               assert(attr_stack->origin);
-               while (1) {
-                       len = strlen(attr_stack->origin);
-                       if (dirlen <= len)
-                               break;
-                       cp = memchr(path + len + 1, '/', dirlen - len - 1);
-                       if (!cp)
-                               cp = path + dirlen;
-                       strbuf_add(&pathbuf, path, cp - path);
+       assert((*stack)->origin);
+
+       strbuf_addstr(&pathbuf, (*stack)->origin);
+       /* Build up to the directory 'path' is in */
+       while (pathbuf.len < dirlen) {
+               size_t len = pathbuf.len;
+               struct attr_stack *next;
+               char *origin;
+
+               /* Skip path-separator */
+               if (len < dirlen && is_dir_sep(path[len]))
+                       len++;
+               /* Find the end of the next component */
+               while (len < dirlen && !is_dir_sep(path[len]))
+                       len++;
+
+               if (pathbuf.len > 0)
                        strbuf_addch(&pathbuf, '/');
-                       strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
-                       elem = read_attr(pathbuf.buf, 0);
-                       strbuf_setlen(&pathbuf, cp - path);
-                       elem->origin = strbuf_detach(&pathbuf, &elem->originlen);
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-                       debug_push(elem);
-               }
+               strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));
+               strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE);
+
+               next = read_attr(pathbuf.buf, 0);
+
+               /* reset the pathbuf to not include "/.gitattributes" */
+               strbuf_setlen(&pathbuf, len);
 
-               strbuf_release(&pathbuf);
+               origin = xstrdup(pathbuf.buf);
+               push_stack(stack, next, origin, len);
        }
 
        /*
         * Finally push the "info" one at the top of the stack.
         */
-       info->prev = attr_stack;
-       attr_stack = info;
+       push_stack(stack, info, NULL, 0);
+
+       strbuf_release(&pathbuf);
 }
 
 static int path_matches(const char *pathname, int pathlen,
@@ -656,16 +1011,16 @@ static int path_matches(const char *pathname, int pathlen,
                              pattern, prefix, pat->patternlen, pat->flags);
 }
 
-static int macroexpand_one(int attr_nr, int rem);
+static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
 
-static int fill_one(const char *what, struct match_attr *a, int rem)
+static int fill_one(const char *what, struct all_attrs_item *all_attrs,
+                   const struct match_attr *a, int rem)
 {
-       struct git_attr_check *check = check_all_attr;
        int i;
 
-       for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) {
-               struct git_attr *attr = a->state[i].attr;
-               const char **n = &(check[attr->attr_nr].value);
+       for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) {
+               const struct git_attr *attr = a->state[i].attr;
+               const char **n = &(all_attrs[attr->attr_nr].value);
                const char *v = a->state[i].setto;
 
                if (*n == ATTR__UNKNOWN) {
@@ -674,64 +1029,72 @@ static int fill_one(const char *what, struct match_attr *a, int rem)
                                  attr, v);
                        *n = v;
                        rem--;
-                       rem = macroexpand_one(attr->attr_nr, rem);
+                       rem = macroexpand_one(all_attrs, attr->attr_nr, rem);
                }
        }
        return rem;
 }
 
 static int fill(const char *path, int pathlen, int basename_offset,
-               struct attr_stack *stk, int rem)
+               const struct attr_stack *stack,
+               struct all_attrs_item *all_attrs, int rem)
 {
-       int i;
-       const char *base = stk->origin ? stk->origin : "";
+       for (; rem > 0 && stack; stack = stack->prev) {
+               int i;
+               const char *base = stack->origin ? stack->origin : "";
 
-       for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
-               struct match_attr *a = stk->attrs[i];
-               if (a->is_macro)
-                       continue;
-               if (path_matches(path, pathlen, basename_offset,
-                                &a->u.pat, base, stk->originlen))
-                       rem = fill_one("fill", a, rem);
+               for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) {
+                       const struct match_attr *a = stack->attrs[i];
+                       if (a->is_macro)
+                               continue;
+                       if (path_matches(path, pathlen, basename_offset,
+                                        &a->u.pat, base, stack->originlen))
+                               rem = fill_one("fill", all_attrs, a, rem);
+               }
        }
+
        return rem;
 }
 
-static int macroexpand_one(int nr, int rem)
+static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem)
 {
-       struct attr_stack *stk;
-       struct match_attr *a = NULL;
-       int i;
+       const struct all_attrs_item *item = &all_attrs[nr];
 
-       if (check_all_attr[nr].value != ATTR__TRUE ||
-           !check_all_attr[nr].attr->maybe_macro)
+       if (item->macro && item->value == ATTR__TRUE)
+               return fill_one("expand", all_attrs, item->macro, rem);
+       else
                return rem;
+}
 
-       for (stk = attr_stack; !a && stk; stk = stk->prev)
-               for (i = stk->num_matches - 1; !a && 0 <= i; i--) {
-                       struct match_attr *ma = stk->attrs[i];
-                       if (!ma->is_macro)
-                               continue;
-                       if (ma->u.attr->attr_nr == nr)
-                               a = ma;
+/*
+ * Marks the attributes which are macros based on the attribute stack.
+ * This prevents having to search through the attribute stack each time
+ * a macro needs to be expanded during the fill stage.
+ */
+static void determine_macros(struct all_attrs_item *all_attrs,
+                            const struct attr_stack *stack)
+{
+       for (; stack; stack = stack->prev) {
+               int i;
+               for (i = stack->num_matches - 1; i >= 0; i--) {
+                       const struct match_attr *ma = stack->attrs[i];
+                       if (ma->is_macro) {
+                               int n = ma->u.attr->attr_nr;
+                               if (!all_attrs[n].macro) {
+                                       all_attrs[n].macro = ma;
+                               }
+                       }
                }
-
-       if (a)
-               rem = fill_one("expand", a, rem);
-
-       return rem;
+       }
 }
 
 /*
- * Collect attributes for path into the array pointed to by
- * check_all_attr. If num is non-zero, only attributes in check[] are
- * collected. Otherwise all attributes are collected.
+ * Collect attributes for path into the array pointed to by check->all_attrs.
+ * If check->check_nr is non-zero, only attributes in check[] are collected.
+ * Otherwise all attributes are collected.
  */
-static void collect_some_attrs(const char *path, int num,
-                              struct git_attr_check *check)
-
+static void collect_some_attrs(const char *path, struct attr_check *check)
 {
-       struct attr_stack *stk;
        int i, pathlen, rem, dirlen;
        const char *cp, *last_slash = NULL;
        int basename_offset;
@@ -749,81 +1112,67 @@ static void collect_some_attrs(const char *path, int num,
                dirlen = 0;
        }
 
-       prepare_attr_stack(path, dirlen);
-       for (i = 0; i < attr_nr; i++)
-               check_all_attr[i].value = ATTR__UNKNOWN;
-       if (num && !cannot_trust_maybe_real) {
+       prepare_attr_stack(path, dirlen, &check->stack);
+       all_attrs_init(&g_attr_hashmap, check);
+       determine_macros(check->all_attrs, check->stack);
+
+       if (check->nr) {
                rem = 0;
-               for (i = 0; i < num; i++) {
-                       if (!check[i].attr->maybe_real) {
-                               struct git_attr_check *c;
-                               c = check_all_attr + check[i].attr->attr_nr;
-                               c->value = ATTR__UNSET;
+               for (i = 0; i < check->nr; i++) {
+                       int n = check->items[i].attr->attr_nr;
+                       struct all_attrs_item *item = &check->all_attrs[n];
+                       if (item->macro) {
+                               item->value = ATTR__UNSET;
                                rem++;
                        }
                }
-               if (rem == num)
+               if (rem == check->nr)
                        return;
        }
 
-       rem = attr_nr;
-       for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
-               rem = fill(path, pathlen, basename_offset, stk, rem);
+       rem = check->all_attrs_nr;
+       fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
 }
 
-int git_check_attr(const char *path, int num, struct git_attr_check *check)
+int git_check_attr(const char *path, struct attr_check *check)
 {
        int i;
 
-       collect_some_attrs(path, num, check);
+       collect_some_attrs(path, check);
 
-       for (i = 0; i < num; i++) {
-               const char *value = check_all_attr[check[i].attr->attr_nr].value;
+       for (i = 0; i < check->nr; i++) {
+               size_t n = check->items[i].attr->attr_nr;
+               const char *value = check->all_attrs[n].value;
                if (value == ATTR__UNKNOWN)
                        value = ATTR__UNSET;
-               check[i].value = value;
+               check->items[i].value = value;
        }
 
        return 0;
 }
 
-int git_all_attrs(const char *path, int *num, struct git_attr_check **check)
+void git_all_attrs(const char *path, struct attr_check *check)
 {
-       int i, count, j;
+       int i;
 
-       collect_some_attrs(path, 0, NULL);
+       attr_check_reset(check);
+       collect_some_attrs(path, check);
 
-       /* Count the number of attributes that are set. */
-       count = 0;
-       for (i = 0; i < attr_nr; i++) {
-               const char *value = check_all_attr[i].value;
-               if (value != ATTR__UNSET && value != ATTR__UNKNOWN)
-                       ++count;
-       }
-       *num = count;
-       ALLOC_ARRAY(*check, count);
-       j = 0;
-       for (i = 0; i < attr_nr; i++) {
-               const char *value = check_all_attr[i].value;
-               if (value != ATTR__UNSET && value != ATTR__UNKNOWN) {
-                       (*check)[j].attr = check_all_attr[i].attr;
-                       (*check)[j].value = value;
-                       ++j;
-               }
+       for (i = 0; i < check->all_attrs_nr; i++) {
+               const char *name = check->all_attrs[i].attr->name;
+               const char *value = check->all_attrs[i].value;
+               struct attr_check_item *item;
+               if (value == ATTR__UNSET || value == ATTR__UNKNOWN)
+                       continue;
+               item = attr_check_append(check, git_attr(name));
+               item->value = value;
        }
-
-       return 0;
 }
 
-void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
+void attr_start(void)
 {
-       enum git_attr_direction old = direction;
-
-       if (is_bare_repository() && new != GIT_ATTR_INDEX)
-               die("BUG: non-INDEX attr direction in a bare repo");
-
-       direction = new;
-       if (new != old)
-               drop_attr_stack();
-       use_index = istate;
+#ifndef NO_PTHREADS
+       pthread_mutex_init(&g_attr_hashmap.mutex, NULL);
+       pthread_mutex_init(&check_vector.mutex, NULL);
+#endif
 }
diff --git a/attr.h b/attr.h
index 8b08d33..442d464 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -4,11 +4,15 @@
 /* An attribute is a pointer to this opaque structure */
 struct git_attr;
 
+/* opaque structures used internally for attribute collection */
+struct all_attrs_item;
+struct attr_stack;
+
 /*
  * Given a string, return the gitattribute object that
  * corresponds to it.
  */
-struct git_attr *git_attr(const char *);
+const struct git_attr *git_attr(const char *);
 
 /* Internal use */
 extern const char git_attr__true[];
@@ -20,38 +24,58 @@ extern const char git_attr__false[];
 #define ATTR_UNSET(v) ((v) == NULL)
 
 /*
- * Send one or more git_attr_check to git_check_attr(), and
+ * Send one or more git_attr_check to git_check_attrs(), and
  * each 'value' member tells what its value is.
  * Unset one is returned as NULL.
  */
-struct git_attr_check {
-       struct git_attr *attr;
+struct attr_check_item {
+       const struct git_attr *attr;
        const char *value;
 };
 
+struct attr_check {
+       int nr;
+       int alloc;
+       struct attr_check_item *items;
+       int all_attrs_nr;
+       struct all_attrs_item *all_attrs;
+       struct attr_stack *stack;
+};
+
+extern struct attr_check *attr_check_alloc(void);
+extern struct attr_check *attr_check_initl(const char *, ...);
+extern struct attr_check *attr_check_dup(const struct attr_check *check);
+
+extern struct attr_check_item *attr_check_append(struct attr_check *check,
+                                                const struct git_attr *attr);
+
+extern void attr_check_reset(struct attr_check *check);
+extern void attr_check_clear(struct attr_check *check);
+extern void attr_check_free(struct attr_check *check);
+
 /*
  * Return the name of the attribute represented by the argument.  The
  * return value is a pointer to a null-delimited string that is part
  * of the internal data structure; it should not be modified or freed.
  */
-char *git_attr_name(struct git_attr *);
+extern const char *git_attr_name(const struct git_attr *);
 
-int git_check_attr(const char *path, int, struct git_attr_check *);
+extern int git_check_attr(const char *path, struct attr_check *check);
 
 /*
- * Retrieve all attributes that apply to the specified path.  *num
- * will be set to the number of attributes on the path; **check will
- * be set to point at a newly-allocated array of git_attr_check
- * objects describing the attributes and their values.  *check must be
- * free()ed by the caller.
+ * Retrieve all attributes that apply to the specified path.
+ * check holds the attributes and their values.
  */
-int git_all_attrs(const char *path, int *num, struct git_attr_check **check);
+extern void git_all_attrs(const char *path, struct attr_check *check);
 
 enum git_attr_direction {
        GIT_ATTR_CHECKIN,
        GIT_ATTR_CHECKOUT,
        GIT_ATTR_INDEX
 };
-void git_attr_set_direction(enum git_attr_direction, struct index_state *);
+void git_attr_set_direction(enum git_attr_direction new_direction,
+                           struct index_state *istate);
+
+extern void attr_start(void);
 
 #endif /* ATTR_H */
index 8e63c40..30808ca 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -940,7 +940,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
        struct commit_list *tried;
        int reaches = 0, all = 0, nr, steps;
        const unsigned char *bisect_rev;
-       char steps_msg[32];
+       char *steps_msg;
 
        read_bisect_terms(&term_bad, &term_good);
        if (read_bisect_refs())
@@ -990,14 +990,15 @@ int bisect_next_all(const char *prefix, int no_checkout)
 
        nr = all - reaches - 1;
        steps = estimate_bisect_steps(all);
-       xsnprintf(steps_msg, sizeof(steps_msg),
-                 Q_("(roughly %d step)", "(roughly %d steps)", steps),
-                 steps);
+
+       steps_msg = xstrfmt(Q_("(roughly %d step)", "(roughly %d steps)",
+                 steps), steps);
        /* TRANSLATORS: the last %s will be replaced with
           "(roughly %d steps)" translation */
        printf(Q_("Bisecting: %d revision left to test after this %s\n",
                  "Bisecting: %d revisions left to test after this %s\n",
                  nr), nr, steps_msg);
+       free(steps_msg);
 
        return bisect_checkout(bisect_rev, no_checkout);
 }
index b955d4f..5c12036 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -345,7 +345,8 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)
            branch, wt->path);
 }
 
-int replace_each_worktree_head_symref(const char *oldref, const char *newref)
+int replace_each_worktree_head_symref(const char *oldref, const char *newref,
+                                     const char *logmsg)
 {
        int ret = 0;
        struct worktree **worktrees = get_worktrees(0);
@@ -358,7 +359,7 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)
                        continue;
 
                if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),
-                                            newref)) {
+                                            newref, logmsg)) {
                        ret = -1;
                        error(_("HEAD of working tree %s is not updated"),
                              worktrees[i]->path);
index 3103eb9..b077885 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -71,6 +71,7 @@ extern void die_if_checked_out(const char *branch, int ignore_current_worktree);
  * This will be used when renaming a branch. Returns 0 if successful, non-zero
  * otherwise.
  */
-extern int replace_each_worktree_head_symref(const char *oldref, const char *newref);
+extern int replace_each_worktree_head_symref(const char *oldref, const char *newref,
+                                            const char *logmsg);
 
 #endif
index 67f8051..9e4a898 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -103,6 +103,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_pull(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_remote(int argc, const char **argv, const char *prefix);
index 31fb605..f7a7a97 100644 (file)
@@ -1049,7 +1049,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
        } else {
                write_state_text(state, "abort-safety", "");
                if (!state->rebasing)
-                       delete_ref("ORIG_HEAD", NULL, 0);
+                       delete_ref(NULL, "ORIG_HEAD", NULL, 0);
        }
 
        /*
@@ -2172,7 +2172,7 @@ static void am_abort(struct am_state *state)
                                has_curr_head ? &curr_head : NULL, 0,
                                UPDATE_REFS_DIE_ON_ERR);
        else if (curr_branch)
-               delete_ref(curr_branch, NULL, REF_NODEREF);
+               delete_ref(NULL, curr_branch, NULL, REF_NODEREF);
 
        free(curr_branch);
        am_destroy(state);
index cffc626..f7aa95f 100644 (file)
@@ -658,8 +658,11 @@ static struct origin *find_rename(struct scoreboard *sb,
 /*
  * Append a new blame entry to a given output queue.
  */
-static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e)
+static void add_blame_entry(struct blame_entry ***queue,
+                           const struct blame_entry *src)
 {
+       struct blame_entry *e = xmalloc(sizeof(*e));
+       memcpy(e, src, sizeof(*e));
        origin_incref(e->suspect);
 
        e->next = **queue;
@@ -760,21 +763,15 @@ static void split_blame(struct blame_entry ***blamed,
                        struct blame_entry *split,
                        struct blame_entry *e)
 {
-       struct blame_entry *new_entry;
-
        if (split[0].suspect && split[2].suspect) {
                /* The first part (reuse storage for the existing entry e) */
                dup_entry(unblamed, e, &split[0]);
 
                /* The last part -- me */
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
-               add_blame_entry(unblamed, new_entry);
+               add_blame_entry(unblamed, &split[2]);
 
                /* ... and the middle part -- parent */
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
-               add_blame_entry(blamed, new_entry);
+               add_blame_entry(blamed, &split[1]);
        }
        else if (!split[0].suspect && !split[2].suspect)
                /*
@@ -785,18 +782,12 @@ static void split_blame(struct blame_entry ***blamed,
        else if (split[0].suspect) {
                /* me and then parent */
                dup_entry(unblamed, e, &split[0]);
-
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
-               add_blame_entry(blamed, new_entry);
+               add_blame_entry(blamed, &split[1]);
        }
        else {
                /* parent and then me */
                dup_entry(blamed, e, &split[1]);
-
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
-               add_blame_entry(unblamed, new_entry);
+               add_blame_entry(unblamed, &split[2]);
        }
 }
 
index 9d30f55..52688f2 100644 (file)
@@ -28,20 +28,21 @@ static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
        N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
        N_("git branch [<options>] [-r | -a] [--points-at]"),
+       N_("git branch [<options>] [-r | -a] [--format]"),
        NULL
 };
 
 static const char *head;
-static unsigned char head_sha1[20];
+static struct object_id head_oid;
 
 static int branch_use_color = -1;
 static char branch_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
-       GIT_COLOR_NORMAL,       /* PLAIN */
-       GIT_COLOR_RED,          /* REMOTE */
-       GIT_COLOR_NORMAL,       /* LOCAL */
-       GIT_COLOR_GREEN,        /* CURRENT */
-       GIT_COLOR_BLUE,         /* UPSTREAM */
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_RED,          /* REMOTE */
+       GIT_COLOR_NORMAL,       /* LOCAL */
+       GIT_COLOR_GREEN,        /* CURRENT */
+       GIT_COLOR_BLUE,         /* UPSTREAM */
 };
 enum color_branch {
        BRANCH_COLOR_RESET = 0,
@@ -117,13 +118,13 @@ static int branch_merged(int kind, const char *name,
        if (kind == FILTER_REFS_BRANCHES) {
                struct branch *branch = branch_get(name);
                const char *upstream = branch_get_upstream(branch, NULL);
-               unsigned char sha1[20];
+               struct object_id oid;
 
                if (upstream &&
                    (reference_name = reference_name_to_free =
                     resolve_refdup(upstream, RESOLVE_REF_READING,
-                                   sha1, NULL)) != NULL)
-                       reference_rev = lookup_commit_reference(sha1);
+                                   oid.hash, NULL)) != NULL)
+                       reference_rev = lookup_commit_reference(oid.hash);
        }
        if (!reference_rev)
                reference_rev = head_rev;
@@ -153,10 +154,10 @@ static int branch_merged(int kind, const char *name,
 }
 
 static int check_branch_commit(const char *branchname, const char *refname,
-                              const unsigned char *sha1, struct commit *head_rev,
+                              const struct object_id *oid, struct commit *head_rev,
                               int kinds, int force)
 {
-       struct commit *rev = lookup_commit_reference(sha1);
+       struct commit *rev = lookup_commit_reference(oid->hash);
        if (!rev) {
                error(_("Couldn't look up commit object for '%s'"), refname);
                return -1;
@@ -183,31 +184,34 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                           int quiet)
 {
        struct commit *head_rev = NULL;
-       unsigned char sha1[20];
+       struct object_id oid;
        char *name = NULL;
        const char *fmt;
        int i;
        int ret = 0;
        int remote_branch = 0;
        struct strbuf bname = STRBUF_INIT;
+       unsigned allowed_interpret;
 
        switch (kinds) {
        case FILTER_REFS_REMOTES:
                fmt = "refs/remotes/%s";
                /* For subsequent UI messages */
                remote_branch = 1;
+               allowed_interpret = INTERPRET_BRANCH_REMOTE;
 
                force = 1;
                break;
        case FILTER_REFS_BRANCHES:
                fmt = "refs/heads/%s";
+               allowed_interpret = INTERPRET_BRANCH_LOCAL;
                break;
        default:
                die(_("cannot use -a with -d"));
        }
 
        if (!force) {
-               head_rev = lookup_commit_reference(head_sha1);
+               head_rev = lookup_commit_reference(head_oid.hash);
                if (!head_rev)
                        die(_("Couldn't look up commit object for HEAD"));
        }
@@ -215,7 +219,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                char *target = NULL;
                int flags = 0;
 
-               strbuf_branchname(&bname, argv[i]);
+               strbuf_branchname(&bname, argv[i], allowed_interpret);
                free(name);
                name = mkpathdup(fmt, bname.buf);
 
@@ -235,7 +239,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                                        RESOLVE_REF_READING
                                        | RESOLVE_REF_NO_RECURSE
                                        | RESOLVE_REF_ALLOW_BAD_NAME,
-                                       sha1, &flags);
+                                       oid.hash, &flags);
                if (!target) {
                        error(remote_branch
                              ? _("remote-tracking branch '%s' not found.")
@@ -245,13 +249,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                }
 
                if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
-                   check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
+                   check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
                                        force)) {
                        ret = 1;
                        goto next;
                }
 
-               if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
+               if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash,
                               REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
@@ -267,7 +271,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                               bname.buf,
                               (flags & REF_ISBROKEN) ? "broken"
                               : (flags & REF_ISSYMREF) ? target
-                              : find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                              : find_unique_abbrev(oid.hash, DEFAULT_ABBREV));
                }
                delete_branch_config(bname.buf);
 
@@ -280,221 +284,109 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        return(ret);
 }
 
-static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
-               int show_upstream_ref)
+static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
 {
-       int ours, theirs;
-       char *ref = NULL;
-       struct branch *branch = branch_get(branch_name);
-       const char *upstream;
-       struct strbuf fancy = STRBUF_INIT;
-       int upstream_is_gone = 0;
-       int added_decoration = 1;
-
-       if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
-               if (!upstream)
-                       return;
-               upstream_is_gone = 1;
-       }
-
-       if (show_upstream_ref) {
-               ref = shorten_unambiguous_ref(upstream, 0);
-               if (want_color(branch_use_color))
-                       strbuf_addf(&fancy, "%s%s%s",
-                                       branch_get_color(BRANCH_COLOR_UPSTREAM),
-                                       ref, branch_get_color(BRANCH_COLOR_RESET));
-               else
-                       strbuf_addstr(&fancy, ref);
-       }
+       int i, max = 0;
+       for (i = 0; i < refs->nr; i++) {
+               struct ref_array_item *it = refs->items[i];
+               const char *desc = it->refname;
+               int w;
 
-       if (upstream_is_gone) {
-               if (show_upstream_ref)
-                       strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
-               else
-                       added_decoration = 0;
-       } else if (!ours && !theirs) {
-               if (show_upstream_ref)
-                       strbuf_addf(stat, _("[%s]"), fancy.buf);
-               else
-                       added_decoration = 0;
-       } else if (!ours) {
-               if (show_upstream_ref)
-                       strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
-               else
-                       strbuf_addf(stat, _("[behind %d]"), theirs);
+               skip_prefix(it->refname, "refs/heads/", &desc);
+               skip_prefix(it->refname, "refs/remotes/", &desc);
+               if (it->kind == FILTER_REFS_DETACHED_HEAD) {
+                       char *head_desc = get_head_description();
+                       w = utf8_strwidth(head_desc);
+                       free(head_desc);
+               } else
+                       w = utf8_strwidth(desc);
 
-       } else if (!theirs) {
-               if (show_upstream_ref)
-                       strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
-               else
-                       strbuf_addf(stat, _("[ahead %d]"), ours);
-       } else {
-               if (show_upstream_ref)
-                       strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
-                                   fancy.buf, ours, theirs);
-               else
-                       strbuf_addf(stat, _("[ahead %d, behind %d]"),
-                                   ours, theirs);
+               if (it->kind == FILTER_REFS_REMOTES)
+                       w += remote_bonus;
+               if (w > max)
+                       max = w;
        }
-       strbuf_release(&fancy);
-       if (added_decoration)
-               strbuf_addch(stat, ' ');
-       free(ref);
+       return max;
 }
 
-static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
-                            struct ref_filter *filter, const char *refname)
+static const char *quote_literal_for_format(const char *s)
 {
-       struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
-       const char *sub = _(" **** invalid ref ****");
-       struct commit *commit = item->commit;
+       static struct strbuf buf = STRBUF_INIT;
 
-       if (!parse_commit(commit)) {
-               pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
-               sub = subject.buf;
+       strbuf_reset(&buf);
+       while (*s) {
+               const char *ep = strchrnul(s, '%');
+               if (s < ep)
+                       strbuf_add(&buf, s, ep - s);
+               if (*ep == '%') {
+                       strbuf_addstr(&buf, "%%");
+                       s = ep + 1;
+               } else {
+                       s = ep;
+               }
        }
-
-       if (item->kind == FILTER_REFS_BRANCHES)
-               fill_tracking_info(&stat, refname, filter->verbose > 1);
-
-       strbuf_addf(out, " %s %s%s",
-               find_unique_abbrev(item->commit->object.oid.hash, filter->abbrev),
-               stat.buf, sub);
-       strbuf_release(&stat);
-       strbuf_release(&subject);
+       return buf.buf;
 }
 
-static char *get_head_description(void)
+static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix)
 {
-       struct strbuf desc = STRBUF_INIT;
-       struct wt_status_state state;
-       memset(&state, 0, sizeof(state));
-       wt_status_get_state(&state, 1);
-       if (state.rebase_in_progress ||
-           state.rebase_interactive_in_progress)
-               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
-                           state.branch);
-       else if (state.bisect_in_progress)
-               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
-                           state.branch);
-       else if (state.detached_from) {
-               if (state.detached_at)
-                       /* TRANSLATORS: make sure this matches
-                          "HEAD detached at " in wt-status.c */
-                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
-                               state.detached_from);
-               else
-                       /* TRANSLATORS: make sure this matches
-                          "HEAD detached from " in wt-status.c */
-                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
-                               state.detached_from);
-       }
-       else
-               strbuf_addstr(&desc, _("(no branch)"));
-       free(state.branch);
-       free(state.onto);
-       free(state.detached_from);
-       return strbuf_detach(&desc, NULL);
-}
+       struct strbuf fmt = STRBUF_INIT;
+       struct strbuf local = STRBUF_INIT;
+       struct strbuf remote = STRBUF_INIT;
 
-static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
-                                     struct ref_filter *filter, const char *remote_prefix)
-{
-       char c;
-       int current = 0;
-       int color;
-       struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
-       const char *prefix_to_show = "";
-       const char *prefix_to_skip = NULL;
-       const char *desc = item->refname;
-       char *to_free = NULL;
+       strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else)  %%(end)",
+                   branch_get_color(BRANCH_COLOR_CURRENT));
 
-       switch (item->kind) {
-       case FILTER_REFS_BRANCHES:
-               prefix_to_skip = "refs/heads/";
-               skip_prefix(desc, prefix_to_skip, &desc);
-               if (!filter->detached && !strcmp(desc, head))
-                       current = 1;
+       if (filter->verbose) {
+               struct strbuf obname = STRBUF_INIT;
+
+               if (filter->abbrev < 0)
+                       strbuf_addf(&obname, "%%(objectname:short)");
+               else if (!filter->abbrev)
+                       strbuf_addf(&obname, "%%(objectname)");
                else
-                       color = BRANCH_COLOR_LOCAL;
-               break;
-       case FILTER_REFS_REMOTES:
-               prefix_to_skip = "refs/remotes/";
-               skip_prefix(desc, prefix_to_skip, &desc);
-               color = BRANCH_COLOR_REMOTE;
-               prefix_to_show = remote_prefix;
-               break;
-       case FILTER_REFS_DETACHED_HEAD:
-               desc = to_free = get_head_description();
-               current = 1;
-               break;
-       default:
-               color = BRANCH_COLOR_PLAIN;
-               break;
-       }
+                       strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev);
 
-       c = ' ';
-       if (current) {
-               c = '*';
-               color = BRANCH_COLOR_CURRENT;
-       }
+               strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
+               strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
+               strbuf_addf(&local, " %s ", obname.buf);
 
-       strbuf_addf(&name, "%s%s", prefix_to_show, desc);
-       if (filter->verbose) {
-               int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
-               strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
-                           maxwidth + utf8_compensation, name.buf,
-                           branch_get_color(BRANCH_COLOR_RESET));
-       } else
-               strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
-                           name.buf, branch_get_color(BRANCH_COLOR_RESET));
-
-       if (item->symref) {
-               const char *symref = item->symref;
-               if (prefix_to_skip)
-                       skip_prefix(symref, prefix_to_skip, &symref);
-               strbuf_addf(&out, " -> %s", symref);
-       }
-       else if (filter->verbose)
-               /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
-               add_verbose_info(&out, item, filter, desc);
-       if (column_active(colopts)) {
-               assert(!filter->verbose && "--column and --verbose are incompatible");
-               string_list_append(&output, out.buf);
+               if (filter->verbose > 1)
+                       strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
+                                   "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
+                                   branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
+               else
+                       strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
+
+               strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
+                           "%%(if)%%(symref)%%(then) -> %%(symref:short)"
+                           "%%(else) %s %%(contents:subject)%%(end)",
+                           branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
+                           branch_get_color(BRANCH_COLOR_RESET), obname.buf);
+               strbuf_release(&obname);
        } else {
-               printf("%s\n", out.buf);
+               strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
+                           branch_get_color(BRANCH_COLOR_RESET));
+               strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
+                           branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix),
+                           branch_get_color(BRANCH_COLOR_RESET));
        }
-       strbuf_release(&name);
-       strbuf_release(&out);
-       free(to_free);
-}
 
-static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
-{
-       int i, max = 0;
-       for (i = 0; i < refs->nr; i++) {
-               struct ref_array_item *it = refs->items[i];
-               const char *desc = it->refname;
-               int w;
+       strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf);
 
-               skip_prefix(it->refname, "refs/heads/", &desc);
-               skip_prefix(it->refname, "refs/remotes/", &desc);
-               w = utf8_strwidth(desc);
-
-               if (it->kind == FILTER_REFS_REMOTES)
-                       w += remote_bonus;
-               if (w > max)
-                       max = w;
-       }
-       return max;
+       strbuf_release(&local);
+       strbuf_release(&remote);
+       return strbuf_detach(&fmt, NULL);
 }
 
-static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
+static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
        int i;
        struct ref_array array;
        int maxwidth = 0;
        const char *remote_prefix = "";
+       struct strbuf out = STRBUF_INIT;
+       char *to_free = NULL;
 
        /*
         * If we are listing more than just remote branches,
@@ -506,18 +398,32 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
 
        memset(&array, 0, sizeof(array));
 
-       verify_ref_format("%(refname)%(symref)");
        filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
 
        if (filter->verbose)
                maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
 
+       if (!format)
+               format = to_free = build_format(filter, maxwidth, remote_prefix);
+       verify_ref_format(format);
+
        ref_array_sort(sorting, &array);
 
-       for (i = 0; i < array.nr; i++)
-               format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
+       for (i = 0; i < array.nr; i++) {
+               format_ref_array_item(array.items[i], format, 0, &out);
+               if (column_active(colopts)) {
+                       assert(!filter->verbose && "--column and --verbose are incompatible");
+                        /* format to a string_list to let print_columns() do its job */
+                       string_list_append(&output, out.buf);
+               } else {
+                       fwrite(out.buf, 1, out.len, stdout);
+                       putchar('\n');
+               }
+               strbuf_release(&out);
+       }
 
        ref_array_clear(&array);
+       free(to_free);
 }
 
 static void reject_rebase_or_bisect_branch(const char *target)
@@ -579,14 +485,15 @@ static void rename_branch(const char *oldname, const char *newname, int force)
 
        if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch rename failed"));
-       strbuf_release(&logmsg);
 
        if (recovery)
                warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
 
-       if (replace_each_worktree_head_symref(oldref.buf, newref.buf))
+       if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
 
+       strbuf_release(&logmsg);
+
        strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
        strbuf_release(&oldref);
        strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
@@ -638,6 +545,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        struct ref_filter filter;
        int icase = 0;
        static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+       const char *format = NULL;
 
        struct option options[] = {
                OPT_GROUP(N_("Generic options")),
@@ -679,9 +587,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        N_("print only branches of the object"), 0, parse_opt_object_name
                },
                OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+               OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END(),
        };
 
+       setup_ref_filter_porcelain_msg();
+
        memset(&filter, 0, sizeof(filter));
        filter.kind = FILTER_REFS_BRANCHES;
        filter.abbrev = -1;
@@ -693,7 +604,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        track = git_branch_track;
 
-       head = resolve_refdup("HEAD", 0, head_sha1, NULL);
+       head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
@@ -749,7 +660,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if (!sorting)
                        sorting = ref_default_sorting();
                sorting->ignore_case = icase;
-               print_ref_list(&filter, sorting);
+               print_ref_list(&filter, sorting, format);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
                return 0;
index 30383e9..8b85cb8 100644 (file)
@@ -409,20 +409,20 @@ static int batch_object_cb(const unsigned char sha1[20], void *vdata)
        return 0;
 }
 
-static int batch_loose_object(const unsigned char *sha1,
+static int batch_loose_object(const struct object_id *oid,
                              const char *path,
                              void *data)
 {
-       sha1_array_append(data, sha1);
+       sha1_array_append(data, oid->hash);
        return 0;
 }
 
-static int batch_packed_object(const unsigned char *sha1,
+static int batch_packed_object(const struct object_id *oid,
                               struct packed_git *pack,
                               uint32_t pos,
                               void *data)
 {
-       sha1_array_append(data, sha1);
+       sha1_array_append(data, oid->hash);
        return 0;
 }
 
index 53a5a18..4d01ca0 100644 (file)
@@ -24,12 +24,13 @@ static const struct option check_attr_options[] = {
        OPT_END()
 };
 
-static void output_attr(int cnt, struct git_attr_check *check,
-       const char *file)
+static void output_attr(struct attr_check *check, const char *file)
 {
        int j;
+       int cnt = check->nr;
+
        for (j = 0; j < cnt; j++) {
-               const char *value = check[j].value;
+               const char *value = check->items[j].value;
 
                if (ATTR_TRUE(value))
                        value = "set";
@@ -42,35 +43,38 @@ static void output_attr(int cnt, struct git_attr_check *check,
                        printf("%s%c" /* path */
                               "%s%c" /* attrname */
                               "%s%c" /* attrvalue */,
-                              file, 0, git_attr_name(check[j].attr), 0, value, 0);
+                              file, 0,
+                              git_attr_name(check->items[j].attr), 0, value, 0);
                } else {
                        quote_c_style(file, NULL, stdout, 0);
-                       printf(": %s: %s\n", git_attr_name(check[j].attr), value);
+                       printf(": %s: %s\n",
+                              git_attr_name(check->items[j].attr), value);
                }
-
        }
 }
 
-static void check_attr(const char *prefix, int cnt,
-       struct git_attr_check *check, const char *file)
+static void check_attr(const char *prefix,
+                      struct attr_check *check,
+                      int collect_all,
+                      const char *file)
 {
        char *full_path =
                prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
-       if (check != NULL) {
-               if (git_check_attr(full_path, cnt, check))
-                       die("git_check_attr died");
-               output_attr(cnt, check, file);
+
+       if (collect_all) {
+               git_all_attrs(full_path, check);
        } else {
-               if (git_all_attrs(full_path, &cnt, &check))
-                       die("git_all_attrs died");
-               output_attr(cnt, check, file);
-               free(check);
+               if (git_check_attr(full_path, check))
+                       die("git_check_attr died");
        }
+       output_attr(check, file);
+
        free(full_path);
 }
 
-static void check_attr_stdin_paths(const char *prefix, int cnt,
-       struct git_attr_check *check)
+static void check_attr_stdin_paths(const char *prefix,
+                                  struct attr_check *check,
+                                  int collect_all)
 {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf unquoted = STRBUF_INIT;
@@ -84,7 +88,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt,
                                die("line is badly quoted");
                        strbuf_swap(&buf, &unquoted);
                }
-               check_attr(prefix, cnt, check, buf.buf);
+               check_attr(prefix, check, collect_all, buf.buf);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
        strbuf_release(&buf);
@@ -99,7 +103,7 @@ static NORETURN void error_with_usage(const char *msg)
 
 int cmd_check_attr(int argc, const char **argv, const char *prefix)
 {
-       struct git_attr_check *check;
+       struct attr_check *check;
        int cnt, i, doubledash, filei;
 
        if (!is_bare_repository())
@@ -159,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                        error_with_usage("No file specified");
        }
 
-       if (all_attrs) {
-               check = NULL;
-       } else {
-               check = xcalloc(cnt, sizeof(*check));
+       check = attr_check_alloc();
+       if (!all_attrs) {
                for (i = 0; i < cnt; i++) {
-                       const char *name;
-                       struct git_attr *a;
-                       name = argv[i];
-                       a = git_attr(name);
+                       const struct git_attr *a = git_attr(argv[i]);
+
                        if (!a)
                                return error("%s: not a valid attribute name",
-                                       name);
-                       check[i].attr = a;
+                                            argv[i]);
+                       attr_check_append(check, a);
                }
        }
 
        if (stdin_paths)
-               check_attr_stdin_paths(prefix, cnt, check);
+               check_attr_stdin_paths(prefix, check, all_attrs);
        else {
                for (i = filei; i < argc; i++)
-                       check_attr(prefix, cnt, check, argv[i]);
+                       check_attr(prefix, check, all_attrs, argv[i]);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
+
+       attr_check_free(check);
        return 0;
 }
index f174f50..81f07c3 100644 (file)
@@ -452,7 +452,7 @@ static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
 
-       strbuf_branchname(&buf, branch->name);
+       strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
        if (strcmp(buf.buf, branch->name))
                branch->name = xstrdup(buf.buf);
        strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
index d6bc3aa..d861f83 100644 (file)
@@ -174,8 +174,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                /* an empty dir could be removed even if it is unreadble */
                res = dry_run ? 0 : rmdir(path->buf);
                if (res) {
+                       int saved_errno = errno;
                        quote_path_relative(path->buf, prefix, &quoted);
-                       warning(_(msg_warn_remove_failed), quoted.buf);
+                       errno = saved_errno;
+                       warning_errno(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                }
                return res;
@@ -208,8 +210,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                                quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else {
+                               int saved_errno = errno;
                                quote_path_relative(path->buf, prefix, &quoted);
-                               warning(_(msg_warn_remove_failed), quoted.buf);
+                               errno = saved_errno;
+                               warning_errno(_(msg_warn_remove_failed), quoted.buf);
                                *dir_gone = 0;
                                ret = 1;
                        }
@@ -230,8 +234,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                if (!res)
                        *dir_gone = 1;
                else {
+                       int saved_errno = errno;
                        quote_path_relative(path->buf, prefix, &quoted);
-                       warning(_(msg_warn_remove_failed), quoted.buf);
+                       errno = saved_errno;
+                       warning_errno(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                        ret = 1;
                }
@@ -981,8 +987,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                } else {
                        res = dry_run ? 0 : unlink(abs_path.buf);
                        if (res) {
+                               int saved_errno = errno;
                                qname = quote_path_relative(item->string, NULL, &buf);
-                               warning(_(msg_warn_remove_failed), qname);
+                               errno = saved_errno;
+                               warning_errno(_(msg_warn_remove_failed), qname);
                                errors++;
                        } else if (!quiet) {
                                qname = quote_path_relative(item->string, NULL, &buf);
index 3f63edb..b4c929b 100644 (file)
@@ -681,7 +681,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
 
 static int checkout(int submodule_progress)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        char *head;
        struct lock_file *lock_file;
        struct unpack_trees_options opts;
@@ -692,7 +692,7 @@ static int checkout(int submodule_progress)
        if (option_no_checkout)
                return 0;
 
-       head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING, oid.hash, NULL);
        if (!head) {
                warning(_("remote HEAD refers to nonexistent ref, "
                          "unable to checkout.\n"));
@@ -700,7 +700,7 @@ static int checkout(int submodule_progress)
        }
        if (!strcmp(head, "HEAD")) {
                if (advice_detached_head)
-                       detach_advice(sha1_to_hex(sha1));
+                       detach_advice(oid_to_hex(&oid));
        } else {
                if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
@@ -721,7 +721,7 @@ static int checkout(int submodule_progress)
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 
-       tree = parse_tree_indirect(sha1);
+       tree = parse_tree_indirect(oid.hash);
        parse_tree(tree);
        init_tree_desc(&t, tree->buffer, tree->size);
        if (unpack_trees(1, &t, &opts) < 0)
@@ -731,7 +731,7 @@ static int checkout(int submodule_progress)
                die(_("unable to write new index file"));
 
        err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
-                          sha1_to_hex(sha1), "1", NULL);
+                          oid_to_hex(&oid), "1", NULL);
 
        if (!err && option_recursive) {
                struct argv_array args = ARGV_ARRAY_INIT;
index 2de5f6c..4e288bc 100644 (file)
@@ -496,7 +496,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
                      struct wt_status *s)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
 
        if (s->relative_paths)
                s->prefix = prefix;
@@ -509,9 +509,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        s->index_file = index_file;
        s->fp = fp;
        s->nowarn = nowarn;
-       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+       s->is_initial = get_sha1(s->reference, oid.hash) ? 1 : 0;
        if (!s->is_initial)
-               hashcpy(s->sha1_commit, sha1);
+               hashcpy(s->sha1_commit, oid.hash);
        s->status_format = status_format;
        s->ignore_submodule_arg = ignore_submodule_arg;
 
@@ -885,7 +885,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                commitable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
        } else {
-               unsigned char sha1[20];
+               struct object_id oid;
                const char *parent = "HEAD";
 
                if (!active_nr && read_cache() < 0)
@@ -894,7 +894,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                if (amend)
                        parent = "HEAD^1";
 
-               if (get_sha1(parent, sha1)) {
+               if (get_sha1(parent, oid.hash)) {
                        int i, ita_nr = 0;
 
                        for (i = 0; i < active_nr; i++)
@@ -1332,7 +1332,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 {
        static struct wt_status s;
        int fd;
-       unsigned char sha1[20];
+       struct object_id oid;
        static struct option builtin_status_options[] = {
                OPT__VERBOSE(&verbose, N_("be verbose")),
                OPT_SET_INT('s', "short", &status_format,
@@ -1382,9 +1382,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 
        fd = hold_locked_index(&index_lock, 0);
 
-       s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+       s.is_initial = get_sha1(s.reference, oid.hash) ? 1 : 0;
        if (!s.is_initial)
-               hashcpy(s.sha1_commit, sha1);
+               hashcpy(s.sha1_commit, oid.hash);
 
        s.ignore_submodule_arg = ignore_submodule_arg;
        s.status_format = status_format;
@@ -1418,19 +1418,19 @@ static const char *implicit_ident_advice(void)
 
 }
 
-static void print_summary(const char *prefix, const unsigned char *sha1,
+static void print_summary(const char *prefix, const struct object_id *oid,
                          int initial_commit)
 {
        struct rev_info rev;
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
-       unsigned char junk_sha1[20];
+       struct object_id junk_oid;
        const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
 
-       commit = lookup_commit(sha1);
+       commit = lookup_commit(oid->hash);
        if (!commit)
                die(_("couldn't look up newly created commit"));
        if (parse_commit(commit))
@@ -1477,7 +1477,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
-       head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL);
+       head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL);
        if (!strcmp(head, "HEAD"))
                head = _("detached HEAD");
        else
@@ -1522,8 +1522,8 @@ static int git_commit_config(const char *k, const char *v, void *cb)
        return git_status_config(k, v, s);
 }
 
-static int run_rewrite_hook(const unsigned char *oldsha1,
-                           const unsigned char *newsha1)
+static int run_rewrite_hook(const struct object_id *oldoid,
+                           const struct object_id *newoid)
 {
        struct child_process proc = CHILD_PROCESS_INIT;
        const char *argv[3];
@@ -1544,7 +1544,7 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
        code = start_command(&proc);
        if (code)
                return code;
-       strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+       strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
        sigchain_push(SIGPIPE, SIG_IGN);
        write_in_full(proc.in, sb.buf, sb.len);
        close(proc.in);
@@ -1636,7 +1636,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
        char *nl;
-       unsigned char sha1[20];
+       struct object_id oid;
        struct commit_list *parents = NULL;
        struct stat statbuf;
        struct commit *current_head = NULL;
@@ -1651,10 +1651,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
        s.colopts = 0;
 
-       if (get_sha1("HEAD", sha1))
+       if (get_sha1("HEAD", oid.hash))
                current_head = NULL;
        else {
-               current_head = lookup_commit_or_die(sha1, "HEAD");
+               current_head = lookup_commit_or_die(oid.hash, "HEAD");
                if (parse_commit(current_head))
                        die(_("could not parse HEAD commit"));
        }
@@ -1759,7 +1759,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
 
        if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1,
-                        parents, sha1, author_ident.buf, sign_commit, extra)) {
+                        parents, oid.hash, author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
@@ -1776,7 +1776,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, "HEAD", sha1,
+           ref_transaction_update(transaction, "HEAD", oid.hash,
                                   current_head
                                   ? current_head->object.oid.hash : null_sha1,
                                   0, sb.buf, &err) ||
@@ -1805,13 +1805,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                cfg = init_copy_notes_for_rewrite("amend");
                if (cfg) {
                        /* we are amending, so current_head is not NULL */
-                       copy_note_for_rewrite(cfg, current_head->object.oid.hash, sha1);
+                       copy_note_for_rewrite(cfg, current_head->object.oid.hash, oid.hash);
                        finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
                }
-               run_rewrite_hook(current_head->object.oid.hash, sha1);
+               run_rewrite_hook(&current_head->object.oid, &oid);
        }
        if (!quiet)
-               print_summary(prefix, sha1, !current_head);
+               print_summary(prefix, &oid, !current_head);
 
        strbuf_release(&err);
        return 0;
index a04b4f2..acb0594 100644 (file)
@@ -53,7 +53,7 @@ static void loose_garbage(const char *path)
                report_garbage(PACKDIR_FILE_GARBAGE, path);
 }
 
-static int count_loose(const unsigned char *sha1, const char *path, void *data)
+static int count_loose(const struct object_id *oid, const char *path, void *data)
 {
        struct stat st;
 
@@ -62,7 +62,7 @@ static int count_loose(const unsigned char *sha1, const char *path, void *data)
        else {
                loose_size += on_disk_bytes(st);
                loose++;
-               if (verbose && has_sha1_pack(sha1))
+               if (verbose && has_sha1_pack(oid->hash))
                        packed_loose++;
        }
        return 0;
index 01490a1..76c1805 100644 (file)
@@ -28,7 +28,8 @@ static int abbrev = -1; /* unspecified */
 static int max_candidates = 10;
 static struct hashmap names;
 static int have_util;
-static const char *pattern;
+static struct string_list patterns = STRING_LIST_INIT_NODUP;
+static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;
 static int always;
 static const char *dirty;
 
@@ -39,11 +40,11 @@ static const char *diff_index_args[] = {
 
 struct commit_name {
        struct hashmap_entry entry;
-       unsigned char peeled[20];
+       struct object_id peeled;
        struct tag *tag;
        unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
        unsigned name_checked:1;
-       unsigned char sha1[20];
+       struct object_id oid;
        char *path;
 };
 
@@ -54,17 +55,17 @@ static const char *prio_names[] = {
 static int commit_name_cmp(const struct commit_name *cn1,
                const struct commit_name *cn2, const void *peeled)
 {
-       return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled);
+       return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled);
 }
 
-static inline struct commit_name *find_commit_name(const unsigned char *peeled)
+static inline struct commit_name *find_commit_name(const struct object_id *peeled)
 {
-       return hashmap_get_from_hash(&names, sha1hash(peeled), peeled);
+       return hashmap_get_from_hash(&names, sha1hash(peeled->hash), peeled->hash);
 }
 
 static int replace_name(struct commit_name *e,
                               int prio,
-                              const unsigned char *sha1,
+                              const struct object_id *oid,
                               struct tag **tag)
 {
        if (!e || e->prio < prio)
@@ -77,13 +78,13 @@ static int replace_name(struct commit_name *e,
                struct tag *t;
 
                if (!e->tag) {
-                       t = lookup_tag(e->sha1);
+                       t = lookup_tag(e->oid.hash);
                        if (!t || parse_tag(t))
                                return 1;
                        e->tag = t;
                }
 
-               t = lookup_tag(sha1);
+               t = lookup_tag(oid->hash);
                if (!t || parse_tag(t))
                        return 0;
                *tag = t;
@@ -96,24 +97,24 @@ static int replace_name(struct commit_name *e,
 }
 
 static void add_to_known_names(const char *path,
-                              const unsigned char *peeled,
+                              const struct object_id *peeled,
                               int prio,
-                              const unsigned char *sha1)
+                              const struct object_id *oid)
 {
        struct commit_name *e = find_commit_name(peeled);
        struct tag *tag = NULL;
-       if (replace_name(e, prio, sha1, &tag)) {
+       if (replace_name(e, prio, oid, &tag)) {
                if (!e) {
                        e = xmalloc(sizeof(struct commit_name));
-                       hashcpy(e->peeled, peeled);
-                       hashmap_entry_init(e, sha1hash(peeled));
+                       oidcpy(&e->peeled, peeled);
+                       hashmap_entry_init(e, sha1hash(peeled->hash));
                        hashmap_add(&names, e);
                        e->path = NULL;
                }
                e->tag = tag;
                e->prio = prio;
                e->name_checked = 0;
-               hashcpy(e->sha1, sha1);
+               oidcpy(&e->oid, oid);
                free(e->path);
                e->path = xstrdup(path);
        }
@@ -129,9 +130,40 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
        if (!all && !is_tag)
                return 0;
 
-       /* Accept only tags that match the pattern, if given */
-       if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL)))
-               return 0;
+       /*
+        * If we're given exclude patterns, first exclude any tag which match
+        * any of the exclude pattern.
+        */
+       if (exclude_patterns.nr) {
+               struct string_list_item *item;
+
+               if (!is_tag)
+                       return 0;
+
+               for_each_string_list_item(item, &exclude_patterns) {
+                       if (!wildmatch(item->string, path + 10, 0, NULL))
+                               return 0;
+               }
+       }
+
+       /*
+        * If we're given patterns, accept only tags which match at least one
+        * pattern.
+        */
+       if (patterns.nr) {
+               struct string_list_item *item;
+
+               if (!is_tag)
+                       return 0;
+
+               for_each_string_list_item(item, &patterns) {
+                       if (!wildmatch(item->string, path + 10, 0, NULL))
+                               break;
+
+                       /* If we get here, no pattern matched. */
+                       return 0;
+               }
+       }
 
        /* Is it annotated? */
        if (!peel_ref(path, peeled.hash)) {
@@ -154,7 +186,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
        else
                prio = 0;
 
-       add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash);
+       add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);
        return 0;
 }
 
@@ -212,7 +244,7 @@ static unsigned long finish_depth_computation(
 static void display_name(struct commit_name *n)
 {
        if (n->prio == 2 && !n->tag) {
-               n->tag = lookup_tag(n->sha1);
+               n->tag = lookup_tag(n->oid.hash);
                if (!n->tag || parse_tag(n->tag))
                        die(_("annotated tag %s not available"), n->path);
        }
@@ -230,14 +262,14 @@ static void display_name(struct commit_name *n)
                printf("%s", n->path);
 }
 
-static void show_suffix(int depth, const unsigned char *sha1)
+static void show_suffix(int depth, const struct object_id *oid)
 {
-       printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
+       printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
 }
 
 static void describe(const char *arg, int last_one)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        struct commit *cmit, *gave_up_on = NULL;
        struct commit_list *list;
        struct commit_name *n;
@@ -246,20 +278,20 @@ static void describe(const char *arg, int last_one)
        unsigned long seen_commits = 0;
        unsigned int unannotated_cnt = 0;
 
-       if (get_sha1(arg, sha1))
+       if (get_oid(arg, &oid))
                die(_("Not a valid object name %s"), arg);
-       cmit = lookup_commit_reference(sha1);
+       cmit = lookup_commit_reference(oid.hash);
        if (!cmit)
                die(_("%s is not a valid '%s' object"), arg, commit_type);
 
-       n = find_commit_name(cmit->object.oid.hash);
+       n = find_commit_name(&cmit->object.oid);
        if (n && (tags || all || n->prio == 2)) {
                /*
                 * Exact match to an existing ref.
                 */
                display_name(n);
                if (longformat)
-                       show_suffix(0, n->tag ? n->tag->tagged->oid.hash : sha1);
+                       show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid);
                if (dirty)
                        printf("%s", dirty);
                printf("\n");
@@ -276,7 +308,7 @@ static void describe(const char *arg, int last_one)
                struct commit *c;
                struct commit_name *n = hashmap_iter_first(&names, &iter);
                for (; n; n = hashmap_iter_next(&iter)) {
-                       c = lookup_commit_reference_gently(n->peeled, 1);
+                       c = lookup_commit_reference_gently(n->peeled.hash, 1);
                        if (c)
                                c->util = n;
                }
@@ -380,7 +412,7 @@ static void describe(const char *arg, int last_one)
 
        display_name(all_matches[0].name);
        if (abbrev)
-               show_suffix(all_matches[0].depth, cmit->object.oid.hash);
+               show_suffix(all_matches[0].depth, &cmit->object.oid);
        if (dirty)
                printf("%s", dirty);
        printf("\n");
@@ -404,8 +436,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                            N_("only output exact matches"), 0),
                OPT_INTEGER(0, "candidates", &max_candidates,
                            N_("consider <n> most recent tags (default: 10)")),
-               OPT_STRING(0, "match",       &pattern, N_("pattern"),
+               OPT_STRING_LIST(0, "match", &patterns, N_("pattern"),
                           N_("only consider tags matching <pattern>")),
+               OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"),
+                          N_("do not consider tags matching <pattern>")),
                OPT_BOOL(0, "always",        &always,
                        N_("show abbreviated commit object as fallback")),
                {OPTION_STRING, 0, "dirty",  &dirty, N_("mark"),
@@ -430,6 +464,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                die(_("--long is incompatible with --abbrev=0"));
 
        if (contains) {
+               struct string_list_item *item;
                struct argv_array args;
 
                argv_array_init(&args);
@@ -440,8 +475,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                        argv_array_push(&args, "--always");
                if (!all) {
                        argv_array_push(&args, "--tags");
-                       if (pattern)
-                               argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
+                       for_each_string_list_item(item, &patterns)
+                               argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
+                       for_each_string_list_item(item, &exclude_patterns)
+                               argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
                }
                if (argc)
                        argv_array_pushv(&args, argv);
index 8ce0048..326f88b 100644 (file)
@@ -7,46 +7,44 @@
 
 static struct rev_info log_tree_opt;
 
-static int diff_tree_commit_sha1(const unsigned char *sha1)
+static int diff_tree_commit_sha1(const struct object_id *oid)
 {
-       struct commit *commit = lookup_commit_reference(sha1);
+       struct commit *commit = lookup_commit_reference(oid->hash);
        if (!commit)
                return -1;
        return log_tree_commit(&log_tree_opt, commit);
 }
 
 /* Diff one or more commits. */
-static int stdin_diff_commit(struct commit *commit, char *line, int len)
+static int stdin_diff_commit(struct commit *commit, const char *p)
 {
-       unsigned char sha1[20];
-       if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
-               /* Graft the fake parents locally to the commit */
-               int pos = 41;
-               struct commit_list **pptr;
-
-               /* Free the real parent list */
-               free_commit_list(commit->parents);
-               commit->parents = NULL;
-               pptr = &(commit->parents);
-               while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
-                       struct commit *parent = lookup_commit(sha1);
-                       if (parent) {
-                               pptr = &commit_list_insert(parent, pptr)->next;
-                       }
-                       pos += 41;
+       struct object_id oid;
+       struct commit_list **pptr = NULL;
+
+       /* Graft the fake parents locally to the commit */
+       while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) {
+               struct commit *parent = lookup_commit(oid.hash);
+               if (!pptr) {
+                       /* Free the real parent list */
+                       free_commit_list(commit->parents);
+                       commit->parents = NULL;
+                       pptr = &(commit->parents);
+               }
+               if (parent) {
+                       pptr = &commit_list_insert(parent, pptr)->next;
                }
        }
        return log_tree_commit(&log_tree_opt, commit);
 }
 
 /* Diff two trees. */
-static int stdin_diff_trees(struct tree *tree1, char *line, int len)
+static int stdin_diff_trees(struct tree *tree1, const char *p)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        struct tree *tree2;
-       if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
+       if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p)
                return error("Need exactly two trees, separated by a space");
-       tree2 = lookup_tree(sha1);
+       tree2 = lookup_tree(oid.hash);
        if (!tree2 || parse_tree(tree2))
                return -1;
        printf("%s %s\n", oid_to_hex(&tree1->object.oid),
@@ -60,23 +58,24 @@ static int stdin_diff_trees(struct tree *tree1, char *line, int len)
 static int diff_tree_stdin(char *line)
 {
        int len = strlen(line);
-       unsigned char sha1[20];
+       struct object_id oid;
        struct object *obj;
+       const char *p;
 
        if (!len || line[len-1] != '\n')
                return -1;
        line[len-1] = 0;
-       if (get_sha1_hex(line, sha1))
+       if (parse_oid_hex(line, &oid, &p))
                return -1;
-       obj = parse_object(sha1);
+       obj = parse_object(oid.hash);
        if (!obj)
                return -1;
        if (obj->type == OBJ_COMMIT)
-               return stdin_diff_commit((struct commit *)obj, line, len);
+               return stdin_diff_commit((struct commit *)obj, p);
        if (obj->type == OBJ_TREE)
-               return stdin_diff_trees((struct tree *)obj, line, len);
+               return stdin_diff_trees((struct tree *)obj, p);
        error("Object %s is a %s, not a commit or tree",
-             sha1_to_hex(sha1), typename(obj->type));
+             oid_to_hex(&oid), typename(obj->type));
        return -1;
 }
 
@@ -141,7 +140,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
                break;
        case 1:
                tree1 = opt->pending.objects[0].item;
-               diff_tree_commit_sha1(tree1->oid.hash);
+               diff_tree_commit_sha1(&tree1->oid);
                break;
        case 2:
                tree1 = opt->pending.objects[0].item;
@@ -164,9 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
                        opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
                                               DIFF_SETUP_USE_CACHE);
                while (fgets(line, sizeof(line), stdin)) {
-                       unsigned char sha1[20];
+                       struct object_id oid;
 
-                       if (get_sha1_hex(line, sha1)) {
+                       if (get_oid_hex(line, &oid)) {
                                fputs(line, stdout);
                                fflush(stdout);
                        }
index 1e815b5..e022063 100644 (file)
@@ -212,7 +212,7 @@ static char *anonymize_blob(unsigned long *size)
        return strbuf_detach(&out, NULL);
 }
 
-static void export_blob(const unsigned char *sha1)
+static void export_blob(const struct object_id *oid)
 {
        unsigned long size;
        enum object_type type;
@@ -223,34 +223,34 @@ static void export_blob(const unsigned char *sha1)
        if (no_data)
                return;
 
-       if (is_null_sha1(sha1))
+       if (is_null_oid(oid))
                return;
 
-       object = lookup_object(sha1);
+       object = lookup_object(oid->hash);
        if (object && object->flags & SHOWN)
                return;
 
        if (anonymize) {
                buf = anonymize_blob(&size);
-               object = (struct object *)lookup_blob(sha1);
+               object = (struct object *)lookup_blob(oid->hash);
                eaten = 0;
        } else {
-               buf = read_sha1_file(sha1, &type, &size);
+               buf = read_sha1_file(oid->hash, &type, &size);
                if (!buf)
-                       die ("Could not read blob %s", sha1_to_hex(sha1));
-               if (check_sha1_signature(sha1, buf, size, typename(type)) < 0)
-                       die("sha1 mismatch in blob %s", sha1_to_hex(sha1));
-               object = parse_object_buffer(sha1, type, size, buf, &eaten);
+                       die ("Could not read blob %s", oid_to_hex(oid));
+               if (check_sha1_signature(oid->hash, buf, size, typename(type)) < 0)
+                       die("sha1 mismatch in blob %s", oid_to_hex(oid));
+               object = parse_object_buffer(oid->hash, type, size, buf, &eaten);
        }
 
        if (!object)
-               die("Could not read blob %s", sha1_to_hex(sha1));
+               die("Could not read blob %s", oid_to_hex(oid));
 
        mark_next_object(object);
 
        printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
        if (size && fwrite(buf, size, 1, stdout) != 1)
-               die_errno ("Could not write blob '%s'", sha1_to_hex(sha1));
+               die_errno ("Could not write blob '%s'", oid_to_hex(oid));
        printf("\n");
 
        show_progress();
@@ -323,19 +323,19 @@ static void print_path(const char *path)
        }
 }
 
-static void *generate_fake_sha1(const void *old, size_t *len)
+static void *generate_fake_oid(const void *old, size_t *len)
 {
        static uint32_t counter = 1; /* avoid null sha1 */
-       unsigned char *out = xcalloc(20, 1);
-       put_be32(out + 16, counter++);
+       unsigned char *out = xcalloc(GIT_SHA1_RAWSZ, 1);
+       put_be32(out + GIT_SHA1_RAWSZ - 4, counter++);
        return out;
 }
 
-static const unsigned char *anonymize_sha1(const unsigned char *sha1)
+static const unsigned char *anonymize_sha1(const struct object_id *oid)
 {
        static struct hashmap sha1s;
-       size_t len = 20;
-       return anonymize_mem(&sha1s, generate_fake_sha1, sha1, &len);
+       size_t len = GIT_SHA1_RAWSZ;
+       return anonymize_mem(&sha1s, generate_fake_oid, oid, &len);
 }
 
 static void show_filemodify(struct diff_queue_struct *q,
@@ -383,7 +383,7 @@ static void show_filemodify(struct diff_queue_struct *q,
                        if (no_data || S_ISGITLINK(spec->mode))
                                printf("M %06o %s ", spec->mode,
                                       sha1_to_hex(anonymize ?
-                                                  anonymize_sha1(spec->oid.hash) :
+                                                  anonymize_sha1(&spec->oid) :
                                                   spec->oid.hash));
                        else {
                                struct object *object = lookup_object(spec->oid.hash);
@@ -572,7 +572,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
        /* Export the referenced blobs, and remember the marks. */
        for (i = 0; i < diff_queued_diff.nr; i++)
                if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
-                       export_blob(diff_queued_diff.queue[i]->two->oid.hash);
+                       export_blob(&diff_queued_diff.queue[i]->two->oid);
 
        refname = commit->util;
        if (anonymize) {
@@ -797,14 +797,14 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
 
        for (i = 0; i < info->nr; i++) {
                struct rev_cmdline_entry *e = info->rev + i;
-               unsigned char sha1[20];
+               struct object_id oid;
                struct commit *commit;
                char *full_name;
 
                if (e->flags & UNINTERESTING)
                        continue;
 
-               if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+               if (dwim_ref(e->name, strlen(e->name), oid.hash, &full_name) != 1)
                        continue;
 
                if (refspecs) {
@@ -828,7 +828,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
                case OBJ_COMMIT:
                        break;
                case OBJ_BLOB:
-                       export_blob(commit->object.oid.hash);
+                       export_blob(&commit->object.oid);
                        continue;
                default: /* OBJ_TAG (nested tags) is already handled */
                        warning("Tag points to object of unexpected type %s, skipping.",
@@ -912,7 +912,7 @@ static void import_marks(char *input_file)
        while (fgets(line, sizeof(line), f)) {
                uint32_t mark;
                char *line_end, *mark_end;
-               unsigned char sha1[20];
+               struct object_id oid;
                struct object *object;
                struct commit *commit;
                enum object_type type;
@@ -924,28 +924,28 @@ static void import_marks(char *input_file)
 
                mark = strtoumax(line + 1, &mark_end, 10);
                if (!mark || mark_end == line + 1
-                       || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1))
+                       || *mark_end != ' ' || get_oid_hex(mark_end + 1, &oid))
                        die("corrupt mark line: %s", line);
 
                if (last_idnum < mark)
                        last_idnum = mark;
 
-               type = sha1_object_info(sha1, NULL);
+               type = sha1_object_info(oid.hash, NULL);
                if (type < 0)
-                       die("object not found: %s", sha1_to_hex(sha1));
+                       die("object not found: %s", oid_to_hex(&oid));
 
                if (type != OBJ_COMMIT)
                        /* only commits */
                        continue;
 
-               commit = lookup_commit(sha1);
+               commit = lookup_commit(oid.hash);
                if (!commit)
-                       die("not a commit? can't happen: %s", sha1_to_hex(sha1));
+                       die("not a commit? can't happen: %s", oid_to_hex(&oid));
 
                object = &commit->object;
 
                if (object->flags & SHOWN)
-                       error("Object %s already has a mark", sha1_to_hex(sha1));
+                       error("Object %s already has a mark", oid_to_hex(&oid));
 
                mark_object(object, mark);
 
index cfe9e44..2a1c1c2 100644 (file)
@@ -219,12 +219,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
         * remote no-such-ref' would silently succeed without issuing
         * an error.
         */
-       for (i = 0; i < nr_sought; i++) {
-               if (!sought[i] || sought[i]->matched)
-                       continue;
-               error("no such remote ref %s", sought[i]->name);
-               ret = 1;
-       }
+       ret |= report_unmatched_refs(sought, nr_sought);
 
        while (ref) {
                printf("%s %s\n",
index efab62f..6faa3c0 100644 (file)
@@ -41,7 +41,7 @@ struct src_data {
 };
 
 struct origin_data {
-       unsigned char sha1[20];
+       struct object_id oid;
        unsigned is_local_branch:1;
 };
 
@@ -59,8 +59,8 @@ static struct string_list origins = STRING_LIST_INIT_DUP;
 struct merge_parents {
        int alloc, nr;
        struct merge_parent {
-               unsigned char given[20];
-               unsigned char commit[20];
+               struct object_id given;
+               struct object_id commit;
                unsigned char used;
        } *item;
 };
@@ -70,14 +70,14 @@ struct merge_parents {
  * hundreds of heads at a time anyway.
  */
 static struct merge_parent *find_merge_parent(struct merge_parents *table,
-                                             unsigned char *given,
-                                             unsigned char *commit)
+                                             struct object_id *given,
+                                             struct object_id *commit)
 {
        int i;
        for (i = 0; i < table->nr; i++) {
-               if (given && hashcmp(table->item[i].given, given))
+               if (given && oidcmp(&table->item[i].given, given))
                        continue;
-               if (commit && hashcmp(table->item[i].commit, commit))
+               if (commit && oidcmp(&table->item[i].commit, commit))
                        continue;
                return &table->item[i];
        }
@@ -85,14 +85,14 @@ static struct merge_parent *find_merge_parent(struct merge_parents *table,
 }
 
 static void add_merge_parent(struct merge_parents *table,
-                            unsigned char *given,
-                            unsigned char *commit)
+                            struct object_id *given,
+                            struct object_id *commit)
 {
        if (table->nr && find_merge_parent(table, given, commit))
                return;
        ALLOC_GROW(table->item, table->nr + 1, table->alloc);
-       hashcpy(table->item[table->nr].given, given);
-       hashcpy(table->item[table->nr].commit, commit);
+       oidcpy(&table->item[table->nr].given, given);
+       oidcpy(&table->item[table->nr].commit, commit);
        table->item[table->nr].used = 0;
        table->nr++;
 }
@@ -106,30 +106,30 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
        struct src_data *src_data;
        struct string_list_item *item;
        int pulling_head = 0;
-       unsigned char sha1[20];
+       struct object_id oid;
 
-       if (len < 43 || line[40] != '\t')
+       if (len < GIT_SHA1_HEXSZ + 3 || line[GIT_SHA1_HEXSZ] != '\t')
                return 1;
 
-       if (starts_with(line + 41, "not-for-merge"))
+       if (starts_with(line + GIT_SHA1_HEXSZ + 1, "not-for-merge"))
                return 0;
 
-       if (line[41] != '\t')
+       if (line[GIT_SHA1_HEXSZ + 1] != '\t')
                return 2;
 
-       i = get_sha1_hex(line, sha1);
+       i = get_oid_hex(line, &oid);
        if (i)
                return 3;
 
-       if (!find_merge_parent(merge_parents, sha1, NULL))
+       if (!find_merge_parent(merge_parents, &oid, NULL))
                return 0; /* subsumed by other parents */
 
        origin_data = xcalloc(1, sizeof(struct origin_data));
-       hashcpy(origin_data->sha1, sha1);
+       oidcpy(&origin_data->oid, &oid);
 
        if (line[len - 1] == '\n')
                line[len - 1] = 0;
-       line += 42;
+       line += GIT_SHA1_HEXSZ + 2;
 
        /*
         * At this point, line points at the beginning of comment e.g.
@@ -338,10 +338,10 @@ static void shortlog(const char *name,
        struct string_list committers = STRING_LIST_INIT_DUP;
        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
        struct strbuf sb = STRBUF_INIT;
-       const unsigned char *sha1 = origin_data->sha1;
+       const struct object_id *oid = &origin_data->oid;
        int limit = opts->shortlog_len;
 
-       branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
+       branch = deref_tag(parse_object(oid->hash), oid_to_hex(oid), GIT_SHA1_HEXSZ);
        if (!branch || branch->type != OBJ_COMMIT)
                return;
 
@@ -531,7 +531,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
 }
 
 static void find_merge_parents(struct merge_parents *result,
-                              struct strbuf *in, unsigned char *head)
+                              struct strbuf *in, struct object_id *head)
 {
        struct commit_list *parents;
        struct commit *head_commit;
@@ -542,31 +542,31 @@ static void find_merge_parents(struct merge_parents *result,
                int len;
                char *p = in->buf + pos;
                char *newline = strchr(p, '\n');
-               unsigned char sha1[20];
+               struct object_id oid;
                struct commit *parent;
                struct object *obj;
 
                len = newline ? newline - p : strlen(p);
                pos += len + !!newline;
 
-               if (len < 43 ||
-                   get_sha1_hex(p, sha1) ||
-                   p[40] != '\t' ||
-                   p[41] != '\t')
+               if (len < GIT_SHA1_HEXSZ + 3 ||
+                   get_oid_hex(p, &oid) ||
+                   p[GIT_SHA1_HEXSZ] != '\t' ||
+                   p[GIT_SHA1_HEXSZ + 1] != '\t')
                        continue; /* skip not-for-merge */
                /*
                 * Do not use get_merge_parent() here; we do not have
                 * "name" here and we do not want to contaminate its
                 * util field yet.
                 */
-               obj = parse_object(sha1);
+               obj = parse_object(oid.hash);
                parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
                if (!parent)
                        continue;
                commit_list_insert(parent, &parents);
-               add_merge_parent(result, obj->oid.hash, parent->object.oid.hash);
+               add_merge_parent(result, &obj->oid, &parent->object.oid);
        }
-       head_commit = lookup_commit(head);
+       head_commit = lookup_commit(head->hash);
        if (head_commit)
                commit_list_insert(head_commit, &parents);
        parents = reduce_heads(parents);
@@ -574,7 +574,7 @@ static void find_merge_parents(struct merge_parents *result,
        while (parents) {
                struct commit *cmit = pop_commit(&parents);
                for (i = 0; i < result->nr; i++)
-                       if (!hashcmp(result->item[i].commit, cmit->object.oid.hash))
+                       if (!oidcmp(&result->item[i].commit, &cmit->object.oid))
                                result->item[i].used = 1;
        }
 
@@ -592,7 +592,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                  struct fmt_merge_msg_opts *opts)
 {
        int i = 0, pos = 0;
-       unsigned char head_sha1[20];
+       struct object_id head_oid;
        const char *current_branch;
        void *current_branch_to_free;
        struct merge_parents merge_parents;
@@ -601,13 +601,13 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 
        /* get current branch */
        current_branch = current_branch_to_free =
-               resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL);
+               resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL);
        if (!current_branch)
                die("No current branch");
        if (starts_with(current_branch, "refs/heads/"))
                current_branch += 11;
 
-       find_merge_parents(&merge_parents, in, head_sha1);
+       find_merge_parents(&merge_parents, in, &head_oid);
 
        /* get a line */
        while (pos < in->len) {
@@ -633,7 +633,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                struct commit *head;
                struct rev_info rev;
 
-               head = lookup_commit_or_die(head_sha1, "HEAD");
+               head = lookup_commit_or_die(head_oid.hash, "HEAD");
                init_revisions(&rev, NULL);
                rev.commit_format = CMIT_FMT_ONELINE;
                rev.ignore_merges = 1;
index 1a5cacc..f76e416 100644 (file)
@@ -396,13 +396,13 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
 
 static int default_refs;
 
-static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
+static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,
        unsigned long timestamp)
 {
        struct object *obj;
 
-       if (!is_null_sha1(sha1)) {
-               obj = lookup_object(sha1);
+       if (!is_null_oid(oid)) {
+               obj = lookup_object(oid->hash);
                if (obj && (obj->flags & HAS_OBJ)) {
                        if (timestamp && name_objects)
                                add_decoration(fsck_walk_options.object_names,
@@ -411,13 +411,13 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
                        obj->used = 1;
                        mark_object_reachable(obj);
                } else {
-                       error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1));
+                       error("%s: invalid reflog entry %s", refname, oid_to_hex(oid));
                        errors_found |= ERROR_REACHABLE;
                }
        }
 }
 
-static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
 {
@@ -425,10 +425,10 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 
        if (verbose)
                fprintf(stderr, "Checking reflog %s->%s\n",
-                       sha1_to_hex(osha1), sha1_to_hex(nsha1));
+                       oid_to_hex(ooid), oid_to_hex(noid));
 
-       fsck_handle_reflog_sha1(refname, osha1, 0);
-       fsck_handle_reflog_sha1(refname, nsha1, timestamp);
+       fsck_handle_reflog_oid(refname, ooid, 0);
+       fsck_handle_reflog_oid(refname, noid, timestamp);
        return 0;
 }
 
@@ -491,7 +491,7 @@ static void get_default_heads(void)
        }
 }
 
-static struct object *parse_loose_object(const unsigned char *sha1,
+static struct object *parse_loose_object(const struct object_id *oid,
                                         const char *path)
 {
        struct object *obj;
@@ -500,27 +500,27 @@ static struct object *parse_loose_object(const unsigned char *sha1,
        unsigned long size;
        int eaten;
 
-       if (read_loose_object(path, sha1, &type, &size, &contents) < 0)
+       if (read_loose_object(path, oid->hash, &type, &size, &contents) < 0)
                return NULL;
 
        if (!contents && type != OBJ_BLOB)
                die("BUG: read_loose_object streamed a non-blob");
 
-       obj = parse_object_buffer(sha1, type, size, contents, &eaten);
+       obj = parse_object_buffer(oid->hash, type, size, contents, &eaten);
 
        if (!eaten)
                free(contents);
        return obj;
 }
 
-static int fsck_loose(const unsigned char *sha1, const char *path, void *data)
+static int fsck_loose(const struct object_id *oid, const char *path, void *data)
 {
-       struct object *obj = parse_loose_object(sha1, path);
+       struct object *obj = parse_loose_object(oid, path);
 
        if (!obj) {
                errors_found |= ERROR_OBJECT;
                error("%s: object corrupt or missing: %s",
-                     sha1_to_hex(sha1), path);
+                     oid_to_hex(oid), path);
                return 0; /* keep checking other objects */
        }
 
@@ -619,26 +619,26 @@ static int fsck_cache_tree(struct cache_tree *it)
        return err;
 }
 
-static void mark_object_for_connectivity(const unsigned char *sha1)
+static void mark_object_for_connectivity(const struct object_id *oid)
 {
-       struct object *obj = lookup_unknown_object(sha1);
+       struct object *obj = lookup_unknown_object(oid->hash);
        obj->flags |= HAS_OBJ;
 }
 
-static int mark_loose_for_connectivity(const unsigned char *sha1,
+static int mark_loose_for_connectivity(const struct object_id *oid,
                                       const char *path,
                                       void *data)
 {
-       mark_object_for_connectivity(sha1);
+       mark_object_for_connectivity(oid);
        return 0;
 }
 
-static int mark_packed_for_connectivity(const unsigned char *sha1,
+static int mark_packed_for_connectivity(const struct object_id *oid,
                                        struct packed_git *pack,
                                        uint32_t pos,
                                        void *data)
 {
-       mark_object_for_connectivity(sha1);
+       mark_object_for_connectivity(oid);
        return 0;
 }
 
index 331f219..c2c61a5 100644 (file)
@@ -33,6 +33,8 @@ static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
 static int detach_auto = 1;
+static unsigned long gc_log_expire_time;
+static const char *gc_log_expire = "1.day.ago";
 static const char *prune_expire = "2.weeks.ago";
 static const char *prune_worktrees_expire = "3.months.ago";
 
@@ -62,24 +64,31 @@ static void report_pack_garbage(unsigned seen_bits, const char *path)
                string_list_append(&pack_garbage, path);
 }
 
-static void git_config_date_string(const char *key, const char **output)
-{
-       if (git_config_get_string_const(key, output))
-               return;
-       if (strcmp(*output, "now")) {
-               unsigned long now = approxidate("now");
-               if (approxidate(*output) >= now)
-                       git_die_config(key, _("Invalid %s: '%s'"), key, *output);
-       }
-}
-
 static void process_log_file(void)
 {
        struct stat st;
-       if (!fstat(get_lock_file_fd(&log_lock), &st) && st.st_size)
+       if (fstat(get_lock_file_fd(&log_lock), &st)) {
+               /*
+                * Perhaps there was an i/o error or another
+                * unlikely situation.  Try to make a note of
+                * this in gc.log along with any existing
+                * messages.
+                */
+               int saved_errno = errno;
+               fprintf(stderr, _("Failed to fstat %s: %s"),
+                       get_tempfile_path(&log_lock.tempfile),
+                       strerror(saved_errno));
+               fflush(stderr);
                commit_lock_file(&log_lock);
-       else
+               errno = saved_errno;
+       } else if (st.st_size) {
+               /* There was some error recorded in the lock file */
+               commit_lock_file(&log_lock);
+       } else {
+               /* No error, clean up any old gc.log */
+               unlink(git_path("gc.log"));
                rollback_lock_file(&log_lock);
+       }
 }
 
 static void process_log_file_at_exit(void)
@@ -111,8 +120,10 @@ static void gc_config(void)
        git_config_get_int("gc.auto", &gc_auto_threshold);
        git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
        git_config_get_bool("gc.autodetach", &detach_auto);
-       git_config_date_string("gc.pruneexpire", &prune_expire);
-       git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire);
+       git_config_get_expiry("gc.pruneexpire", &prune_expire);
+       git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
+       git_config_get_expiry("gc.logexpiry", &gc_log_expire);
+
        git_config(git_default_config, NULL);
 }
 
@@ -290,19 +301,34 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
 static int report_last_gc_error(void)
 {
        struct strbuf sb = STRBUF_INIT;
-       int ret;
+       int ret = 0;
+       struct stat st;
+       char *gc_log_path = git_pathdup("gc.log");
+
+       if (stat(gc_log_path, &st)) {
+               if (errno == ENOENT)
+                       goto done;
+
+               ret = error_errno(_("Can't stat %s"), gc_log_path);
+               goto done;
+       }
+
+       if (st.st_mtime < gc_log_expire_time)
+               goto done;
 
-       ret = strbuf_read_file(&sb, git_path("gc.log"), 0);
+       ret = strbuf_read_file(&sb, gc_log_path, 0);
        if (ret > 0)
-               return error(_("The last gc run reported the following. "
+               ret = error(_("The last gc run reported the following. "
                               "Please correct the root cause\n"
                               "and remove %s.\n"
                               "Automatic cleanup will not be performed "
                               "until the file is removed.\n\n"
                               "%s"),
-                            git_path("gc.log"), sb.buf);
+                           gc_log_path, sb.buf);
        strbuf_release(&sb);
-       return 0;
+done:
+       free(gc_log_path);
+       return ret;
 }
 
 static int gc_before_repack(void)
@@ -349,7 +375,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
        argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
+       /* default expiry time, overwritten in gc_config */
        gc_config();
+       if (parse_expiry_date(gc_log_expire, &gc_log_expire_time))
+               die(_("Failed to parse gc.logexpiry value %s"), gc_log_expire);
 
        if (pack_refs < 0)
                pack_refs = !is_bare_repository();
@@ -448,5 +477,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                warning(_("There are too many unreachable loose objects; "
                        "run 'git prune' to remove them."));
 
+       if (!daemonized)
+               unlink(git_path("gc.log"));
+
        return 0;
 }
index 2c727ef..837836f 100644 (file)
@@ -294,17 +294,17 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
        return st;
 }
 
-static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
+static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
 {
        void *data;
 
        grep_read_lock();
-       data = read_sha1_file(sha1, type, size);
+       data = read_sha1_file(oid->hash, type, size);
        grep_read_unlock();
        return data;
 }
 
-static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
+static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
                     const char *filename, int tree_name_len,
                     const char *path)
 {
@@ -323,7 +323,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
 
 #ifndef NO_PTHREADS
        if (num_threads) {
-               add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
+               add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid);
                strbuf_release(&pathbuf);
                return 0;
        } else
@@ -332,7 +332,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
                struct grep_source gs;
                int hit;
 
-               grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
+               grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid);
                strbuf_release(&pathbuf);
                hit = grep_source(opt, &gs);
 
@@ -690,7 +690,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec,
                            ce_skip_worktree(ce)) {
                                if (ce_stage(ce) || ce_intent_to_add(ce))
                                        continue;
-                               hit |= grep_sha1(opt, ce->oid.hash, ce->name,
+                               hit |= grep_oid(opt, &ce->oid, ce->name,
                                                 0, ce->name);
                        } else {
                                hit |= grep_file(opt, ce->name);
@@ -750,7 +750,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
                strbuf_add(base, entry.path, te_len);
 
                if (S_ISREG(entry.mode)) {
-                       hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
+                       hit |= grep_oid(opt, entry.oid, base->buf, tn_len,
                                         check_attr ? base->buf + tn_len : NULL);
                } else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
@@ -758,7 +758,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
                        void *data;
                        unsigned long size;
 
-                       data = lock_and_read_sha1_file(entry.oid->hash, &type, &size);
+                       data = lock_and_read_oid_file(entry.oid, &type, &size);
                        if (!data)
                                die(_("unable to read tree (%s)"),
                                    oid_to_hex(entry.oid));
@@ -787,7 +787,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                       struct object *obj, const char *name, const char *path)
 {
        if (obj->type == OBJ_BLOB)
-               return grep_sha1(opt, obj->oid.hash, name, 0, path);
+               return grep_oid(opt, &obj->oid, name, 0, path);
        if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
@@ -967,6 +967,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        int dummy;
        int use_index = 1;
        int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
+       int allow_revs;
 
        struct option options[] = {
                OPT_BOOL(0, "cached", &cached,
@@ -1149,26 +1150,69 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 
        compile_grep_patterns(&opt);
 
-       /* Check revs and then paths */
+       /*
+        * We have to find "--" in a separate pass, because its presence
+        * influences how we will parse arguments that come before it.
+        */
+       for (i = 0; i < argc; i++) {
+               if (!strcmp(argv[i], "--")) {
+                       seen_dashdash = 1;
+                       break;
+               }
+       }
+
+       /*
+        * Resolve any rev arguments. If we have a dashdash, then everything up
+        * to it must resolve as a rev. If not, then we stop at the first
+        * non-rev and assume everything else is a path.
+        */
+       allow_revs = use_index && !untracked;
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
-               unsigned char sha1[20];
+               struct object_id oid;
                struct object_context oc;
-               /* Is it a rev? */
-               if (!get_sha1_with_context(arg, 0, sha1, &oc)) {
-                       struct object *object = parse_object_or_die(sha1, arg);
-                       if (!seen_dashdash)
-                               verify_non_filename(prefix, arg);
-                       add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
-                       continue;
-               }
+               struct object *object;
+
                if (!strcmp(arg, "--")) {
                        i++;
-                       seen_dashdash = 1;
+                       break;
                }
-               break;
+
+               if (!allow_revs) {
+                       if (seen_dashdash)
+                               die(_("--no-index or --untracked cannot be used with revs"));
+                       break;
+               }
+
+               if (get_sha1_with_context(arg, 0, oid.hash, &oc)) {
+                       if (seen_dashdash)
+                               die(_("unable to resolve revision: %s"), arg);
+                       break;
+               }
+
+               object = parse_object_or_die(oid.hash, arg);
+               if (!seen_dashdash)
+                       verify_non_filename(prefix, arg);
+               add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
        }
 
+       /*
+        * Anything left over is presumed to be a path. But in the non-dashdash
+        * "do what I mean" case, we verify and complain when that isn't true.
+        */
+       if (!seen_dashdash) {
+               int j;
+               for (j = i; j < argc; j++)
+                       verify_filename(prefix, argv[j], j == i && allow_revs);
+       }
+
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_CWD |
+                      (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
+                      prefix, argv + i);
+       pathspec.max_depth = opt.max_depth;
+       pathspec.recursive = 1;
+
 #ifndef NO_PTHREADS
        if (list.nr || cached || show_in_pager)
                num_threads = 0;
@@ -1190,20 +1234,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        }
 #endif
 
-       /* The rest are paths */
-       if (!seen_dashdash) {
-               int j;
-               for (j = i; j < argc; j++)
-                       verify_filename(prefix, argv[j], j == i);
-       }
-
-       parse_pathspec(&pathspec, 0,
-                      PATHSPEC_PREFER_CWD |
-                      (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
-                      prefix, argv + i);
-       pathspec.max_depth = opt.max_depth;
-       pathspec.recursive = 1;
-
        if (recurse_submodules) {
                gitmodules_config();
                compile_submodule_options(&opt, &pathspec, cached, untracked,
@@ -1245,8 +1275,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 
        if (!use_index || untracked) {
                int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
-               if (list.nr)
-                       die(_("--no-index or --untracked cannot be used with revs."));
                hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
        } else if (0 <= opt_exclude) {
                die(_("--[no-]exclude-standard cannot be used for tracked contents."));
index 1d4d6a0..8a6acb0 100644 (file)
@@ -338,7 +338,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
 {
        int reinit;
        int exist_ok = flags & INIT_DB_EXIST_OK;
-       char *original_git_dir = real_pathdup(git_dir);
+       char *original_git_dir = real_pathdup(git_dir, 1);
 
        if (real_git_dir) {
                struct stat st;
@@ -489,7 +489,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
        if (real_git_dir && !is_absolute_path(real_git_dir))
-               real_git_dir = real_pathdup(real_git_dir);
+               real_git_dir = real_pathdup(real_git_dir, 1);
 
        if (argc == 1) {
                int mkdir_tried = 0;
@@ -560,7 +560,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                const char *git_dir_parent = strrchr(git_dir, '/');
                if (git_dir_parent) {
                        char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
-                       git_work_tree_cfg = real_pathdup(rel);
+                       git_work_tree_cfg = real_pathdup(rel, 1);
                        free(rel);
                }
                if (!git_work_tree_cfg)
index 55d20cc..281af8c 100644 (file)
@@ -989,8 +989,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
            open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
                return;
 
-       log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
-                               &need_8bit_cte);
+       log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte);
 
        for (i = 0; !need_8bit_cte && i < nr; i++) {
                const char *buf = get_commit_buffer(list[i], NULL);
@@ -1005,6 +1004,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        msg = body;
        pp.fmt = CMIT_FMT_EMAIL;
        pp.date_mode.type = DATE_RFC2822;
+       pp.rev = rev;
+       pp.print_email_subject = 1;
        pp_user_info(&pp, NULL, &sb, committer, encoding);
        pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
        pp_remainder(&pp, &msg, &sb, 0);
index b572a37..cfe2a79 100644 (file)
@@ -36,12 +36,12 @@ static const char * const merge_base_usage[] = {
 
 static struct commit *get_commit_reference(const char *arg)
 {
-       unsigned char revkey[20];
+       struct object_id revkey;
        struct commit *r;
 
-       if (get_sha1(arg, revkey))
+       if (get_oid(arg, &revkey))
                die("Not a valid object name %s", arg);
-       r = lookup_commit_reference(revkey);
+       r = lookup_commit_reference(revkey.hash);
        if (!r)
                die("Not a valid commit name %s", arg);
 
@@ -113,14 +113,14 @@ struct rev_collect {
        unsigned int initial : 1;
 };
 
-static void add_one_commit(unsigned char *sha1, struct rev_collect *revs)
+static void add_one_commit(struct object_id *oid, struct rev_collect *revs)
 {
        struct commit *commit;
 
-       if (is_null_sha1(sha1))
+       if (is_null_oid(oid))
                return;
 
-       commit = lookup_commit(sha1);
+       commit = lookup_commit(oid->hash);
        if (!commit ||
            (commit->object.flags & TMP_MARK) ||
            parse_commit(commit))
@@ -131,7 +131,7 @@ static void add_one_commit(unsigned char *sha1, struct rev_collect *revs)
        commit->object.flags |= TMP_MARK;
 }
 
-static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
                                  const char *ident, unsigned long timestamp,
                                  int tz, const char *message, void *cbdata)
 {
@@ -139,15 +139,15 @@ static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 
        if (revs->initial) {
                revs->initial = 0;
-               add_one_commit(osha1, revs);
+               add_one_commit(ooid, revs);
        }
-       add_one_commit(nsha1, revs);
+       add_one_commit(noid, revs);
        return 0;
 }
 
 static int handle_fork_point(int argc, const char **argv)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        char *refname;
        const char *commitname;
        struct rev_collect revs;
@@ -155,7 +155,7 @@ static int handle_fork_point(int argc, const char **argv)
        struct commit_list *bases;
        int i, ret = 0;
 
-       switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &refname)) {
+       switch (dwim_ref(argv[0], strlen(argv[0]), oid.hash, &refname)) {
        case 0:
                die("No such ref: '%s'", argv[0]);
        case 1:
@@ -165,16 +165,16 @@ static int handle_fork_point(int argc, const char **argv)
        }
 
        commitname = (argc == 2) ? argv[1] : "HEAD";
-       if (get_sha1(commitname, sha1))
+       if (get_oid(commitname, &oid))
                die("Not a valid object name: '%s'", commitname);
 
-       derived = lookup_commit_reference(sha1);
+       derived = lookup_commit_reference(oid.hash);
        memset(&revs, 0, sizeof(revs));
        revs.initial = 1;
        for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
 
-       if (!revs.nr && !get_sha1(refname, sha1))
-               add_one_commit(sha1, &revs);
+       if (!revs.nr && !get_oid(refname, &oid))
+               add_one_commit(&oid, &revs);
 
        for (i = 0; i < revs.nr; i++)
                revs.commit[i]->object.flags &= ~TMP_MARK;
index a96d4fb..7554b8d 100644 (file)
@@ -244,7 +244,7 @@ static void drop_save(void)
        unlink(git_path_merge_mode());
 }
 
-static int save_state(unsigned char *stash)
+static int save_state(struct object_id *stash)
 {
        int len;
        struct child_process cp = CHILD_PROCESS_INIT;
@@ -265,7 +265,7 @@ static int save_state(unsigned char *stash)
        else if (!len)          /* no changes */
                return -1;
        strbuf_setlen(&buffer, buffer.len-1);
-       if (get_sha1(buffer.buf, stash))
+       if (get_oid(buffer.buf, stash))
                die(_("not a valid object: %s"), buffer.buf);
        return 0;
 }
@@ -305,18 +305,18 @@ static void reset_hard(unsigned const char *sha1, int verbose)
                die(_("read-tree failed"));
 }
 
-static void restore_state(const unsigned char *head,
-                         const unsigned char *stash)
+static void restore_state(const struct object_id *head,
+                         const struct object_id *stash)
 {
        struct strbuf sb = STRBUF_INIT;
        const char *args[] = { "stash", "apply", NULL, NULL };
 
-       if (is_null_sha1(stash))
+       if (is_null_oid(stash))
                return;
 
-       reset_hard(head, 1);
+       reset_hard(head->hash, 1);
 
-       args[2] = sha1_to_hex(stash);
+       args[2] = oid_to_hex(stash);
 
        /*
         * It is OK to ignore error here, for example when there was
@@ -376,10 +376,10 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
 
 static void finish(struct commit *head_commit,
                   struct commit_list *remoteheads,
-                  const unsigned char *new_head, const char *msg)
+                  const struct object_id *new_head, const char *msg)
 {
        struct strbuf reflog_message = STRBUF_INIT;
-       const unsigned char *head = head_commit->object.oid.hash;
+       const struct object_id *head = &head_commit->object.oid;
 
        if (!msg)
                strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
@@ -397,7 +397,7 @@ static void finish(struct commit *head_commit,
                else {
                        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
                        update_ref(reflog_message.buf, "HEAD",
-                               new_head, head, 0,
+                               new_head->hash, head->hash, 0,
                                UPDATE_REFS_DIE_ON_ERR);
                        /*
                         * We ignore errors in 'gc --auto', since the
@@ -416,7 +416,7 @@ static void finish(struct commit *head_commit,
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
                diff_setup_done(&opts);
-               diff_tree_sha1(head, new_head, "", &opts);
+               diff_tree_sha1(head->hash, new_head->hash, "", &opts);
                diffcore_std(&opts);
                diff_flush(&opts);
        }
@@ -431,35 +431,35 @@ static void finish(struct commit *head_commit,
 static void merge_name(const char *remote, struct strbuf *msg)
 {
        struct commit *remote_head;
-       unsigned char branch_head[20];
+       struct object_id branch_head;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
        char *found_ref;
        int len, early;
 
-       strbuf_branchname(&bname, remote);
+       strbuf_branchname(&bname, remote, 0);
        remote = bname.buf;
 
-       memset(branch_head, 0, sizeof(branch_head));
+       oidclr(&branch_head);
        remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
-       if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
+       if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) {
                if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
-                                   sha1_to_hex(branch_head), remote);
+                                   oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
                if (starts_with(found_ref, "refs/tags/")) {
                        strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
-                                   sha1_to_hex(branch_head), remote);
+                                   oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
                if (starts_with(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
-                                   sha1_to_hex(branch_head), remote);
+                                   oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
        }
@@ -590,8 +590,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
        return git_diff_ui_config(k, v, cb);
 }
 
-static int read_tree_trivial(unsigned char *common, unsigned char *head,
-                            unsigned char *one)
+static int read_tree_trivial(struct object_id *common, struct object_id *head,
+                            struct object_id *one)
 {
        int i, nr_trees = 0;
        struct tree *trees[MAX_UNPACK_TREES];
@@ -606,13 +606,13 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head,
        opts.verbose_update = 1;
        opts.trivial_merges_only = 1;
        opts.merge = 1;
-       trees[nr_trees] = parse_tree_indirect(common);
+       trees[nr_trees] = parse_tree_indirect(common->hash);
        if (!trees[nr_trees++])
                return -1;
-       trees[nr_trees] = parse_tree_indirect(head);
+       trees[nr_trees] = parse_tree_indirect(head->hash);
        if (!trees[nr_trees++])
                return -1;
-       trees[nr_trees] = parse_tree_indirect(one);
+       trees[nr_trees] = parse_tree_indirect(one->hash);
        if (!trees[nr_trees++])
                return -1;
        opts.fn = threeway_merge;
@@ -626,9 +626,9 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head,
        return 0;
 }
 
-static void write_tree_trivial(unsigned char *sha1)
+static void write_tree_trivial(struct object_id *oid)
 {
-       if (write_cache_as_tree(sha1, 0, NULL))
+       if (write_cache_as_tree(oid->hash, 0, NULL))
                die(_("git write-tree failed to write a tree"));
 }
 
@@ -781,7 +781,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
 
 static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
 {
-       unsigned char result_tree[20], result_commit[20];
+       struct object_id result_tree, result_commit;
        struct commit_list *parents, **pptr = &parents;
        static struct lock_file lock;
 
@@ -792,15 +792,15 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
                return error(_("Unable to write index."));
        rollback_lock_file(&lock);
 
-       write_tree_trivial(result_tree);
+       write_tree_trivial(&result_tree);
        printf(_("Wonderful.\n"));
        pptr = commit_list_append(head, pptr);
        pptr = commit_list_append(remoteheads->item, pptr);
        prepare_to_commit(remoteheads);
-       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
-                       result_commit, NULL, sign_commit))
+       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents,
+                       result_commit.hash, NULL, sign_commit))
                die(_("failed to write commit object"));
-       finish(head, remoteheads, result_commit, "In-index merge");
+       finish(head, remoteheads, &result_commit, "In-index merge");
        drop_save();
        return 0;
 }
@@ -809,12 +809,12 @@ static int finish_automerge(struct commit *head,
                            int head_subsumed,
                            struct commit_list *common,
                            struct commit_list *remoteheads,
-                           unsigned char *result_tree,
+                           struct object_id *result_tree,
                            const char *wt_strategy)
 {
        struct commit_list *parents = NULL;
        struct strbuf buf = STRBUF_INIT;
-       unsigned char result_commit[20];
+       struct object_id result_commit;
 
        free_commit_list(common);
        parents = remoteheads;
@@ -822,11 +822,11 @@ static int finish_automerge(struct commit *head,
                commit_list_insert(head, &parents);
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit(remoteheads);
-       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
-                       result_commit, NULL, sign_commit))
+       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree->hash, parents,
+                       result_commit.hash, NULL, sign_commit))
                die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
-       finish(head, remoteheads, result_commit, buf.buf);
+       finish(head, remoteheads, &result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
        return 0;
@@ -854,18 +854,18 @@ static int suggest_conflicts(void)
 }
 
 static struct commit *is_old_style_invocation(int argc, const char **argv,
-                                             const unsigned char *head)
+                                             const struct object_id *head)
 {
        struct commit *second_token = NULL;
        if (argc > 2) {
-               unsigned char second_sha1[20];
+               struct object_id second_oid;
 
-               if (get_sha1(argv[1], second_sha1))
+               if (get_oid(argv[1], &second_oid))
                        return NULL;
-               second_token = lookup_commit_reference_gently(second_sha1, 0);
+               second_token = lookup_commit_reference_gently(second_oid.hash, 0);
                if (!second_token)
                        die(_("'%s' is not a commit"), argv[1]);
-               if (hashcmp(second_token->object.oid.hash, head))
+               if (oidcmp(&second_token->object.oid, head))
                        return NULL;
        }
        return second_token;
@@ -1038,7 +1038,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
                die_errno(_("could not close '%s'"), filename);
 
        for (pos = 0; pos < merge_names->len; pos = npos) {
-               unsigned char sha1[20];
+               struct object_id oid;
                char *ptr;
                struct commit *commit;
 
@@ -1048,16 +1048,16 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
                else
                        npos = merge_names->len;
 
-               if (npos - pos < 40 + 2 ||
-                   get_sha1_hex(merge_names->buf + pos, sha1))
+               if (npos - pos < GIT_SHA1_HEXSZ + 2 ||
+                   get_oid_hex(merge_names->buf + pos, &oid))
                        commit = NULL; /* bad */
-               else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
+               else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2))
                        continue; /* not-for-merge */
                else {
-                       char saved = merge_names->buf[pos + 40];
-                       merge_names->buf[pos + 40] = '\0';
+                       char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ];
+                       merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0';
                        commit = get_merge_parent(merge_names->buf + pos);
-                       merge_names->buf[pos + 40] = saved;
+                       merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved;
                }
                if (!commit) {
                        if (ptr)
@@ -1117,9 +1117,7 @@ static struct commit_list *collect_parents(struct commit *head_commit,
 
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
-       unsigned char result_tree[20];
-       unsigned char stash[20];
-       unsigned char head_sha1[20];
+       struct object_id result_tree, stash, head_oid;
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
@@ -1138,13 +1136,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL);
+       branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
        if (branch && starts_with(branch, "refs/heads/"))
                branch += 11;
-       if (!branch || is_null_sha1(head_sha1))
+       if (!branch || is_null_oid(&head_oid))
                head_commit = NULL;
        else
-               head_commit = lookup_commit_or_die(head_sha1, "HEAD");
+               head_commit = lookup_commit_or_die(head_oid.hash, "HEAD");
 
        init_diff_ui_defaults();
        git_config(git_merge_config, NULL);
@@ -1242,7 +1240,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                 * to forbid "git merge" into a branch yet to be born.
                 * We do the same for "git pull".
                 */
-               unsigned char *remote_head_sha1;
+               struct object_id *remote_head_oid;
                if (squash)
                        die(_("Squash commit into empty head not supported yet"));
                if (fast_forward == FF_NO)
@@ -1254,9 +1252,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        die(_("%s - not something we can merge"), argv[0]);
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
-               remote_head_sha1 = remoteheads->item->object.oid.hash;
-               read_empty(remote_head_sha1, 0);
-               update_ref("initial pull", "HEAD", remote_head_sha1,
+               remote_head_oid = &remoteheads->item->object.oid;
+               read_empty(remote_head_oid->hash, 0);
+               update_ref("initial pull", "HEAD", remote_head_oid->hash,
                           NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
        }
@@ -1270,7 +1268,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * additional safety measure to check for it.
         */
        if (!have_message &&
-           is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) {
+           is_old_style_invocation(argc, argv, &head_commit->object.oid)) {
                warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
                strbuf_addstr(&merge_msg, argv[0]);
                head_arg = argv[1];
@@ -1422,7 +1420,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        goto done;
                }
 
-               finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf);
+               finish(head_commit, remoteheads, &commit->object.oid, msg.buf);
                drop_save();
                goto done;
        } else if (!remoteheads->next && common->next)
@@ -1441,9 +1439,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        /* See if it is really trivial. */
                        git_committer_info(IDENT_STRICT);
                        printf(_("Trying really trivial in-index merge...\n"));
-                       if (!read_tree_trivial(common->item->object.oid.hash,
-                                              head_commit->object.oid.hash,
-                                              remoteheads->item->object.oid.hash)) {
+                       if (!read_tree_trivial(&common->item->object.oid,
+                                              &head_commit->object.oid,
+                                              &remoteheads->item->object.oid)) {
                                ret = merge_trivial(head_commit, remoteheads);
                                goto done;
                        }
@@ -1495,14 +1493,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
            /*
             * Stash away the local changes so that we can try more than one.
             */
-           save_state(stash))
-               hashclr(stash);
+           save_state(&stash))
+               oidclr(&stash);
 
        for (i = 0; i < use_strategies_nr; i++) {
                int ret;
                if (i) {
                        printf(_("Rewinding the tree to pristine...\n"));
-                       restore_state(head_commit->object.oid.hash, stash);
+                       restore_state(&head_commit->object.oid, &stash);
                }
                if (use_strategies_nr != 1)
                        printf(_("Trying merge strategy %s...\n"),
@@ -1547,7 +1545,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                }
 
                /* Automerge succeeded. */
-               write_tree_trivial(result_tree);
+               write_tree_trivial(&result_tree);
                automerge_was_ok = 1;
                break;
        }
@@ -1559,7 +1557,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (automerge_was_ok) {
                ret = finish_automerge(head_commit, head_subsumed,
                                       common, remoteheads,
-                                      result_tree, wt_strategy);
+                                      &result_tree, wt_strategy);
                goto done;
        }
 
@@ -1568,7 +1566,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * it up.
         */
        if (!best_strategy) {
-               restore_state(head_commit->object.oid.hash, stash);
+               restore_state(&head_commit->object.oid, &stash);
                if (use_strategies_nr > 1)
                        fprintf(stderr,
                                _("No merge strategy handled the merge.\n"));
@@ -1581,7 +1579,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                ; /* We already have its result in the working tree. */
        else {
                printf(_("Rewinding the tree to pristine...\n"));
-               restore_state(head_commit->object.oid.hash, stash);
+               restore_state(&head_commit->object.oid, &stash);
                printf(_("Using the %s to prepare resolving by hand.\n"),
                        best_strategy);
                try_merge_strategy(best_strategy, common, remoteheads,
index cd89d48..8bdc3ea 100644 (file)
@@ -108,7 +108,8 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
 struct name_ref_data {
        int tags_only;
        int name_only;
-       const char *ref_filter;
+       struct string_list ref_filters;
+       struct string_list exclude_filters;
 };
 
 static struct tip_table {
@@ -150,18 +151,49 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
        if (data->tags_only && !starts_with(path, "refs/tags/"))
                return 0;
 
-       if (data->ref_filter) {
-               switch (subpath_matches(path, data->ref_filter)) {
-               case -1: /* did not match */
-                       return 0;
-               case 0:  /* matched fully */
-                       break;
-               default: /* matched subpath */
-                       can_abbreviate_output = 1;
-                       break;
+       if (data->exclude_filters.nr) {
+               struct string_list_item *item;
+
+               for_each_string_list_item(item, &data->exclude_filters) {
+                       if (subpath_matches(path, item->string) >= 0)
+                               return 0;
                }
        }
 
+       if (data->ref_filters.nr) {
+               struct string_list_item *item;
+               int matched = 0;
+
+               /* See if any of the patterns match. */
+               for_each_string_list_item(item, &data->ref_filters) {
+                       /*
+                        * Check all patterns even after finding a match, so
+                        * that we can see if a match with a subpath exists.
+                        * When a user asked for 'refs/tags/v*' and 'v1.*',
+                        * both of which match, the user is showing her
+                        * willingness to accept a shortened output by having
+                        * the 'v1.*' in the acceptable refnames, so we
+                        * shouldn't stop when seeing 'refs/tags/v1.4' matches
+                        * 'refs/tags/v*'.  We should show it as 'v1.4'.
+                        */
+                       switch (subpath_matches(path, item->string)) {
+                       case -1: /* did not match */
+                               break;
+                       case 0: /* matched fully */
+                               matched = 1;
+                               break;
+                       default: /* matched subpath */
+                               matched = 1;
+                               can_abbreviate_output = 1;
+                               break;
+                       }
+               }
+
+               /* If none of the patterns matched, stop now */
+               if (!matched)
+                       return 0;
+       }
+
        add_to_tip_table(oid->hash, path, can_abbreviate_output);
 
        while (o && o->type == OBJ_TAG) {
@@ -306,12 +338,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
        struct object_array revs = OBJECT_ARRAY_INIT;
        int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
-       struct name_ref_data data = { 0, 0, NULL };
+       struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
        struct option opts[] = {
                OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
                OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
-               OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
+               OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
                                   N_("only use refs matching <pattern>")),
+               OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"),
+                                  N_("ignore refs matching <pattern>")),
                OPT_GROUP(""),
                OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
                OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
index 5248a9b..0513f74 100644 (file)
@@