Merge branch 'ew/http-alternates-as-redirects-warning'
authorJunio C Hamano <gitster@pobox.com>
Tue, 14 Mar 2017 22:23:19 +0000 (15:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 14 Mar 2017 22:23:19 +0000 (15:23 -0700)
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.

* ew/http-alternates-as-redirects-warning:
http: release strbuf on disabled alternates
http: inform about alternates-as-redirects behavior

183 files changed:
.gitignore
.mailmap
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.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/branch.c
builtin/check-attr.c
builtin/checkout.c
builtin/clean.c
builtin/describe.c
builtin/fetch-pack.c
builtin/gc.c
builtin/grep.c
builtin/init-db.c
builtin/log.c
builtin/merge.c
builtin/name-rev.c
builtin/notes.c
builtin/pack-objects.c
builtin/rebase--helper.c [new file with mode: 0644]
builtin/receive-pack.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-ref.c
cache.h
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
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
preload-index.c
pretty.c
progress.c
ref-filter.c
ref-filter.h
refs.c
refs.h
refs/files-backend.c
refs/refs-internal.h
remote.c
remote.h
revision.c
setup.c
sha1_file.c
sha1_name.c
strbuf.c
strbuf.h
submodule.c
t/gitweb-lib.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/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
xdiff/xemit.c
index b1020b875fc006c81ed92ffb93e73876487c7c57..833ef3b0b783b8180d0dad1ce336713bddf09b26 100644 (file)
 /git-read-tree
 /git-rebase
 /git-rebase--am
+/git-rebase--helper
 /git-rebase--interactive
 /git-rebase--merge
 /git-receive-pack
index ab59b2fac613013ffcf5e041fc2c942f3f97cb1f..e06526a49300eb78e5cac3504902d6a3b1053324 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>
diff --git a/Documentation/RelNotes/2.13.0.txt b/Documentation/RelNotes/2.13.0.txt
new file mode 100644 (file)
index 0000000..8d5e887
--- /dev/null
@@ -0,0 +1,255 @@
+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).
+
+
+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).
+
+ * 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 015346c4173c5f29b33445280ae849f3fcfffaa3..5e5c2ae5fb378dc3226e58a4a0df7c3c63472f60 100644 (file)
@@ -350,16 +350,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 +1405,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 +1928,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 +1966,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
index cf5262622f6a7f82b3989cbcb4ee1ebd4652b220..706916c94c1d930ef1fe476c7a16c140aeef7420 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 d2a7ff56e860b92c4dd346a0ea1eb2eb9f7fde79..231105cff48d9c109b2001623c67d51690c595ba 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 d91ddbd5fe490527bd3032789d0bf3f2eef93f93..89cc0f48deef7152d59f4d4665ff3179ed8e4315 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 8ddb207409df99825b561be42234e9c23707287d..631cbd840a08eb29add1e900ac9272992669a75c 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 28d46cc03b217c156769f6123c7b3e3351eabce4..092f1bcf9f89f124a2d3c80eadeab5b4a63db941 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 4f8f20a3606201b1835affc523ff2828549d0285..25dcdcc2890f0a6814c0000c3c8970e02c0717f8 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 e4ac448ff565a0951444d0b1b98b2d5f86f01a9f..8755f3af7bcd16b564fdd01563e36d3cc4103c07 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 0a09698c033fb2e1cdf68aa33eed4f109a034d5f..6e4bb022043faeddd363c899872e7de96c926c1c 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 abe13f3bedaf55dbefc03d26b934351107ff50af..111e1be6f54f73f25ef6715a17410e461724519a 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 446209e2062df8d0b1c99a25eaaf4dc6f15bce3c..1cab703f73611260845ef5e9926fe377e729b934 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 dbc91f98ffbee198bed7d2eeeb2758687c17adf8..9dee7bef35fb1df1afa2fcabebe22043c66b6fba 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 ca28fb8e2a07bebde63c896939211fbec69e93e0..e8e68f528cf2fa5705e7cedce8d3d807fa20057d 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 7241e968935505cdb9d507ab910b35178fcbf3bd..91c02b8c856abf2c24af9ea23f0ee01710b8cd12 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`.
 
index 2e9e344cd757674a655579c6c58569ff19a6f22e..70191d06b69ed307c6ce1d8f46c158b61eb6545f 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 725065ef2d7b6b3c7c6c29a434a8996a7e1ae559..ba873657cf2f9d139d2ddb7c19e6865cc159a64d 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 8acc72ebb8bbe956324f730612070678a9a9e188..e05d0cddefe16194f87b00918f057bf4fb481855 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 aa895da4a5fbc5e83c87bfa98683157d3d43fa0e..df0941d456b0fa1ab3be4f459f73edd3145ffb32 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 e0b66c1220a7d22ef5b8eb960a4e2e389a71f884..a53d093ca1666e6f84f229f94a3e4b7b1ed6c899 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 46bc6d077c474374a92532af3692b76854803da7..c0a60f315811c788f3431b5757c38ffcea487386 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 8ad29e61a950c2cb1f33779977a33c63ae0df33b..fc9320e59ecac99fc4d3f6234c74f89aab4dd45b 100644 (file)
@@ -386,8 +386,10 @@ Glob magic is incompatible with literal magic.
 
 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 260266867768a549e58e540eaa95d3bbe422e5c0..e7cbb7c13adf2f7ea3b15279a17f1a06da859cbc 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 27bd701c0d6862f38c700fbfc02c24b135bd6c0e..36768b479e16c9456982230f078d98513a72c37f 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 6a208e92bf30c849028268b5fca54b902f671bbd..817d1cf7ef2a2a99ab11e5a88a27dfea673fec79 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.12.0
+DEF_VER=v2.12.GIT
 
 LF='
 '
index 8e4081e0619f8a9927a3a4c544048f576a194fd0..ed68700acb19bfe1f24e3143200baf112dd5a7dd 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
index c0cd5580ea48cf4818781bb8d4d7c2d9d5d63ae6..f17af66a97c8097ab91f074478c4a5cb90425725 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 d09c3d51093ac9e4da65e8a127b17ac9023520b5..125bf78f3b9ed2f1444e1873ed02cce9f0f4c5b8 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 2f0c26e0e2cbee88aa671f3960d21720e4307aba..b02e068aa347db683b00246061b48bfafabbc1c0 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 01751e574bc10c1211d0253d215917ecc4ce82dd..60b889198656f42a1d073503c4da5886bf0a0526 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 1fcf042b87c8a39fb967e07c5acb81c0c364d136..5493bff224a98361811c1e9fa88bb16efbf85d22 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)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_lock(&map->mutex);
+#endif
+}
+
+static inline void hashmap_unlock(struct attr_hashmap *map)
 {
-       unsigned val = 0, c;
+#ifndef NO_PTHREADS
+       pthread_mutex_unlock(&map->mutex);
+#endif
+}
 
-       while (namelen--) {
-               c = *name++;
-               val = ((val << 7) | (val >> 22)) ^ c;
+/*
+ * 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 struct git_attr *git_attr_internal(const char *name, int len)
+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);
+}
+
+/*
+ * 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,173 @@ 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_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 +676,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 +738,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 +751,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 +803,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 +813,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 +828,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 +898,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 +912,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);
 
-               strbuf_release(&pathbuf);
+               /* reset the pathbuf to not include "/.gitattributes" */
+               strbuf_setlen(&pathbuf, len);
+
+               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 +994,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 +1012,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 +1095,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 8b08d33af84ebbb376a69d85f3db6c03eeb78a63..48ab3e1c2f1c3eaf11e32f345fc833fc160c5b02 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,57 @@ 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_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 8e63c40d274d7693b0c41e702cbe9ed03b1d12ae..30808cadf7613d73cae2e4caaedd922ba9a48ca0 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 b955d4f316c799f7788896806302bfd4dbcf3ec4..5c12036b0206fe250f50bea30578785b090f9ee4 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 3103eb9add31e08600f9fa158303417e36d3b229..b07788558c0e76a701b571f73c0462dcf2abcc63 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 67f80519dafc4875434437a34e438453bc2c78c4..9e4a89816d5fbde0ed0b3bebff8f90f70dd4480d 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 31fb60578f6caacfb376fe84938dda9432dcfc5a..f7a7a971fbe762a4fb691e786e86f681f2bbd7c1 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 9d30f55b0b83cfc09294e2cb7cabdc9aa72e2508..291fe90de3f2f76db3add485e7dcf368b9ed35f4 100644 (file)
@@ -28,6 +28,7 @@ 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
 };
 
@@ -37,11 +38,11 @@ static unsigned char head_sha1[20];
 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,
@@ -190,17 +191,20 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        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"));
@@ -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);
 
@@ -251,7 +255,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                        goto next;
                }
 
-               if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
+               if (delete_ref(NULL, name, is_null_sha1(sha1) ? NULL : sha1,
                               REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
@@ -280,221 +284,98 @@ 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) {
+               strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
+               strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
+               strbuf_addf(&local, " %%(objectname:short=7) ");
+
+               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
-                       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;
-       }
-
-       c = ' ';
-       if (current) {
-               c = '*';
-               color = BRANCH_COLOR_CURRENT;
-       }
+                       strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
 
-       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,
+               strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s%%(if)%%(symref)%%(then) -> %%(symref:short)"
+                           "%%(else) %%(objectname:short=7) %%(contents:subject)%%(end)",
+                           branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
                            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);
        } 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;
 
-               skip_prefix(it->refname, "refs/heads/", &desc);
-               skip_prefix(it->refname, "refs/remotes/", &desc);
-               w = utf8_strwidth(desc);
+       strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf);
 
-               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 +387,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 +474,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 +534,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 +576,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;
@@ -749,7 +649,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 53a5a18c1681f5f13f6ddd4fd4d7d30c0ea14ce6..4d01ca0c8ba0ba2a1d2c209b766e19d9ca9c8ac0 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 f174f503033ea7f1274fc8f09c49c0ae378c9eee..81f07c3ef271db08e36ee7a89b1d128d099ecb96 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 d6bc3aaaea0dfdbac2386d54f4ff2d25dbccf7e6..d861f836a29bf98d248f27632a8c47711a5da3a5 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 01490a157efc5d85626833c0b1c25b75fb7ee469..6769446e1f57537879fd1b411f83ba7393bad338 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;
 
@@ -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)) {
@@ -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 cfe9e447c27469407ab439bacb540a4d0b68d4b4..2a1c1c213f4092a274c562324c4d1820c58a6be2 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 331f2192607a4f7657750f4592193e6c0b8cea55..a2b9e8924e1a3e3a537d86c4a3da1fa1967c1863 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";
 
@@ -76,10 +78,28 @@ static void git_config_date_string(const char *key, const char **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)
@@ -113,6 +133,8 @@ static void gc_config(void)
        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_date_string("gc.logexpiry", &gc_log_expire);
+
        git_config(git_default_config, NULL);
 }
 
@@ -290,19 +312,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");
 
-       ret = strbuf_read_file(&sb, git_path("gc.log"), 0);
+       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, 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 +386,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 +488,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 2c727ef499c0fa7b44c0cf1890cc44427dc8921f..9304c33e75051baa65ee02d1daa03c361e25339e 100644 (file)
@@ -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_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, sha1, &oc)) {
+                       if (seen_dashdash)
+                               die(_("unable to resolve revision: %s"), arg);
+                       break;
+               }
+
+               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);
        }
 
+       /*
+        * 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 1d4d6a0078919636dfc0ffefa2aec1699a9827f1..8a6acb0ec69330f7aae20d34fb5c5733f0058c67 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 55d20cc2d88ab03d96f0476b222be3b2f6832ed9..281af8c1ecb04c7be05d9a17a1d5f3bd43420c85 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 a96d4fb501bf1441b52a313313b8c04f3187e4d9..848a29855611d22bf19f3740a577a2b312294257 100644 (file)
@@ -438,7 +438,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        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));
index cd89d48b65e8f70819f73ef6e2cfd0634752b284..8bdc3eaa6fa4472d8352e7de10ef9d816e8ca83f 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 5248a9bad8d4d18c8ff0f145e8049606baa8849a..4b492abd417e3520d7ab98725f359087e9fcddd7 100644 (file)
@@ -681,9 +681,9 @@ static int merge_abort(struct notes_merge_options *o)
         * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
         */
 
-       if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
+       if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0))
                ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL"));
-       if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
+       if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF))
                ret += error(_("failed to delete ref NOTES_MERGE_REF"));
        if (notes_merge_abort(o))
                ret += error(_("failed to remove 'git notes merge' worktree"));
index 8841f8b366b4cee57d13a9086071351f849ddbba..f294dcffa90aa3d8c916e448a4de905fa6363d26 100644 (file)
@@ -894,24 +894,15 @@ static void write_pack_file(void)
                        written, nr_result);
 }
 
-static void setup_delta_attr_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_delta;
-
-       if (!attr_delta)
-               attr_delta = git_attr("delta");
-
-       check[0].attr = attr_delta;
-}
-
 static int no_try_delta(const char *path)
 {
-       struct git_attr_check check[1];
+       static struct attr_check *check;
 
-       setup_delta_attr_check(check);
-       if (git_check_attr(path, ARRAY_SIZE(check), check))
+       if (!check)
+               check = attr_check_initl("delta", NULL);
+       if (git_check_attr(path, check))
                return 0;
-       if (ATTR_FALSE(check->value))
+       if (ATTR_FALSE(check->items[0].value))
                return 1;
        return 0;
 }
@@ -1539,6 +1530,8 @@ static int pack_offset_sort(const void *_a, const void *_b)
  *   2. Updating our size/type to the non-delta representation. These were
  *      either not recorded initially (size) or overwritten with the delta type
  *      (type) when check_object() decided to reuse the delta.
+ *
+ *   3. Resetting our delta depth, as we are now a base object.
  */
 static void drop_reused_delta(struct object_entry *entry)
 {
@@ -1552,6 +1545,7 @@ static void drop_reused_delta(struct object_entry *entry)
                        p = &(*p)->delta_sibling;
        }
        entry->delta = NULL;
+       entry->depth = 0;
 
        oi.sizep = &entry->size;
        oi.typep = &entry->type;
@@ -1570,39 +1564,123 @@ static void drop_reused_delta(struct object_entry *entry)
  * Follow the chain of deltas from this entry onward, throwing away any links
  * that cause us to hit a cycle (as determined by the DFS state flags in
  * the entries).
+ *
+ * We also detect too-long reused chains that would violate our --depth
+ * limit.
  */
 static void break_delta_chains(struct object_entry *entry)
 {
-       /* If it's not a delta, it can't be part of a cycle. */
-       if (!entry->delta) {
-               entry->dfs_state = DFS_DONE;
-               return;
-       }
+       /*
+        * The actual depth of each object we will write is stored as an int,
+        * as it cannot exceed our int "depth" limit. But before we break
+        * changes based no that limit, we may potentially go as deep as the
+        * number of objects, which is elsewhere bounded to a uint32_t.
+        */
+       uint32_t total_depth;
+       struct object_entry *cur, *next;
+
+       for (cur = entry, total_depth = 0;
+            cur;
+            cur = cur->delta, total_depth++) {
+               if (cur->dfs_state == DFS_DONE) {
+                       /*
+                        * We've already seen this object and know it isn't
+                        * part of a cycle. We do need to append its depth
+                        * to our count.
+                        */
+                       total_depth += cur->depth;
+                       break;
+               }
+
+               /*
+                * We break cycles before looping, so an ACTIVE state (or any
+                * other cruft which made its way into the state variable)
+                * is a bug.
+                */
+               if (cur->dfs_state != DFS_NONE)
+                       die("BUG: confusing delta dfs state in first pass: %d",
+                           cur->dfs_state);
 
-       switch (entry->dfs_state) {
-       case DFS_NONE:
                /*
-                * This is the first time we've seen the object. We mark it as
-                * part of the active potential cycle and recurse.
+                * Now we know this is the first time we've seen the object. If
+                * it's not a delta, we're done traversing, but we'll mark it
+                * done to save time on future traversals.
                 */
-               entry->dfs_state = DFS_ACTIVE;
-               break_delta_chains(entry->delta);
-               entry->dfs_state = DFS_DONE;
-               break;
+               if (!cur->delta) {
+                       cur->dfs_state = DFS_DONE;
+                       break;
+               }
 
-       case DFS_DONE:
-               /* object already examined, and not part of a cycle */
-               break;
+               /*
+                * Mark ourselves as active and see if the next step causes
+                * us to cycle to another active object. It's important to do
+                * this _before_ we loop, because it impacts where we make the
+                * cut, and thus how our total_depth counter works.
+                * E.g., We may see a partial loop like:
+                *
+                *   A -> B -> C -> D -> B
+                *
+                * Cutting B->C breaks the cycle. But now the depth of A is
+                * only 1, and our total_depth counter is at 3. The size of the
+                * error is always one less than the size of the cycle we
+                * broke. Commits C and D were "lost" from A's chain.
+                *
+                * If we instead cut D->B, then the depth of A is correct at 3.
+                * We keep all commits in the chain that we examined.
+                */
+               cur->dfs_state = DFS_ACTIVE;
+               if (cur->delta->dfs_state == DFS_ACTIVE) {
+                       drop_reused_delta(cur);
+                       cur->dfs_state = DFS_DONE;
+                       break;
+               }
+       }
+
+       /*
+        * And now that we've gone all the way to the bottom of the chain, we
+        * need to clear the active flags and set the depth fields as
+        * appropriate. Unlike the loop above, which can quit when it drops a
+        * delta, we need to keep going to look for more depth cuts. So we need
+        * an extra "next" pointer to keep going after we reset cur->delta.
+        */
+       for (cur = entry; cur; cur = next) {
+               next = cur->delta;
+
+               /*
+                * We should have a chain of zero or more ACTIVE states down to
+                * a final DONE. We can quit after the DONE, because either it
+                * has no bases, or we've already handled them in a previous
+                * call.
+                */
+               if (cur->dfs_state == DFS_DONE)
+                       break;
+               else if (cur->dfs_state != DFS_ACTIVE)
+                       die("BUG: confusing delta dfs state in second pass: %d",
+                           cur->dfs_state);
 
-       case DFS_ACTIVE:
                /*
-                * We found a cycle that needs broken. It would be correct to
-                * break any link in the chain, but it's convenient to
-                * break this one.
+                * If the total_depth is more than depth, then we need to snip
+                * the chain into two or more smaller chains that don't exceed
+                * the maximum depth. Most of the resulting chains will contain
+                * (depth + 1) entries (i.e., depth deltas plus one base), and
+                * the last chain (i.e., the one containing entry) will contain
+                * whatever entries are left over, namely
+                * (total_depth % (depth + 1)) of them.
+                *
+                * Since we are iterating towards decreasing depth, we need to
+                * decrement total_depth as we go, and we need to write to the
+                * entry what its final depth will be after all of the
+                * snipping. Since we're snipping into chains of length (depth
+                * + 1) entries, the final depth of an entry will be its
+                * original depth modulo (depth + 1). Any time we encounter an
+                * entry whose final depth is supposed to be zero, we snip it
+                * from its delta base, thereby making it so.
                 */
-               drop_reused_delta(entry);
-               entry->dfs_state = DFS_DONE;
-               break;
+               cur->depth = (total_depth--) % (depth + 1);
+               if (!cur->depth)
+                       drop_reused_delta(cur);
+
+               cur->dfs_state = DFS_DONE;
        }
 }
 
diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
new file mode 100644 (file)
index 0000000..ca1ebb2
--- /dev/null
@@ -0,0 +1,40 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "sequencer.h"
+
+static const char * const builtin_rebase_helper_usage[] = {
+       N_("git rebase--helper [<options>]"),
+       NULL
+};
+
+int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
+{
+       struct replay_opts opts = REPLAY_OPTS_INIT;
+       enum {
+               CONTINUE = 1, ABORT
+       } command = 0;
+       struct option options[] = {
+               OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
+               OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
+                               CONTINUE),
+               OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
+                               ABORT),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+
+       opts.action = REPLAY_INTERACTIVE_REBASE;
+       opts.allow_ff = 1;
+       opts.allow_empty = 1;
+
+       argc = parse_options(argc, argv, NULL, options,
+                       builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
+
+       if (command == CONTINUE && argc == 1)
+               return !!sequencer_continue(&opts);
+       if (command == ABORT && argc == 1)
+               return !!sequencer_remove_state(&opts);
+       usage_with_options(builtin_rebase_helper_usage, options);
+}
index 1dbb8a069225be1e9d9fe27ad4b83a8bd66ca511..9ed8fbbfad71daf45381dbccf0aebe4a5b9af03d 100644 (file)
@@ -21,6 +21,7 @@
 #include "sigchain.h"
 #include "fsck.h"
 #include "tmp-objdir.h"
+#include "oidset.h"
 
 static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@ -250,8 +251,9 @@ static void show_ref(const char *path, const unsigned char *sha1)
 }
 
 static int show_ref_cb(const char *path_full, const struct object_id *oid,
-                      int flag, void *unused)
+                      int flag, void *data)
 {
+       struct oidset *seen = data;
        const char *path = strip_namespace(path_full);
 
        if (ref_is_hidden(path, path_full))
@@ -260,37 +262,38 @@ static int show_ref_cb(const char *path_full, const struct object_id *oid,
        /*
         * Advertise refs outside our current namespace as ".have"
         * refs, so that the client can use them to minimize data
-        * transfer but will otherwise ignore them. This happens to
-        * cover ".have" that are thrown in by add_one_alternate_ref()
-        * to mark histories that are complete in our alternates as
-        * well.
+        * transfer but will otherwise ignore them.
         */
-       if (!path)
+       if (!path) {
+               if (oidset_insert(seen, oid))
+                       return 0;
                path = ".have";
+       } else {
+               oidset_insert(seen, oid);
+       }
        show_ref(path, oid->hash);
        return 0;
 }
 
-static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+static void show_one_alternate_ref(const char *refname,
+                                  const struct object_id *oid,
+                                  void *data)
 {
-       show_ref(".have", sha1);
-       return 0;
-}
+       struct oidset *seen = data;
 
-static void collect_one_alternate_ref(const struct ref *ref, void *data)
-{
-       struct sha1_array *sa = data;
-       sha1_array_append(sa, ref->old_oid.hash);
+       if (oidset_insert(seen, oid))
+               return;
+
+       show_ref(".have", oid->hash);
 }
 
 static void write_head_info(void)
 {
-       struct sha1_array sa = SHA1_ARRAY_INIT;
+       static struct oidset seen = OIDSET_INIT;
 
-       for_each_alternate_ref(collect_one_alternate_ref, &sa);
-       sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
-       sha1_array_clear(&sa);
-       for_each_ref(show_ref_cb, NULL);
+       for_each_ref(show_ref_cb, &seen);
+       for_each_alternate_ref(show_one_alternate_ref, &seen);
+       oidset_clear(&seen);
        if (!sent_capabilities)
                show_ref("capabilities^{}", null_sha1);
 
index 5339ed6ad17bb0c83e05d3e60d654c4f0632ee50..addf97ad29343b2328adce2c27ccea92aee6af7a 100644 (file)
@@ -691,7 +691,7 @@ static int mv(int argc, const char **argv)
                read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
-               if (delete_ref(item->string, NULL, REF_NODEREF))
+               if (delete_ref(NULL, item->string, NULL, REF_NODEREF))
                        die(_("deleting '%s' failed"), item->string);
        }
        for (i = 0; i < remote_branches.nr; i++) {
@@ -769,7 +769,9 @@ static int rm(int argc, const char **argv)
                                strbuf_reset(&buf);
                                strbuf_addf(&buf, "branch.%s.%s",
                                                item->string, *k);
-                               git_config_set(buf.buf, NULL);
+                               result = git_config_set_gently(buf.buf, NULL);
+                               if (result && result != CONFIG_NOTHING_SET)
+                                       die(_("could not unset '%s'"), buf.buf);
                        }
                }
        }
@@ -1248,7 +1250,7 @@ static int set_head(int argc, const char **argv)
                        head_name = xstrdup(states.heads.items[0].string);
                free_remote_ref_states(&states);
        } else if (opt_d && !opt_a && argc == 1) {
-               if (delete_ref(buf.buf, NULL, REF_NODEREF))
+               if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF))
                        result |= error(_("Could not delete %s"), buf.buf);
        } else
                usage_with_options(builtin_remote_sethead_usage, options);
index b58c714cb8935efd08c81c8ebfd8922e1704c8f4..226d0f95236f299a1506beefb5babf1cf52574f0 100644 (file)
@@ -121,7 +121,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
 static int delete_replace_ref(const char *name, const char *ref,
                              const unsigned char *sha1)
 {
-       if (delete_ref(ref, sha1, 0))
+       if (delete_ref(NULL, ref, sha1, 0))
                return 1;
        printf("Deleted replace ref '%s'\n", name);
        return 0;
index 8ab915bfcb71ae5d5f4bd1bebe7fd3c8725780b2..fc3b906c47bbcbefdc35b178016c031e46c06691 100644 (file)
@@ -256,7 +256,7 @@ static int reset_refs(const char *rev, const struct object_id *oid)
                update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
                           UPDATE_REFS_MSG_ON_ERR);
        } else if (old_orig)
-               delete_ref("ORIG_HEAD", old_orig->hash, 0);
+               delete_ref(NULL, "ORIG_HEAD", old_orig->hash, 0);
        set_reflog_message(&msg, "updating HEAD", rev);
        update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0,
                                       UPDATE_REFS_MSG_ON_ERR);
index ff13e59e1dbd200b6e7cf7d1c508b620b19e8cc7..e08677e559f443f8a290f200f691ef770ecde3ef 100644 (file)
@@ -545,6 +545,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
        unsigned int flags = 0;
        const char *name = NULL;
        struct object_context unused;
+       struct strbuf buf = STRBUF_INIT;
 
        if (argc > 1 && !strcmp("--parseopt", argv[1]))
                return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -599,7 +600,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                if (!strcmp(arg, "--git-path")) {
                        if (!argv[i + 1])
                                die("--git-path requires an argument");
-                       puts(git_path("%s", argv[i + 1]));
+                       strbuf_reset(&buf);
+                       puts(relative_path(git_path("%s", argv[i + 1]),
+                                          prefix, &buf));
                        i++;
                        continue;
                }
@@ -802,17 +805,27 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                putchar('\n');
                                continue;
                        }
-                       if (!strcmp(arg, "--git-dir")) {
+                       if (!strcmp(arg, "--git-dir") ||
+                           !strcmp(arg, "--absolute-git-dir")) {
                                const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
                                char *cwd;
                                int len;
-                               if (gitdir) {
-                                       puts(gitdir);
-                                       continue;
-                               }
-                               if (!prefix) {
-                                       puts(".git");
-                                       continue;
+                               if (arg[2] == 'g') {    /* --git-dir */
+                                       if (gitdir) {
+                                               puts(gitdir);
+                                               continue;
+                                       }
+                                       if (!prefix) {
+                                               puts(".git");
+                                               continue;
+                                       }
+                               } else {                /* --absolute-git-dir */
+                                       if (!gitdir && !prefix)
+                                               gitdir = ".git";
+                                       if (gitdir) {
+                                               puts(real_path(gitdir));
+                                               continue;
+                                       }
                                }
                                cwd = xgetcwd();
                                len = strlen(cwd);
@@ -821,8 +834,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--git-common-dir")) {
-                               const char *pfx = prefix ? prefix : "";
-                               puts(prefix_filename(pfx, strlen(pfx), get_git_common_dir()));
+                               strbuf_reset(&buf);
+                               puts(relative_path(get_git_common_dir(),
+                                                  prefix, &buf));
                                continue;
                        }
                        if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -845,7 +859,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                        die(_("Could not read the index"));
                                if (the_index.split_index) {
                                        const unsigned char *sha1 = the_index.split_index->base_sha1;
-                                       puts(git_path("sharedindex.%s", sha1_to_hex(sha1)));
+                                       const char *path = git_path("sharedindex.%s", sha1_to_hex(sha1));
+                                       strbuf_reset(&buf);
+                                       puts(relative_path(path, prefix, &buf));
                                }
                                continue;
                        }
@@ -897,6 +913,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                        continue;
                verify_filename(prefix, arg, 1);
        }
+       strbuf_release(&buf);
        if (verify) {
                if (revs_count == 1) {
                        show_rev(type, sha1, name);
index c9585d475d90c41554465f870e530c16ab93199f..f78bb4818d7a5254d7c3c2c34d1f370cd3aa9327 100644 (file)
@@ -148,7 +148,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 
        ctx.fmt = CMIT_FMT_USERFORMAT;
        ctx.abbrev = log->abbrev;
-       ctx.subject = "";
+       ctx.print_email_subject = 1;
        ctx.after_subject = "";
        ctx.date_mode.type = DATE_NORMAL;
        ctx.output_encoding = get_log_output_encoding();
index 974f3403abe76288dc98797e3ceac21a070828ec..19756595d57f27c35c72449d03a95cefd55aef15 100644 (file)
@@ -275,8 +275,7 @@ static void show_one_commit(struct commit *commit, int no_name)
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
                pretty_str = pretty.buf;
        }
-       if (starts_with(pretty_str, "[PATCH] "))
-               pretty_str += 8;
+       skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
 
        if (!no_name) {
                if (name && name->head_name) {
@@ -470,18 +469,14 @@ static void snarf_refs(int head, int remotes)
        }
 }
 
-static int rev_is_head(char *head, int headlen, char *name,
+static int rev_is_head(const char *head, const char *name,
                       unsigned char *head_sha1, unsigned char *sha1)
 {
-       if ((!head[0]) ||
-           (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+       if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
                return 0;
-       if (starts_with(head, "refs/heads/"))
-               head += 11;
-       if (starts_with(name, "refs/heads/"))
-               name += 11;
-       else if (starts_with(name, "heads/"))
-               name += 6;
+       skip_prefix(head, "refs/heads/", &head);
+       if (!skip_prefix(name, "refs/heads/", &name))
+               skip_prefix(name, "heads/", &name);
        return !strcmp(head, name);
 }
 
@@ -620,9 +615,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        int all_heads = 0, all_remotes = 0;
        int all_mask, all_revs;
        enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
-       char head[128];
-       const char *head_p;
-       int head_len;
+       char *head;
        struct object_id head_oid;
        int merge_base = 0;
        int independent = 0;
@@ -787,32 +780,24 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        snarf_refs(all_heads, all_remotes);
        }
 
-       head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
-                                   head_oid.hash, NULL);
-       if (head_p) {
-               head_len = strlen(head_p);
-               memcpy(head, head_p, head_len + 1);
-       }
-       else {
-               head_len = 0;
-               head[0] = 0;
-       }
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING,
+                             head_oid.hash, NULL);
 
-       if (with_current_branch && head_p) {
+       if (with_current_branch && head) {
                int has_head = 0;
                for (i = 0; !has_head && i < ref_name_cnt; i++) {
                        /* We are only interested in adding the branch
                         * HEAD points at.
                         */
                        if (rev_is_head(head,
-                                       head_len,
                                        ref_name[i],
                                        head_oid.hash, NULL))
                                has_head++;
                }
                if (!has_head) {
-                       int offset = starts_with(head, "refs/heads/") ? 11 : 0;
-                       append_one_rev(head + offset);
+                       const char *name = head;
+                       skip_prefix(name, "refs/heads/", &name);
+                       append_one_rev(name);
                }
        }
 
@@ -866,7 +851,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                for (i = 0; i < num_rev; i++) {
                        int j;
                        int is_head = rev_is_head(head,
-                                                 head_len,
                                                  ref_name[i],
                                                  head_oid.hash,
                                                  rev[i]->object.oid.hash);
index 899dc334e323a53f3e8a2981a1f8140f55f136a9..15a5430c0028f45ff6265a821ff85aa9d8445230 100644 (file)
@@ -356,12 +356,10 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
                        strbuf_addf(&remotesb, "remote.%s.url", remote);
                        free(remote);
 
-                       if (git_config_get_string(remotesb.buf, &remoteurl))
-                               /*
-                                * The repository is its own
-                                * authoritative upstream
-                                */
+                       if (git_config_get_string(remotesb.buf, &remoteurl)) {
+                               warning(_("could not lookup configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf);
                                remoteurl = xgetcwd();
+                       }
                        relurl = relative_url(remoteurl, url, NULL);
                        strbuf_release(&remotesb);
                        free(remoteurl);
index 96eed944683a45c33fa5d4a05414ae56406fafca..70addef158b347488366f6c5787d326c3657adcd 100644 (file)
@@ -58,7 +58,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                        die("Cannot delete %s, not a symbolic ref", argv[0]);
                if (!strcmp(argv[0], "HEAD"))
                        die("deleting '%s' is not allowed", argv[0]);
-               return delete_ref(argv[0], NULL, REF_NODEREF);
+               return delete_ref(NULL, argv[0], NULL, REF_NODEREF);
        }
 
        switch (argc) {
index e40c4a96763a371b104a5bf47e6d839ed93e8ef5..ad29be692384454ffd2f35f134d49c227ae1a67b 100644 (file)
@@ -45,11 +45,11 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con
        if (!format) {
                if (filter->lines) {
                        to_free = xstrfmt("%s %%(contents:lines=%d)",
-                                         "%(align:15)%(refname:strip=2)%(end)",
+                                         "%(align:15)%(refname:lstrip=2)%(end)",
                                          filter->lines);
                        format = to_free;
                } else
-                       format = "%(refname:strip=2)";
+                       format = "%(refname:lstrip=2)";
        }
 
        verify_ref_format(format);
@@ -97,7 +97,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
 static int delete_tag(const char *name, const char *ref,
                      const unsigned char *sha1, const void *cb_data)
 {
-       if (delete_ref(ref, sha1, 0))
+       if (delete_ref(NULL, ref, sha1, 0))
                return 1;
        printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
        return 0;
@@ -302,6 +302,54 @@ static void create_tag(const unsigned char *object, const char *tag,
        }
 }
 
+static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
+{
+       enum object_type type;
+       struct commit *c;
+       char *buf;
+       unsigned long size;
+       int subject_len = 0;
+       const char *subject_start;
+
+       char *rla = getenv("GIT_REFLOG_ACTION");
+       if (rla) {
+               strbuf_addstr(sb, rla);
+       } else {
+               strbuf_addstr(sb, _("tag: tagging "));
+               strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV);
+       }
+
+       strbuf_addstr(sb, " (");
+       type = sha1_object_info(sha1, NULL);
+       switch (type) {
+       default:
+               strbuf_addstr(sb, _("object of unknown type"));
+               break;
+       case OBJ_COMMIT:
+               if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) {
+                       subject_len = find_commit_subject(buf, &subject_start);
+                       strbuf_insert(sb, sb->len, subject_start, subject_len);
+               } else {
+                       strbuf_addstr(sb, _("commit object"));
+               }
+               free(buf);
+
+               if ((c = lookup_commit_reference(sha1)) != NULL)
+                       strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
+               break;
+       case OBJ_TREE:
+               strbuf_addstr(sb, _("tree object"));
+               break;
+       case OBJ_BLOB:
+               strbuf_addstr(sb, _("blob object"));
+               break;
+       case OBJ_TAG:
+               strbuf_addstr(sb, _("other tag object"));
+               break;
+       }
+       strbuf_addch(sb, ')');
+}
+
 struct msg_arg {
        int given;
        struct strbuf buf;
@@ -335,6 +383,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf ref = STRBUF_INIT;
+       struct strbuf reflog_msg = STRBUF_INIT;
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
        struct create_tag_options opt;
@@ -389,6 +438,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
+       setup_ref_filter_porcelain_msg();
+
        git_config(git_tag_config, sorting_tail);
 
        memset(&opt, 0, sizeof(opt));
@@ -494,6 +545,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
 
+       create_reflog_msg(object, &reflog_msg);
+
        if (create_tag_object) {
                if (force_sign_annotate && !annotate)
                        opt.sign = 1;
@@ -504,7 +557,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (!transaction ||
            ref_transaction_update(transaction, ref.buf, object, prev,
                                   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
-                                  NULL, &err) ||
+                                  reflog_msg.buf, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
        ref_transaction_free(transaction);
@@ -514,5 +567,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        strbuf_release(&err);
        strbuf_release(&buf);
        strbuf_release(&ref);
+       strbuf_release(&reflog_msg);
        return 0;
 }
index 7f30d3a76f69e58ae3cdbbe05c5ebdce6c8a120d..0b2ecf41aeda427e75eb7fe36388e7d0d91d5413 100644 (file)
@@ -433,7 +433,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                 * For purposes of backwards compatibility, we treat
                 * NULL_SHA1 as "don't care" here:
                 */
-               return delete_ref(refname,
+               return delete_ref(msg, refname,
                                  (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL,
                                  flags);
        else
diff --git a/cache.h b/cache.h
index 61fc86e6d7199518555632a0b0b584471b9083a8..c958269715cc73e218604f6c3b82f547d376b050 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1045,9 +1045,6 @@ static inline int is_empty_tree_oid(const struct object_id *oid)
        return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
 }
 
-
-int git_mkstemp(char *path, size_t n, const char *template);
-
 /* set default permissions by passing mode arguments to open(2) */
 int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
 int git_mkstemp_mode(char *pattern, int mode);
@@ -1072,8 +1069,9 @@ int adjust_shared_perm(const char *path);
 
 /*
  * Create the directory containing the named path, using care to be
- * somewhat safe against races.  Return one of the scld_error values
- * to indicate success/failure.
+ * somewhat safe against races. Return one of the scld_error values to
+ * indicate success/failure. On error, set errno to describe the
+ * problem.
  *
  * SCLD_VANISHED indicates that one of the ancestor directories of the
  * path existed at one point during the function call and then
@@ -1097,6 +1095,49 @@ enum scld_error {
 enum scld_error safe_create_leading_directories(char *path);
 enum scld_error safe_create_leading_directories_const(const char *path);
 
+/*
+ * Callback function for raceproof_create_file(). This function is
+ * expected to do something that makes dirname(path) permanent despite
+ * the fact that other processes might be cleaning up empty
+ * directories at the same time. Usually it will create a file named
+ * path, but alternatively it could create another file in that
+ * directory, or even chdir() into that directory. The function should
+ * return 0 if the action was completed successfully. On error, it
+ * should return a nonzero result and set errno.
+ * raceproof_create_file() treats two errno values specially:
+ *
+ * - ENOENT -- dirname(path) does not exist. In this case,
+ *             raceproof_create_file() tries creating dirname(path)
+ *             (and any parent directories, if necessary) and calls
+ *             the function again.
+ *
+ * - EISDIR -- the file already exists and is a directory. In this
+ *             case, raceproof_create_file() removes the directory if
+ *             it is empty (and recursively any empty directories that
+ *             it contains) and calls the function again.
+ *
+ * Any other errno causes raceproof_create_file() to fail with the
+ * callback's return value and errno.
+ *
+ * Obviously, this function should be OK with being called again if it
+ * fails with ENOENT or EISDIR. In other scenarios it will not be
+ * called again.
+ */
+typedef int create_file_fn(const char *path, void *cb);
+
+/*
+ * Create a file in dirname(path) by calling fn, creating leading
+ * directories if necessary. Retry a few times in case we are racing
+ * with another process that is trying to clean up the directory that
+ * contains path. See the documentation for create_file_fn for more
+ * details.
+ *
+ * Return the value and set the errno that resulted from the most
+ * recent call of fn. fn is always called at least once, and will be
+ * called more than once if it returns ENOENT or EISDIR.
+ */
+int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
+
 int mkdir_in_gitdir(const char *path);
 extern char *expand_user_path(const char *path);
 const char *enter_repo(const char *path, int strict);
@@ -1109,7 +1150,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
                      int die_on_error);
 const char *real_path(const char *path);
 const char *real_path_if_valid(const char *path);
-char *real_pathdup(const char *path);
+char *real_pathdup(const char *path, int die_on_error);
 const char *absolute_path(const char *path);
 char *absolute_pathdup(const char *path);
 const char *remove_leading_path(const char *in, const char *prefix);
@@ -1319,7 +1360,37 @@ extern char *oid_to_hex_r(char *out, const struct object_id *oid);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern char *oid_to_hex(const struct object_id *oid);  /* same static buffer as sha1_to_hex */
 
-extern int interpret_branch_name(const char *str, int len, struct strbuf *);
+/*
+ * This reads short-hand syntax that not only evaluates to a commit
+ * object name, but also can act as if the end user spelled the name
+ * of the branch from the command line.
+ *
+ * - "@{-N}" finds the name of the Nth previous branch we were on, and
+ *   places the name of the branch in the given buf and returns the
+ *   number of characters parsed if successful.
+ *
+ * - "<branch>@{upstream}" finds the name of the other ref that
+ *   <branch> is configured to merge with (missing <branch> defaults
+ *   to the current branch), and places the name of the branch in the
+ *   given buf and returns the number of characters parsed if
+ *   successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ *
+ * If "allowed" is non-zero, it is a treated as a bitfield of allowable
+ * expansions: local branches ("refs/heads/"), remote branches
+ * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
+ * allowed, even ones to refs outside of those namespaces.
+ */
+#define INTERPRET_BRANCH_LOCAL (1<<0)
+#define INTERPRET_BRANCH_REMOTE (1<<1)
+#define INTERPRET_BRANCH_HEAD (1<<2)
+extern int interpret_branch_name(const char *str, int len, struct strbuf *,
+                                unsigned allowed);
 extern int get_oid_mb(const char *str, struct object_id *oid);
 
 extern int validate_headref(const char *ref);
@@ -1819,8 +1890,11 @@ extern int git_config_include(const char *name, const char *value, void *data);
  *
  * (i.e., what gets handed to a config_fn_t). The caller provides the section;
  * we return -1 if it does not match, 0 otherwise. The subsection and key
- * out-parameters are filled by the function (and subsection is NULL if it is
+ * out-parameters are filled by the function (and *subsection is NULL if it is
  * missing).
+ *
+ * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
+ * there is no subsection at all.
  */
 extern int parse_config_key(const char *var,
                            const char *section,
index 2cf85158b4899b664a3cbae8d0777f5a9e473318..73c78c2b80c1a21e83f1942347a76fb76511a0d3 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -415,8 +415,7 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
                p++;
        if (*p) {
                p = skip_blank_lines(p + 2);
-               for (eol = p; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
+               eol = strchrnul(p, '\n');
        } else
                eol = p;
 
@@ -1308,11 +1307,11 @@ void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)
 
 static inline int standard_header_field(const char *field, size_t len)
 {
-       return ((len == 4 && !memcmp(field, "tree ", 5)) ||
-               (len == 6 && !memcmp(field, "parent ", 7)) ||
-               (len == 6 && !memcmp(field, "author ", 7)) ||
-               (len == 9 && !memcmp(field, "committer ", 10)) ||
-               (len == 8 && !memcmp(field, "encoding ", 9)));
+       return ((len == 4 && !memcmp(field, "tree", 4)) ||
+               (len == 6 && !memcmp(field, "parent", 6)) ||
+               (len == 6 && !memcmp(field, "author", 6)) ||
+               (len == 9 && !memcmp(field, "committer", 9)) ||
+               (len == 8 && !memcmp(field, "encoding", 8)));
 }
 
 static int excluded_header_field(const char *field, size_t len, const char **exclude)
@@ -1322,8 +1321,7 @@ static int excluded_header_field(const char *field, size_t len, const char **exc
 
        while (*exclude) {
                size_t xlen = strlen(*exclude);
-               if (len == xlen &&
-                   !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+               if (len == xlen && !memcmp(field, *exclude, xlen))
                        return 1;
                exclude++;
        }
@@ -1354,12 +1352,11 @@ static struct commit_extra_header *read_commit_extra_header_lines(
                strbuf_reset(&buf);
                it = NULL;
 
-               eof = strchr(line, ' ');
-               if (next <= eof)
+               eof = memchr(line, ' ', next - line);
+               if (!eof)
                        eof = next;
-
-               if (standard_header_field(line, eof - line) ||
-                   excluded_header_field(line, eof - line, exclude))
+               else if (standard_header_field(line, eof - line) ||
+                        excluded_header_field(line, eof - line, exclude))
                        continue;
 
                it = xcalloc(1, sizeof(*it));
index 9c12abb91110151e3a982c4d8baf8753d0382441..528272ac9bacebf263c3c9a33bad4dd81bddd4d8 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -142,21 +142,24 @@ static inline int cmit_fmt_is_mail(enum cmit_fmt fmt)
        return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD);
 }
 
+struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
+
 struct pretty_print_context {
        /*
         * Callers should tweak these to change the behavior of pp_* functions.
         */
        enum cmit_fmt fmt;
        int abbrev;
-       const char *subject;
        const char *after_subject;
        int preserve_subject;
        struct date_mode date_mode;
        unsigned date_mode_explicit:1;
+       int print_email_subject;
        int expand_tabs_in_log;
        int need_8bit_cte;
        char *notes_message;
        struct reflog_walk_info *reflog_info;
+       struct rev_info *rev;
        const char *output_encoding;
        struct string_list *mailmap;
        int color;
@@ -175,7 +178,6 @@ struct userformat_want {
 };
 
 extern int has_non_ascii(const char *text);
-struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 extern const char *logmsg_reencode(const struct commit *commit,
                                   char **commit_encoding,
                                   const char *output_encoding);
index c654f95551c33d6b346ae7fe43a6cac987dd6b75..6a689007e7ce3fe08f148e8b82c0a1c618c513a5 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "exec_cmd.h"
+#include "attr.h"
 
 /*
  * Many parts of Git have subprograms communicate via pipe, expect the
@@ -33,6 +34,8 @@ int main(int argc, const char **argv)
 
        git_setup_gettext();
 
+       attr_start();
+
        git_extract_argv0_path(argv[0]);
 
        restore_sigpipe_to_default();
index c6b874a7bf763851b425d526f704966aaad129b2..0e9e1ebefc0a6771de7bf20615e41176ed986179 100644 (file)
--- a/config.c
+++ b/config.c
@@ -201,11 +201,105 @@ void git_config_push_parameter(const char *text)
        strbuf_release(&env);
 }
 
+static inline int iskeychar(int c)
+{
+       return isalnum(c) || c == '-';
+}
+
+/*
+ * Auxiliary function to sanity-check and split the key into the section
+ * identifier and variable name.
+ *
+ * Returns 0 on success, -1 when there is an invalid character in the key and
+ * -2 if there is no section name in the key.
+ *
+ * store_key - pointer to char* which will hold a copy of the key with
+ *             lowercase section and variable name
+ * baselen - pointer to int which will hold the length of the
+ *           section + subsection part, can be NULL
+ */
+static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
+{
+       int i, dot, baselen;
+       const char *last_dot = strrchr(key, '.');
+
+       /*
+        * Since "key" actually contains the section name and the real
+        * key name separated by a dot, we have to know where the dot is.
+        */
+
+       if (last_dot == NULL || last_dot == key) {
+               if (!quiet)
+                       error("key does not contain a section: %s", key);
+               return -CONFIG_NO_SECTION_OR_NAME;
+       }
+
+       if (!last_dot[1]) {
+               if (!quiet)
+                       error("key does not contain variable name: %s", key);
+               return -CONFIG_NO_SECTION_OR_NAME;
+       }
+
+       baselen = last_dot - key;
+       if (baselen_)
+               *baselen_ = baselen;
+
+       /*
+        * Validate the key and while at it, lower case it for matching.
+        */
+       if (store_key)
+               *store_key = xmallocz(strlen(key));
+
+       dot = 0;
+       for (i = 0; key[i]; i++) {
+               unsigned char c = key[i];
+               if (c == '.')
+                       dot = 1;
+               /* Leave the extended basename untouched.. */
+               if (!dot || i > baselen) {
+                       if (!iskeychar(c) ||
+                           (i == baselen + 1 && !isalpha(c))) {
+                               if (!quiet)
+                                       error("invalid key: %s", key);
+                               goto out_free_ret_1;
+                       }
+                       c = tolower(c);
+               } else if (c == '\n') {
+                       if (!quiet)
+                               error("invalid key (newline): %s", key);
+                       goto out_free_ret_1;
+               }
+               if (store_key)
+                       (*store_key)[i] = c;
+       }
+
+       return 0;
+
+out_free_ret_1:
+       if (store_key) {
+               free(*store_key);
+               *store_key = NULL;
+       }
+       return -CONFIG_INVALID_KEY;
+}
+
+int git_config_parse_key(const char *key, char **store_key, int *baselen)
+{
+       return git_config_parse_key_1(key, store_key, baselen, 0);
+}
+
+int git_config_key_is_valid(const char *key)
+{
+       return !git_config_parse_key_1(key, NULL, NULL, 1);
+}
+
 int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
 {
        const char *value;
+       char *canonical_name;
        struct strbuf **pair;
+       int ret;
 
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
@@ -223,13 +317,15 @@ int git_config_parse_parameter(const char *text,
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
-       strbuf_tolower(pair[0]);
-       if (fn(pair[0]->buf, value, data) < 0) {
-               strbuf_list_free(pair);
-               return -1;
+
+       if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
+               ret = -1;
+       } else {
+               ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
+               free(canonical_name);
        }
        strbuf_list_free(pair);
-       return 0;
+       return ret;
 }
 
 int git_config_from_parameters(config_fn_t fn, void *data)
@@ -356,11 +452,6 @@ static char *parse_value(void)
        }
 }
 
-static inline int iskeychar(int c)
-{
-       return isalnum(c) || c == '-';
-}
-
 static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 {
        int c;
@@ -1989,93 +2080,6 @@ void git_config_set(const char *key, const char *value)
        git_config_set_multivar(key, value, NULL, 0);
 }
 
-/*
- * Auxiliary function to sanity-check and split the key into the section
- * identifier and variable name.
- *
- * Returns 0 on success, -1 when there is an invalid character in the key and
- * -2 if there is no section name in the key.
- *
- * store_key - pointer to char* which will hold a copy of the key with
- *             lowercase section and variable name
- * baselen - pointer to int which will hold the length of the
- *           section + subsection part, can be NULL
- */
-static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
-{
-       int i, dot, baselen;
-       const char *last_dot = strrchr(key, '.');
-
-       /*
-        * Since "key" actually contains the section name and the real
-        * key name separated by a dot, we have to know where the dot is.
-        */
-
-       if (last_dot == NULL || last_dot == key) {
-               if (!quiet)
-                       error("key does not contain a section: %s", key);
-               return -CONFIG_NO_SECTION_OR_NAME;
-       }
-
-       if (!last_dot[1]) {
-               if (!quiet)
-                       error("key does not contain variable name: %s", key);
-               return -CONFIG_NO_SECTION_OR_NAME;
-       }
-
-       baselen = last_dot - key;
-       if (baselen_)
-               *baselen_ = baselen;
-
-       /*
-        * Validate the key and while at it, lower case it for matching.
-        */
-       if (store_key)
-               *store_key = xmallocz(strlen(key));
-
-       dot = 0;
-       for (i = 0; key[i]; i++) {
-               unsigned char c = key[i];
-               if (c == '.')
-                       dot = 1;
-               /* Leave the extended basename untouched.. */
-               if (!dot || i > baselen) {
-                       if (!iskeychar(c) ||
-                           (i == baselen + 1 && !isalpha(c))) {
-                               if (!quiet)
-                                       error("invalid key: %s", key);
-                               goto out_free_ret_1;
-                       }
-                       c = tolower(c);
-               } else if (c == '\n') {
-                       if (!quiet)
-                               error("invalid key (newline): %s", key);
-                       goto out_free_ret_1;
-               }
-               if (store_key)
-                       (*store_key)[i] = c;
-       }
-
-       return 0;
-
-out_free_ret_1:
-       if (store_key) {
-               free(*store_key);
-               *store_key = NULL;
-       }
-       return -CONFIG_INVALID_KEY;
-}
-
-int git_config_parse_key(const char *key, char **store_key, int *baselen)
-{
-       return git_config_parse_key_1(key, store_key, baselen, 0);
-}
-
-int git_config_key_is_valid(const char *key)
-{
-       return !git_config_parse_key_1(key, NULL, NULL, 1);
-}
-
 /*
  * If value==NULL, unset in (remove from) config,
  * if value_regex!=NULL, disregard key/value pairs where value does not match.
@@ -2536,11 +2540,10 @@ int parse_config_key(const char *var,
                     const char **subsection, int *subsection_len,
                     const char **key)
 {
-       int section_len = strlen(section);
        const char *dot;
 
        /* Does it start with "section." ? */
-       if (!starts_with(var, section) || var[section_len] != '.')
+       if (!skip_prefix(var, section, &var) || *var != '.')
                return -1;
 
        /*
@@ -2552,12 +2555,16 @@ int parse_config_key(const char *var,
        *key = dot + 1;
 
        /* Did we have a subsection at all? */
-       if (dot == var + section_len) {
-               *subsection = NULL;
-               *subsection_len = 0;
+       if (dot == var) {
+               if (subsection) {
+                       *subsection = NULL;
+                       *subsection_len = 0;
+               }
        }
        else {
-               *subsection = var + section_len + 1;
+               if (!subsection)
+                       return -1;
+               *subsection = var + 1;
                *subsection_len = dot - *subsection;
        }
 
index 447f36ac2e31dd4d11e90f326b114a78fdba8df0..399fe192719072691ebdbd7954fd8bd6cc12b96e 100644 (file)
@@ -27,7 +27,6 @@ endif
 ifeq ($(uname_S),Linux)
        HAVE_ALLOCA_H = YesPlease
        NO_STRLCPY = YesPlease
-       NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
        HAVE_DEV_TTY = YesPlease
@@ -41,7 +40,6 @@ endif
 ifeq ($(uname_S),GNU/kFreeBSD)
        HAVE_ALLOCA_H = YesPlease
        NO_STRLCPY = YesPlease
-       NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
@@ -55,7 +53,6 @@ ifeq ($(uname_S),UnixWare)
        SHELL_PATH = /usr/local/bin/bash
        NO_IPV6 = YesPlease
        NO_HSTRERROR = YesPlease
-       NO_MKSTEMPS = YesPlease
        BASIC_CFLAGS += -Kthread
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
@@ -79,7 +76,6 @@ ifeq ($(uname_S),SCO_SV)
        SHELL_PATH = /usr/bin/bash
        NO_IPV6 = YesPlease
        NO_HSTRERROR = YesPlease
-       NO_MKSTEMPS = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
        NO_STRCASESTR = YesPlease
@@ -122,7 +118,6 @@ ifeq ($(uname_S),SunOS)
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
        NO_MSGFMT_EXTENDED_OPTIONS = YesPlease
        HAVE_DEV_TTY = YesPlease
@@ -168,7 +163,6 @@ ifeq ($(uname_O),Cygwin)
                NO_D_TYPE_IN_DIRENT = YesPlease
                NO_STRCASESTR = YesPlease
                NO_MEMMEM = YesPlease
-               NO_MKSTEMPS = YesPlease
                NO_SYMLINK_HEAD = YesPlease
                NO_IPV6 = YesPlease
                OLD_ICONV = UnfortunatelyYes
@@ -233,7 +227,6 @@ ifeq ($(uname_S),NetBSD)
        BASIC_CFLAGS += -I/usr/pkg/include
        BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
        USE_ST_TIMESPEC = YesPlease
-       NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        HAVE_BSD_SYSCTL = YesPlease
 endif
@@ -242,7 +235,6 @@ ifeq ($(uname_S),AIX)
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_STRLCPY = YesPlease
        NO_NSEC = YesPlease
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
@@ -263,7 +255,6 @@ ifeq ($(uname_S),GNU)
        # GNU/Hurd
        HAVE_ALLOCA_H = YesPlease
        NO_STRLCPY = YesPlease
-       NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
 endif
@@ -272,7 +263,6 @@ ifeq ($(uname_S),IRIX)
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_MKDTEMP = YesPlease
        # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads
        # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set),
@@ -291,7 +281,6 @@ ifeq ($(uname_S),IRIX64)
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_MKDTEMP = YesPlease
        # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads
        # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set),
@@ -311,7 +300,6 @@ ifeq ($(uname_S),HP-UX)
        NO_SETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_STRLCPY = YesPlease
        NO_MKDTEMP = YesPlease
        NO_UNSETENV = YesPlease
@@ -352,7 +340,6 @@ ifeq ($(uname_S),Windows)
        NO_ICONV = YesPlease
        NO_STRTOUMAX = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_MKSTEMPS = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
        NO_SVN_TESTS = YesPlease
        RUNTIME_PREFIX = YesPlease
@@ -402,7 +389,6 @@ ifeq ($(uname_S),Interix)
        NO_MKDTEMP = YesPlease
        NO_STRTOUMAX = YesPlease
        NO_NSEC = YesPlease
-       NO_MKSTEMPS = YesPlease
        ifeq ($(uname_R),3.5)
                NO_INET_NTOP = YesPlease
                NO_INET_PTON = YesPlease
@@ -461,7 +447,6 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_MKSTEMPS = YesPlease
        # Currently libiconv-1.9.1.
        OLD_ICONV = UnfortunatelyYes
        NO_REGEX = YesPlease
@@ -503,7 +488,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NEEDS_LIBICONV = YesPlease
        NO_STRTOUMAX = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
        RUNTIME_PREFIX = YesPlease
@@ -515,7 +499,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
        NO_PYTHON = YesPlease
-       BLK_SHA1 = YesPlease
        ETAGS_TARGET = ETAGS
        NO_INET_PTON = YesPlease
        NO_INET_NTOP = YesPlease
@@ -584,7 +567,6 @@ ifeq ($(uname_S),QNX)
        NO_ICONV = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_MKSTEMPS = YesPlease
        NO_NSEC = YesPlease
        NO_PTHREADS = YesPlease
        NO_R_TO_GCC_LINKER = YesPlease
index 0b15f04b1089e48f20353cf068d7d37587b8d966..128165529fd70ac889ed0fb5098e4290893b593d 100644 (file)
@@ -1050,12 +1050,6 @@ GIT_CHECK_FUNC(mkdtemp,
 [NO_MKDTEMP=YesPlease])
 GIT_CONF_SUBST([NO_MKDTEMP])
 #
-# Define NO_MKSTEMPS if you don't have mkstemps in the C library.
-GIT_CHECK_FUNC(mkstemps,
-[NO_MKSTEMPS=],
-[NO_MKSTEMPS=YesPlease])
-GIT_CONF_SUBST([NO_MKSTEMPS])
-#
 # Define NO_INITGROUPS if you don't have initgroups in the C library.
 GIT_CHECK_FUNC(initgroups,
 [NO_INITGROUPS=],
index 8cb93b0720d9a33d8fc0cddb21c3d1c1a789da96..7d65c1c73634363e85a652619cc8705223bc70cb 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -691,6 +691,68 @@ static const char *get_ssh_command(void)
        return NULL;
 }
 
+static int override_ssh_variant(int *port_option, int *needs_batch)
+{
+       char *variant;
+
+       variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT"));
+       if (!variant &&
+           git_config_get_string("ssh.variant", &variant))
+               return 0;
+
+       if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) {
+               *port_option = 'P';
+               *needs_batch = 0;
+       } else if (!strcmp(variant, "tortoiseplink")) {
+               *port_option = 'P';
+               *needs_batch = 1;
+       } else {
+               *port_option = 'p';
+               *needs_batch = 0;
+       }
+       free(variant);
+       return 1;
+}
+
+static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
+                              int *port_option, int *needs_batch)
+{
+       const char *variant;
+       char *p = NULL;
+
+       if (override_ssh_variant(port_option, needs_batch))
+               return;
+
+       if (!is_cmdline) {
+               p = xstrdup(ssh_command);
+               variant = basename(p);
+       } else {
+               const char **ssh_argv;
+
+               p = xstrdup(ssh_command);
+               if (split_cmdline(p, &ssh_argv)) {
+                       variant = basename((char *)ssh_argv[0]);
+                       /*
+                        * At this point, variant points into the buffer
+                        * referenced by p, hence we do not need ssh_argv
+                        * any longer.
+                        */
+                       free(ssh_argv);
+               } else
+                       return;
+       }
+
+       if (!strcasecmp(variant, "plink") ||
+           !strcasecmp(variant, "plink.exe"))
+               *port_option = 'P';
+       else if (!strcasecmp(variant, "tortoiseplink") ||
+                !strcasecmp(variant, "tortoiseplink.exe")) {
+               *port_option = 'P';
+               *needs_batch = 1;
+       }
+       free(p);
+}
+
 /*
  * This returns a dummy child_process if the transport protocol does not
  * need fork(2), or a struct child_process object if it does.  Once done,
@@ -769,7 +831,8 @@ struct child_process *git_connect(int fd[2], const char *url,
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
                        const char *ssh;
-                       int putty = 0, tortoiseplink = 0;
+                       int needs_batch = 0;
+                       int port_option = 'p';
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -792,10 +855,10 @@ struct child_process *git_connect(int fd[2], const char *url,
                        }
 
                        ssh = get_ssh_command();
-                       if (!ssh) {
-                               const char *base;
-                               char *ssh_dup;
-
+                       if (ssh)
+                               handle_ssh_variant(ssh, 1, &port_option,
+                                                  &needs_batch);
+                       else {
                                /*
                                 * GIT_SSH is the no-shell version of
                                 * GIT_SSH_COMMAND (and must remain so for
@@ -806,17 +869,10 @@ struct child_process *git_connect(int fd[2], const char *url,
                                ssh = getenv("GIT_SSH");
                                if (!ssh)
                                        ssh = "ssh";
-
-                               ssh_dup = xstrdup(ssh);
-                               base = basename(ssh_dup);
-
-                               tortoiseplink = !strcasecmp(base, "tortoiseplink") ||
-                                       !strcasecmp(base, "tortoiseplink.exe");
-                               putty = tortoiseplink ||
-                                       !strcasecmp(base, "plink") ||
-                                       !strcasecmp(base, "plink.exe");
-
-                               free(ssh_dup);
+                               else
+                                       handle_ssh_variant(ssh, 0,
+                                                          &port_option,
+                                                          &needs_batch);
                        }
 
                        argv_array_push(&conn->args, ssh);
@@ -824,11 +880,11 @@ struct child_process *git_connect(int fd[2], const char *url,
                                argv_array_push(&conn->args, "-4");
                        else if (flags & CONNECT_IPV6)
                                argv_array_push(&conn->args, "-6");
-                       if (tortoiseplink)
+                       if (needs_batch)
                                argv_array_push(&conn->args, "-batch");
                        if (port) {
-                               /* P is for PuTTY, p is for OpenSSH */
-                               argv_array_push(&conn->args, putty ? "-P" : "-p");
+                               argv_array_pushf(&conn->args,
+                                                "-%c", port_option);
                                argv_array_push(&conn->args, port);
                        }
                        argv_array_push(&conn->args, ssh_host);
index 2d7f25d99f0754e82e2d0615ebd42b02d8fe5a66..4ba98b7eaff268c6fc9f2f984449fd4d9e1bfd4b 100644 (file)
@@ -24,3 +24,19 @@ expression n;
 @@
 - memcpy(dst, src, n * sizeof(T));
 + COPY_ARRAY(dst, src, n);
+
+@@
+type T;
+T *ptr;
+expression n;
+@@
+- ptr = xmalloc(n * sizeof(*ptr));
++ ALLOC_ARRAY(ptr, n);
+
+@@
+type T;
+T *ptr;
+expression n;
+@@
+- ptr = xmalloc(n * sizeof(T));
++ ALLOC_ARRAY(ptr, n);
index 63995f22ff29f717a360f300e626ddea91d59fa2..1d580e49b0ca3304ad7b3be62664df47ea84757d 100644 (file)
@@ -38,3 +38,9 @@ expression E1, E2, E3;
 @@
 - strbuf_addstr(E1, find_unique_abbrev(E2, E3));
 + strbuf_add_unique_abbrev(E1, E2, E3);
+
+@@
+expression E1, E2;
+@@
+- strbuf_addstr(E1, real_path(E2));
++ strbuf_add_real_path(E1, E2);
index 41ee52991d0c1b1fa2a6021133cffecd3ca9deb0..fc32286a43cdece9ea468c4a07c2a8e3d585f1b5 100644 (file)
@@ -34,21 +34,41 @@ case "$COMP_WORDBREAKS" in
 *)   COMP_WORDBREAKS="$COMP_WORDBREAKS:"
 esac
 
+# Discovers the path to the git repository taking any '--git-dir=<path>' and
+# '-C <path>' options into account and stores it in the $__git_repo_path
+# variable.
+__git_find_repo_path ()
+{
+       if [ -n "$__git_repo_path" ]; then
+               # we already know where it is
+               return
+       fi
+
+       if [ -n "${__git_C_args-}" ]; then
+               __git_repo_path="$(git "${__git_C_args[@]}" \
+                       ${__git_dir:+--git-dir="$__git_dir"} \
+                       rev-parse --absolute-git-dir 2>/dev/null)"
+       elif [ -n "${__git_dir-}" ]; then
+               test -d "$__git_dir" &&
+               __git_repo_path="$__git_dir"
+       elif [ -n "${GIT_DIR-}" ]; then
+               test -d "${GIT_DIR-}" &&
+               __git_repo_path="$GIT_DIR"
+       elif [ -d .git ]; then
+               __git_repo_path=.git
+       else
+               __git_repo_path="$(git rev-parse --git-dir 2>/dev/null)"
+       fi
+}
+
+# Deprecated: use __git_find_repo_path() and $__git_repo_path instead
 # __gitdir accepts 0 or 1 arguments (i.e., location)
 # returns location of .git repo
 __gitdir ()
 {
        if [ -z "${1-}" ]; then
-               if [ -n "${__git_dir-}" ]; then
-                       echo "$__git_dir"
-               elif [ -n "${GIT_DIR-}" ]; then
-                       test -d "${GIT_DIR-}" || return 1
-                       echo "$GIT_DIR"
-               elif [ -d .git ]; then
-                       echo .git
-               else
-                       git rev-parse --git-dir 2>/dev/null
-               fi
+               __git_find_repo_path || return 1
+               echo "$__git_repo_path"
        elif [ -d "$1/.git" ]; then
                echo "$1/.git"
        else
@@ -56,6 +76,14 @@ __gitdir ()
        fi
 }
 
+# Runs git with all the options given as argument, respecting any
+# '--git-dir=<path>' and '-C <path>' options present on the command line
+__git ()
+{
+       git ${__git_C_args:+"${__git_C_args[@]}"} \
+               ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
+}
+
 # The following function is based on code from:
 #
 #   bash_completion - programmable completion functions for bash 3.2+
@@ -283,11 +311,11 @@ __gitcomp_file ()
 __git_ls_files_helper ()
 {
        if [ "$2" == "--committable" ]; then
-               git -C "$1" diff-index --name-only --relative HEAD
+               __git -C "$1" diff-index --name-only --relative HEAD
        else
                # NOTE: $2 is not quoted in order to support multiple options
-               git -C "$1" ls-files --exclude-standard $2
-       fi 2>/dev/null
+               __git -C "$1" ls-files --exclude-standard $2
+       fi
 }
 
 
@@ -299,47 +327,61 @@ __git_ls_files_helper ()
 #    slash.
 __git_index_files ()
 {
-       local dir="$(__gitdir)" root="${2-.}" file
+       local root="${2-.}" file
 
-       if [ -d "$dir" ]; then
-               __git_ls_files_helper "$root" "$1" |
-               while read -r file; do
-                       case "$file" in
-                       ?*/*) echo "${file%%/*}" ;;
-                       *) echo "$file" ;;
-                       esac
-               done | sort | uniq
-       fi
+       __git_ls_files_helper "$root" "$1" |
+       while read -r file; do
+               case "$file" in
+               ?*/*) echo "${file%%/*}" ;;
+               *) echo "$file" ;;
+               esac
+       done | sort | uniq
 }
 
 __git_heads ()
 {
-       local dir="$(__gitdir)"
-       if [ -d "$dir" ]; then
-               git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
-                       refs/heads
-               return
-       fi
+       __git for-each-ref --format='%(refname:short)' refs/heads
 }
 
 __git_tags ()
 {
-       local dir="$(__gitdir)"
-       if [ -d "$dir" ]; then
-               git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
-                       refs/tags
-               return
-       fi
+       __git for-each-ref --format='%(refname:short)' refs/tags
 }
 
-# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
-# presence of 2nd argument means use the guess heuristic employed
-# by checkout for tracking branches
+# Lists refs from the local (by default) or from a remote repository.
+# It accepts 0, 1 or 2 arguments:
+# 1: The remote to list refs from (optional; ignored, if set but empty).
+#    Can be the name of a configured remote, a path, or a URL.
+# 2: In addition to local refs, list unique branches from refs/remotes/ for
+#    'git checkout's tracking DWIMery (optional; ignored, if set but empty).
 __git_refs ()
 {
-       local i hash dir="$(__gitdir "${1-}")" track="${2-}"
+       local i hash dir track="${2-}"
+       local list_refs_from=path remote="${1-}"
        local format refs pfx
-       if [ -d "$dir" ]; then
+
+       __git_find_repo_path
+       dir="$__git_repo_path"
+
+       if [ -z "$remote" ]; then
+               if [ -z "$dir" ]; then
+                       return
+               fi
+       else
+               if __git_is_configured_remote "$remote"; then
+                       # configured remote takes precedence over a
+                       # local directory with the same name
+                       list_refs_from=remote
+               elif [ -d "$remote/.git" ]; then
+                       dir="$remote/.git"
+               elif [ -d "$remote" ]; then
+                       dir="$remote"
+               else
+                       list_refs_from=url
+               fi
+       fi
+
+       if [ "$list_refs_from" = path ]; then
                case "$cur" in
                refs|refs/*)
                        format="refname"
@@ -355,14 +397,14 @@ __git_refs ()
                        refs="refs/tags refs/heads refs/remotes"
                        ;;
                esac
-               git --git-dir="$dir" for-each-ref --format="$pfx%($format)" \
+               __git_dir="$dir" __git for-each-ref --format="$pfx%($format)" \
                        $refs
                if [ -n "$track" ]; then
                        # employ the heuristic used by git checkout
                        # Try to find a remote branch that matches the completion word
                        # but only output if the branch name is unique
                        local ref entry
-                       git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
+                       __git for-each-ref --shell --format="ref=%(refname:short)" \
                                "refs/remotes/" | \
                        while read -r entry; do
                                eval "$entry"
@@ -376,7 +418,7 @@ __git_refs ()
        fi
        case "$cur" in
        refs|refs/*)
-               git ls-remote "$dir" "$cur*" 2>/dev/null | \
+               __git ls-remote "$remote" "$cur*" | \
                while read -r hash i; do
                        case "$i" in
                        *^{}) ;;
@@ -385,9 +427,21 @@ __git_refs ()
                done
                ;;
        *)
-               echo "HEAD"
-               git for-each-ref --format="%(refname:short)" -- \
-                       "refs/remotes/$dir/" 2>/dev/null | sed -e "s#^$dir/##"
+               if [ "$list_refs_from" = remote ]; then
+                       echo "HEAD"
+                       __git for-each-ref --format="%(refname:short)" \
+                               "refs/remotes/$remote/" | sed -e "s#^$remote/##"
+               else
+                       __git ls-remote "$remote" HEAD \
+                               "refs/tags/*" "refs/heads/*" "refs/remotes/*" |
+                       while read -r hash i; do
+                               case "$i" in
+                               *^{})   ;;
+                               refs/*) echo "${i#refs/*/}" ;;
+                               *)      echo "$i" ;;  # symbolic refs
+                               esac
+                       done
+               fi
                ;;
        esac
 }
@@ -405,7 +459,7 @@ __git_refs2 ()
 __git_refs_remotes ()
 {
        local i hash
-       git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \
+       __git ls-remote "$1" 'refs/heads/*' | \
        while read -r hash i; do
                echo "$i:refs/remotes/$1/${i#refs/heads/}"
        done
@@ -413,9 +467,21 @@ __git_refs_remotes ()
 
 __git_remotes ()
 {
-       local d="$(__gitdir)"
-       test -d "$d/remotes" && ls -1 "$d/remotes"
-       git --git-dir="$d" remote
+       __git_find_repo_path
+       test -d "$__git_repo_path/remotes" && ls -1 "$__git_repo_path/remotes"
+       __git remote
+}
+
+# Returns true if $1 matches the name of a configured remote, false otherwise.
+__git_is_configured_remote ()
+{
+       local remote
+       for remote in $(__git_remotes); do
+               if [ "$remote" = "$1" ]; then
+                       return 0
+               fi
+       done
+       return 1
 }
 
 __git_list_merge_strategies ()
@@ -469,7 +535,7 @@ __git_complete_revlist_file ()
                *)   pfx="$ref:$pfx" ;;
                esac
 
-               __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
+               __gitcomp_nl "$(__git ls-tree "$ls" \
                                | sed '/^100... blob /{
                                           s,^.*        ,,
                                           s,$, ,
@@ -747,7 +813,7 @@ __git_compute_porcelain_commands ()
 __git_get_config_variables ()
 {
        local section="$1" i IFS=$'\n'
-       for i in $(git --git-dir="$(__gitdir)" config --name-only --get-regexp "^$section\..*" 2>/dev/null); do
+       for i in $(__git config --name-only --get-regexp "^$section\..*"); do
                echo "${i#$section.}"
        done
 }
@@ -765,8 +831,7 @@ __git_aliases ()
 # __git_aliased_command requires 1 argument
 __git_aliased_command ()
 {
-       local word cmdline=$(git --git-dir="$(__gitdir)" \
-               config --get "alias.$1")
+       local word cmdline=$(__git config --get "alias.$1")
        for word in $cmdline; do
                case "$word" in
                \!gitk|gitk)
@@ -842,7 +907,7 @@ __git_get_option_value ()
        done
 
        if [ -n "$config_key" ] && [ -z "$result" ]; then
-               result="$(git --git-dir="$(__gitdir)" config "$config_key")"
+               result="$(__git config "$config_key")"
        fi
 
        echo "$result"
@@ -901,8 +966,8 @@ __git_whitespacelist="nowarn warn error error-all fix"
 
 _git_am ()
 {
-       local dir="$(__gitdir)"
-       if [ -d "$dir"/rebase-apply ]; then
+       __git_find_repo_path
+       if [ -d "$__git_repo_path"/rebase-apply ]; then
                __gitcomp "--skip --continue --resolved --abort"
                return
        fi
@@ -990,7 +1055,8 @@ _git_bisect ()
        local subcommands="start bad good skip reset visualize replay log run"
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
-               if [ -f "$(__gitdir)"/BISECT_START ]; then
+               __git_find_repo_path
+               if [ -f "$__git_repo_path"/BISECT_START ]; then
                        __gitcomp "$subcommands"
                else
                        __gitcomp "replay start"
@@ -1096,8 +1162,8 @@ _git_cherry ()
 
 _git_cherry_pick ()
 {
-       local dir="$(__gitdir)"
-       if [ -f "$dir"/CHERRY_PICK_HEAD ]; then
+       __git_find_repo_path
+       if [ -f "$__git_repo_path"/CHERRY_PICK_HEAD ]; then
                __gitcomp "--continue --quit --abort"
                return
        fi
@@ -1192,7 +1258,7 @@ _git_commit ()
                return
        esac
 
-       if git rev-parse --verify --quiet HEAD >/dev/null; then
+       if __git rev-parse --verify --quiet HEAD >/dev/null; then
                __git_complete_index_file "--committable"
        else
                # This is the first commit
@@ -1207,6 +1273,7 @@ _git_describe ()
                __gitcomp "
                        --all --tags --contains --abbrev= --candidates=
                        --exact-match --debug --long --match --always --first-parent
+                       --exclude
                        "
                return
        esac
@@ -1500,10 +1567,10 @@ __git_log_date_formats="relative iso8601 rfc2822 short local default raw"
 _git_log ()
 {
        __git_has_doubledash && return
+       __git_find_repo_path
 
-       local g="$(git rev-parse --git-dir 2>/dev/null)"
        local merge=""
-       if [ -f "$g/MERGE_HEAD" ]; then
+       if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
                merge="--merge"
        fi
        case "$cur" in
@@ -1750,11 +1817,12 @@ _git_push ()
 
 _git_rebase ()
 {
-       local dir="$(__gitdir)"
-       if [ -f "$dir"/rebase-merge/interactive ]; then
+       __git_find_repo_path
+       if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then
                __gitcomp "--continue --skip --abort --quit --edit-todo"
                return
-       elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
+       elif [ -d "$__git_repo_path"/rebase-apply ] || \
+            [ -d "$__git_repo_path"/rebase-merge ]; then
                __gitcomp "--continue --skip --abort --quit"
                return
        fi
@@ -1802,9 +1870,7 @@ _git_send_email ()
 {
        case "$prev" in
        --to|--cc|--bcc|--from)
-               __gitcomp "
-               $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null)
-               "
+               __gitcomp "$(__git send-email --dump-aliases)"
                return
                ;;
        esac
@@ -1834,9 +1900,7 @@ _git_send_email ()
                return
                ;;
        --to=*|--cc=*|--bcc=*|--from=*)
-               __gitcomp "
-               $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null)
-               " "" "${cur#--*=}"
+               __gitcomp "$(__git send-email --dump-aliases)" "" "${cur#--*=}"
                return
                ;;
        --*)
@@ -1930,7 +1994,7 @@ __git_config_get_set_variables ()
                c=$((--c))
        done
 
-       git --git-dir="$(__gitdir)" config $config_file --name-only --list 2>/dev/null
+       __git config $config_file --name-only --list
 }
 
 _git_config ()
@@ -1965,9 +2029,8 @@ _git_config ()
        remote.*.push)
                local remote="${prev#remote.}"
                remote="${remote%.push}"
-               __gitcomp_nl "$(git --git-dir="$(__gitdir)" \
-                       for-each-ref --format='%(refname):%(refname)' \
-                       refs/heads)"
+               __gitcomp_nl "$(__git for-each-ref \
+                       --format='%(refname):%(refname)' refs/heads)"
                return
                ;;
        pull.twohead|pull.octopus)
@@ -2482,8 +2545,8 @@ _git_reset ()
 
 _git_revert ()
 {
-       local dir="$(__gitdir)"
-       if [ -f "$dir"/REVERT_HEAD ]; then
+       __git_find_repo_path
+       if [ -f "$__git_repo_path"/REVERT_HEAD ]; then
                __gitcomp "--continue --quit --abort"
                return
        fi
@@ -2606,12 +2669,12 @@ _git_stash ()
                        if [ $cword -eq 3 ]; then
                                __gitcomp_nl "$(__git_refs)";
                        else
-                               __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
+                               __gitcomp_nl "$(__git stash list \
                                                | sed -n -e 's/:.*//p')"
                        fi
                        ;;
                show,*|apply,*|drop,*|pop,*)
-                       __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
+                       __gitcomp_nl "$(__git stash list \
                                        | sed -n -e 's/:.*//p')"
                        ;;
                *)
@@ -2838,7 +2901,8 @@ _git_worktree ()
 
 __git_main ()
 {
-       local i c=1 command __git_dir
+       local i c=1 command __git_dir __git_repo_path
+       local __git_C_args C_args_count=0
 
        while [ $c -lt $cword ]; do
                i="${words[c]}"
@@ -2848,6 +2912,10 @@ __git_main ()
                --bare)      __git_dir="." ;;
                --help) command="help"; break ;;
                -c|--work-tree|--namespace) ((c++)) ;;
+               -C)     __git_C_args[C_args_count++]=-C
+                       ((c++))
+                       __git_C_args[C_args_count++]="${words[c]}"
+                       ;;
                -*) ;;
                *) command="$i"; break ;;
                esac
@@ -2855,6 +2923,17 @@ __git_main ()
        done
 
        if [ -z "$command" ]; then
+               case "$prev" in
+               --git-dir|-C|--work-tree)
+                       # these need a path argument, let's fall back to
+                       # Bash filename completion
+                       return
+                       ;;
+               -c|--namespace)
+                       # we don't support completing these options' arguments
+                       return
+                       ;;
+               esac
                case "$cur" in
                --*)   __gitcomp "
                        --paginate
@@ -2880,13 +2959,13 @@ __git_main ()
        fi
 
        local completion_func="_git_${command//-/_}"
-       declare -f $completion_func >/dev/null && $completion_func && return
+       declare -f $completion_func >/dev/null 2>/dev/null && $completion_func && return
 
        local expansion=$(__git_aliased_command "$command")
        if [ -n "$expansion" ]; then
                words[1]=$expansion
                completion_func="_git_${expansion//-/_}"
-               declare -f $completion_func >/dev/null && $completion_func
+               declare -f $completion_func >/dev/null 2>/dev/null && $completion_func
        fi
 }
 
@@ -2894,9 +2973,11 @@ __gitk_main ()
 {
        __git_has_doubledash && return
 
-       local g="$(__gitdir)"
+       local __git_repo_path
+       __git_find_repo_path
+
        local merge=""
-       if [ -f "$g/MERGE_HEAD" ]; then
+       if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
                merge="--merge"
        fi
        case "$cur" in
index 712a1377e208f717b61df44f0b5ea0ba99dc72d1..1c3d87f8619e7fb2432d3e318ad59c3ed84ee9ec 100755 (executable)
@@ -1,13 +1,11 @@
-#!/usr/bin/env python
+#!/bin/sh
 
-import sys
-
-sys.stderr.write('WARNING: git-remote-bzr is now maintained independently.\n')
-sys.stderr.write('WARNING: For more information visit https://github.com/felipec/git-remote-bzr\n')
-
-sys.stderr.write('''WARNING:
+cat >&2 <<'EOT'
+WARNING: git-remote-bzr is now maintained independently.
+WARNING: For more information visit https://github.com/felipec/git-remote-bzr
+WARNING:
 WARNING: You can pick a directory on your $PATH and download it, e.g.:
-WARNING:   $ wget -O $HOME/bin/git-remote-bzr \\
+WARNING:   $ wget -O $HOME/bin/git-remote-bzr \
 WARNING:     https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr
 WARNING:   $ chmod +x $HOME/bin/git-remote-bzr
-''')
+EOT
index 4255ad6312ec027b65ffd4dfc456bc0ed5dd533f..8e9188364c6f1ae23f3c3c8d86db9e6653a11d1f 100755 (executable)
@@ -1,13 +1,11 @@
-#!/usr/bin/env python
+#!/bin/sh
 
-import sys
-
-sys.stderr.write('WARNING: git-remote-hg is now maintained independently.\n')
-sys.stderr.write('WARNING: For more information visit https://github.com/felipec/git-remote-hg\n')
-
-sys.stderr.write('''WARNING:
+cat >&2 <<'EOT'
+WARNING: git-remote-hg is now maintained independently.
+WARNING: For more information visit https://github.com/felipec/git-remote-hg
+WARNING:
 WARNING: You can pick a directory on your $PATH and download it, e.g.:
-WARNING:   $ wget -O $HOME/bin/git-remote-hg \\
+WARNING:   $ wget -O $HOME/bin/git-remote-hg \
 WARNING:     https://raw.github.com/felipec/git-remote-hg/master/git-remote-hg
 WARNING:   $ chmod +x $HOME/bin/git-remote-hg
-''')
+EOT
index 4e17e45ed265b3f4861d1b7a1e86edaa4df3b1ac..8d652bf27c9444d3696c4b942dc85f062e2bf53e 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1028,7 +1028,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
+static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -1045,7 +1045,7 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
        return CRLF_UNDEFINED;
 }
 
-static enum eol git_path_check_eol(struct git_attr_check *check)
+static enum eol git_path_check_eol(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -1058,7 +1058,7 @@ static enum eol git_path_check_eol(struct git_attr_check *check)
        return EOL_UNSET;
 }
 
-static struct convert_driver *git_path_check_convert(struct git_attr_check *check)
+static struct convert_driver *git_path_check_convert(struct attr_check_item *check)
 {
        const char *value = check->value;
        struct convert_driver *drv;
@@ -1071,7 +1071,7 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check *chec
        return NULL;
 }
 
-static int git_path_check_ident(struct git_attr_check *check)
+static int git_path_check_ident(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -1085,24 +1085,19 @@ struct conv_attrs {
        int ident;
 };
 
-static const char *conv_attr_name[] = {
-       "crlf", "ident", "filter", "eol", "text",
-};
-#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
-
 static void convert_attrs(struct conv_attrs *ca, const char *path)
 {
-       int i;
-       static struct git_attr_check ccheck[NUM_CONV_ATTRS];
+       static struct attr_check *check;
 
-       if (!ccheck[0].attr) {
-               for (i = 0; i < NUM_CONV_ATTRS; i++)
-                       ccheck[i].attr = git_attr(conv_attr_name[i]);
+       if (!check) {
+               check = attr_check_initl("crlf", "ident", "filter",
+                                        "eol", "text", NULL);
                user_convert_tail = &user_convert;
                git_config(read_convert_config, NULL);
        }
 
-       if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
+       if (!git_check_attr(path, check)) {
+               struct attr_check_item *ccheck = check->items;
                ca->crlf_action = git_path_check_crlf(ccheck + 4);
                if (ca->crlf_action == CRLF_UNDEFINED)
                        ca->crlf_action = git_path_check_crlf(ccheck + 0);
diff --git a/diff.c b/diff.c
index 051761be405ece65423d43830f08ed7b7dec9756..a628ac3a95108ca79aef0b41b7051ab2fcbe693a 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2870,8 +2870,25 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
                        s->should_free = 1;
                        return 0;
                }
-               if (size_only)
+
+               /*
+                * Even if the caller would be happy with getting
+                * only the size, we cannot return early at this
+                * point if the path requires us to run the content
+                * conversion.
+                */
+               if (size_only && !would_convert_to_git(s->path))
                        return 0;
+
+               /*
+                * Note: this check uses xsize_t(st.st_size) that may
+                * not be the true size of the blob after it goes
+                * through convert_to_git().  This may not strictly be
+                * correct, but the whole point of big_file_threshold
+                * and is_binary check being that we want to avoid
+                * opening the file and inspecting the contents, this
+                * is probably fine.
+                */
                if ((flags & CHECK_BINARY) &&
                    s->size > big_file_threshold && s->is_binary == -1) {
                        s->is_binary = 1;
diff --git a/dir.c b/dir.c
index 4541f9e1460cde7945e2443f2fee5c7df18073f5..aeeb5ce10490ef1e0adb907d1e21bcbbbbb401ac 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -2730,8 +2730,8 @@ void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
 {
        struct strbuf file_name = STRBUF_INIT;
        struct strbuf rel_path = STRBUF_INIT;
-       char *git_dir = real_pathdup(git_dir_);
-       char *work_tree = real_pathdup(work_tree_);
+       char *git_dir = real_pathdup(git_dir_, 1);
+       char *work_tree = real_pathdup(work_tree_, 1);
 
        /* Update gitfile */
        strbuf_addf(&file_name, "%s/.git", work_tree);
index c07fb17fb70bdb3cdf3ae9ab23196af9a0b7ad1b..42dc3106d2fae1976dcfdfbf264a1a9ed96a71b6 100644 (file)
@@ -259,7 +259,7 @@ void set_git_work_tree(const char *new_work_tree)
                return;
        }
        git_work_tree_initialized = 1;
-       work_tree = real_pathdup(new_work_tree);
+       work_tree = real_pathdup(new_work_tree, 1);
 }
 
 const char *get_git_work_tree(void)
index 61f6a43579f5e3fc98e562fe6f0c6d8c118e0ded..f73210973f12256f120351ef6826c64b62e51880 100644 (file)
@@ -142,8 +142,8 @@ int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len)
         * the endianness conversion in a separate pass to ensure
         * we're loading 8-byte aligned words.
         */
-       memcpy(self->buffer, ptr, self->buffer_size * sizeof(uint64_t));
-       ptr += self->buffer_size * sizeof(uint64_t);
+       memcpy(self->buffer, ptr, self->buffer_size * sizeof(eword_t));
+       ptr += self->buffer_size * sizeof(eword_t);
 
        for (i = 0; i < self->buffer_size; ++i)
                self->buffer[i] = ntohll(self->buffer[i]);
index 64fe602f0bb62c4848798d1bdb43a7be9ab5f523..6c13472c42257a47b07058156a7070cf427a038d 100644 (file)
@@ -1752,7 +1752,7 @@ static int update_branch(struct branch *b)
 
        if (is_null_sha1(b->sha1)) {
                if (b->delete)
-                       delete_ref(b->name, NULL, 0);
+                       delete_ref(NULL, b->name, NULL, 0);
                return 0;
        }
        if (read_ref(b->name, old_sha1))
index 601f0779a1903a2b860412e3404e4ea8e4bce573..d07d85ce302d39abc8c017c377d4230d729fdeb4 100644 (file)
@@ -35,6 +35,7 @@ static const char *alternate_shallow_file;
 #define COMMON_REF     (1U << 2)
 #define SEEN           (1U << 3)
 #define POPPED         (1U << 4)
+#define ALTERNATE      (1U << 5)
 
 static int marked;
 
@@ -67,6 +68,41 @@ static inline void print_verbose(const struct fetch_pack_args *args,
        fputc('\n', stderr);
 }
 
+struct alternate_object_cache {
+       struct object **items;
+       size_t nr, alloc;
+};
+
+static void cache_one_alternate(const char *refname,
+                               const struct object_id *oid,
+                               void *vcache)
+{
+       struct alternate_object_cache *cache = vcache;
+       struct object *obj = parse_object(oid->hash);
+
+       if (!obj || (obj->flags & ALTERNATE))
+               return;
+
+       obj->flags |= ALTERNATE;
+       ALLOC_GROW(cache->items, cache->nr + 1, cache->alloc);
+       cache->items[cache->nr++] = obj;
+}
+
+static void for_each_cached_alternate(void (*cb)(struct object *))
+{
+       static int initialized;
+       static struct alternate_object_cache cache;
+       size_t i;
+
+       if (!initialized) {
+               for_each_alternate_ref(cache_one_alternate, &cache);
+               initialized = 1;
+       }
+
+       for (i = 0; i < cache.nr; i++)
+               cb(cache.items[i]);
+}
+
 static void rev_list_push(struct commit *commit, int mark)
 {
        if (!(commit->object.flags & mark)) {
@@ -253,9 +289,9 @@ static void send_request(struct fetch_pack_args *args,
                write_or_die(fd, buf->buf, buf->len);
 }
 
-static void insert_one_alternate_ref(const struct ref *ref, void *unused)
+static void insert_one_alternate_object(struct object *obj)
 {
-       rev_list_insert_ref(NULL, ref->old_oid.hash);
+       rev_list_insert_ref(NULL, obj->oid.hash);
 }
 
 #define INITIAL_FLUSH 16
@@ -298,7 +334,7 @@ static int find_common(struct fetch_pack_args *args,
        marked = 1;
 
        for_each_ref(rev_list_insert_ref_oid, NULL);
-       for_each_alternate_ref(insert_one_alternate_ref, NULL);
+       for_each_cached_alternate(insert_one_alternate_object);
 
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
@@ -578,7 +614,7 @@ static void filter_refs(struct fetch_pack_args *args,
                                        break; /* definitely do not have it */
                                else if (cmp == 0) {
                                        keep = 1; /* definitely have it */
-                                       sought[i]->matched = 1;
+                                       sought[i]->match_status = REF_MATCHED;
                                }
                                i++;
                        }
@@ -598,30 +634,32 @@ static void filter_refs(struct fetch_pack_args *args,
        }
 
        /* Append unmatched requests to the list */
-       if ((allow_unadvertised_object_request &
-           (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) {
-               for (i = 0; i < nr_sought; i++) {
-                       unsigned char sha1[20];
+       for (i = 0; i < nr_sought; i++) {
+               unsigned char sha1[20];
 
-                       ref = sought[i];
-                       if (ref->matched)
-                               continue;
-                       if (get_sha1_hex(ref->name, sha1) ||
-                           ref->name[40] != '\0' ||
-                           hashcmp(sha1, ref->old_oid.hash))
-                               continue;
+               ref = sought[i];
+               if (ref->match_status != REF_NOT_MATCHED)
+                       continue;
+               if (get_sha1_hex(ref->name, sha1) ||
+                   ref->name[40] != '\0' ||
+                   hashcmp(sha1, ref->old_oid.hash))
+                       continue;
 
-                       ref->matched = 1;
+               if ((allow_unadvertised_object_request &
+                   (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) {
+                       ref->match_status = REF_MATCHED;
                        *newtail = copy_ref(ref);
                        newtail = &(*newtail)->next;
+               } else {
+                       ref->match_status = REF_UNADVERTISED_NOT_ALLOWED;
                }
        }
        *refs = newlist;
 }
 
-static void mark_alternate_complete(const struct ref *ref, void *unused)
+static void mark_alternate_complete(struct object *obj)
 {
-       mark_complete(ref->old_oid.hash);
+       mark_complete(obj->oid.hash);
 }
 
 static int everything_local(struct fetch_pack_args *args,
@@ -657,7 +695,7 @@ static int everything_local(struct fetch_pack_args *args,
 
        if (!args->deepen) {
                for_each_ref(mark_complete_oid, NULL);
-               for_each_alternate_ref(mark_alternate_complete, NULL);
+               for_each_cached_alternate(mark_alternate_complete);
                commit_list_sort_by_date(&complete);
                if (cutoff)
                        mark_recent_complete_commits(args, cutoff);
@@ -1094,3 +1132,26 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
        clear_shallow_info(&si);
        return ref_cpy;
 }
+
+int report_unmatched_refs(struct ref **sought, int nr_sought)
+{
+       int i, ret = 0;
+
+       for (i = 0; i < nr_sought; i++) {
+               if (!sought[i])
+                       continue;
+               switch (sought[i]->match_status) {
+               case REF_MATCHED:
+                       continue;
+               case REF_NOT_MATCHED:
+                       error(_("no such remote ref %s"), sought[i]->name);
+                       break;
+               case REF_UNADVERTISED_NOT_ALLOWED:
+                       error(_("Server does not allow request for unadvertised object %s"),
+                             sought[i]->name);
+                       break;
+               }
+               ret = 1;
+       }
+       return ret;
+}
index c912e3d321c6a80952dbcd266786d1042a49e579..a2d46e6e754fef713c42fa34b29588eb14bd0e6a 100644 (file)
@@ -45,4 +45,10 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
                       struct sha1_array *shallow,
                       char **pack_lockfile);
 
+/*
+ * Print an appropriate error message for each sought ref that wasn't
+ * matched.  Return 0 if all sought refs were matched, otherwise 1.
+ */
+int report_unmatched_refs(struct ref **sought, int nr_sought);
+
 #endif
index 982593c89e2f253c2fa4d626c0c00fde7935aead..f5c816e273318709e90a9f5e9a47143a3842f8e3 100755 (executable)
@@ -92,7 +92,7 @@ sub colored {
 }
 
 # command line options
-my $cmd;
+my $patch_mode_only;
 my $patch_mode;
 my $patch_mode_revision;
 
@@ -1299,7 +1299,7 @@ sub patch_update_cmd {
                }
                return 0;
        }
-       if ($patch_mode) {
+       if ($patch_mode_only) {
                @them = @mods;
        }
        else {
@@ -1721,7 +1721,7 @@ sub process_args {
                die sprintf(__("invalid argument %s, expecting --"),
                               $arg) unless $arg eq "--";
                %patch_mode_flavour = %{$patch_modes{$patch_mode}};
-               $cmd = 1;
+               $patch_mode_only = 1;
        }
        elsif ($arg ne "--") {
                die sprintf(__("invalid argument %s, expecting --"), $arg);
@@ -1758,7 +1758,7 @@ sub main_loop {
 
 process_args();
 refresh();
-if ($cmd) {
+if ($patch_mode_only) {
        patch_update_cmd();
 }
 else {
index ef6d560e156c0adefd897e04377b09d5c32edf08..e626851fe98673b4ac546864030975870ab4dfa7 100644 (file)
@@ -639,11 +639,6 @@ extern int gitsetenv(const char *, const char *, int);
 extern char *gitmkdtemp(char *);
 #endif
 
-#ifdef NO_MKSTEMPS
-#define mkstemps gitmkstemps
-extern int gitmkstemps(char *, int);
-#endif
-
 #ifdef NO_UNSETENV
 #define unsetenv gitunsetenv
 extern void gitunsetenv(const char *);
index 86b2ff1e07614846465109dcb4b9afed11013839..2b8cdba157d9cd822acc88b7ec58cecda0149b85 100755 (executable)
@@ -46,6 +46,8 @@ git_commit_non_empty_tree()
 {
        if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
                map "$3"
+       elif test $# = 1 && test "$1" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904; then
+               :
        else
                git commit-tree "$@"
        fi
index 4734094a3f1aaa7d7c767a0740b46dd6abdd6f7a..2c9c0165b5ab0cee8c9369b9bee33277bd8bc5e9 100644 (file)
@@ -1069,6 +1069,10 @@ git_rebase__interactive () {
 
 case "$action" in
 continue)
+       if test ! -d "$rewritten"
+       then
+               exec git rebase--helper ${force_rebase:+--no-ff} --continue
+       fi
        # do we have anything to commit?
        if git diff-index --cached --quiet HEAD --
        then
@@ -1128,6 +1132,10 @@ first and then run 'git rebase --continue' again.")"
 skip)
        git rerere clear
 
+       if test ! -d "$rewritten"
+       then
+               exec git rebase--helper ${force_rebase:+--no-ff} --continue
+       fi
        do_rest
        return 0
        ;;
@@ -1314,6 +1322,11 @@ expand_todo_ids
 test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
 
 checkout_onto
+if test -z "$rebase_root" && test ! -d "$rewritten"
+then
+       require_clean_work_tree "rebase"
+       exec git rebase--helper ${force_rebase:+--no-ff} --continue
+fi
 do_rest
 
 }
index 068d60b3e698f03f433db33d9e5f085ea57ce0a3..eea0a517f71b66f861db38f70fac0593f8c8cea4 100755 (executable)
@@ -1563,7 +1563,7 @@ sub send_message {
        # Now parse the message body
        while(<$fh>) {
                $message .=  $_;
-               if (/^(Signed-off-by|Cc): (.*)$/i) {
+               if (/^(Signed-off-by|Cc): ([^>]*>?)/i) {
                        chomp;
                        my ($what, $c) = ($1, $2);
                        chomp $c;
index 10c284d1aa2273a3dfe5cc39f1d6738830d02462..9c70662cc81269316375c54cf45a9c838b384a85 100755 (executable)
@@ -7,8 +7,11 @@ USAGE="list [<options>]
    or: $dashless drop [-q|--quiet] [<stash>]
    or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
    or: $dashless branch <branchname> [<stash>]
-   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-                      [-u|--include-untracked] [-a|--all] [<message>]]
+   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+                     [-u|--include-untracked] [-a|--all] [<message>]
+   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+                      [-u|--include-untracked] [-a|--all] [-m <message>]
+                      [-- <pathspec>...]]
    or: $dashless clear"
 
 SUBDIRECTORY_OK=Yes
@@ -33,15 +36,15 @@ else
 fi
 
 no_changes () {
-       git diff-index --quiet --cached HEAD --ignore-submodules -- &&
-       git diff-files --quiet --ignore-submodules &&
+       git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
+       git diff-files --quiet --ignore-submodules -- "$@" &&
        (test -z "$untracked" || test -z "$(untracked_files)")
 }
 
 untracked_files () {
        excl_opt=--exclude-standard
        test "$untracked" = "all" && excl_opt=
-       git ls-files -o -z $excl_opt
+       git ls-files -o -z $excl_opt -- "$@"
 }
 
 clear_stash () {
@@ -56,11 +59,29 @@ clear_stash () {
 }
 
 create_stash () {
-       stash_msg="$1"
-       untracked="$2"
+       stash_msg=
+       untracked=
+       while test $# != 0
+       do
+               case "$1" in
+               -m|--message)
+                       shift
+                       stash_msg=${1?"BUG: create_stash () -m requires an argument"}
+                       ;;
+               -u|--include-untracked)
+                       shift
+                       untracked=${1?"BUG: create_stash () -u requires an argument"}
+                       ;;
+               --)
+                       shift
+                       break
+                       ;;
+               esac
+               shift
+       done
 
        git update-index -q --refresh
-       if no_changes
+       if no_changes "$@"
        then
                exit 0
        fi
@@ -92,7 +113,7 @@ create_stash () {
                # Untracked files are stored by themselves in a parentless commit, for
                # ease of unpacking later.
                u_commit=$(
-                       untracked_files | (
+                       untracked_files "$@" | (
                                GIT_INDEX_FILE="$TMPindex" &&
                                export GIT_INDEX_FILE &&
                                rm -f "$TMPindex" &&
@@ -115,7 +136,7 @@ create_stash () {
                        git read-tree --index-output="$TMPindex" -m $i_tree &&
                        GIT_INDEX_FILE="$TMPindex" &&
                        export GIT_INDEX_FILE &&
-                       git diff-index --name-only -z HEAD -- >"$TMP-stagenames" &&
+                       git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
                        git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
                        git write-tree &&
                        rm -f "$TMPindex"
@@ -129,7 +150,7 @@ create_stash () {
 
                # find out what the user wants
                GIT_INDEX_FILE="$TMP-index" \
-                       git add--interactive --patch=stash -- &&
+                       git add--interactive --patch=stash -- "$@" &&
 
                # state of the working tree
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
@@ -189,10 +210,11 @@ store_stash () {
        return $ret
 }
 
-save_stash () {
+push_stash () {
        keep_index=
        patch_mode=
        untracked=
+       stash_msg=
        while test $# != 0
        do
                case "$1" in
@@ -216,6 +238,11 @@ save_stash () {
                -a|--all)
                        untracked=all
                        ;;
+               -m|--message)
+                       shift
+                       test -z ${1+x} && usage
+                       stash_msg=$1
+                       ;;
                --help)
                        show_help
                        ;;
@@ -251,29 +278,38 @@ save_stash () {
                die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
        fi
 
-       stash_msg="$*"
+       test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
 
        git update-index -q --refresh
-       if no_changes
+       if no_changes "$@"
        then
                say "$(gettext "No local changes to save")"
                exit 0
        fi
+
        git reflog exists $ref_stash ||
                clear_stash || die "$(gettext "Cannot initialize stash")"
 
-       create_stash "$stash_msg" $untracked
+       create_stash -m "$stash_msg" -u "$untracked" -- "$@"
        store_stash -m "$stash_msg" -q $w_commit ||
        die "$(gettext "Cannot save the current status")"
        say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
 
        if test -z "$patch_mode"
        then
-               git reset --hard ${GIT_QUIET:+-q}
+               if test $# != 0
+               then
+                       git reset ${GIT_QUIET:+-q} -- "$@"
+                       git ls-files -z --modified -- "$@" |
+                       git checkout-index -z --force --stdin
+                       git clean --force ${GIT_QUIET:+-q} -d -- "$@"
+               else
+                       git reset --hard ${GIT_QUIET:+-q}
+               fi
                test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
                if test -n "$untracked"
                then
-                       git clean --force --quiet -d $CLEAN_X_OPTION
+                       git clean --force --quiet -d $CLEAN_X_OPTION -- "$@"
                fi
 
                if test "$keep_index" = "t" && test -n "$i_tree"
@@ -291,6 +327,36 @@ save_stash () {
        fi
 }
 
+save_stash () {
+       push_options=
+       while test $# != 0
+       do
+               case "$1" in
+               --)
+                       shift
+                       break
+                       ;;
+               -*)
+                       # pass all options through to push_stash
+                       push_options="$push_options $1"
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done
+
+       stash_msg="$*"
+
+       if test -z "$stash_msg"
+       then
+               push_stash $push_options
+       else
+               push_stash $push_options -m "$stash_msg"
+       fi
+}
+
 have_stash () {
        git rev-parse --verify --quiet $ref_stash >/dev/null
 }
@@ -590,18 +656,21 @@ apply_to_branch () {
        }
 }
 
+test "$1" = "-p" && set "push" "$@"
+
 PARSE_CACHE='--not-parsed'
-# The default command is "save" if nothing but options are given
+# The default command is "push" if nothing but options are given
 seen_non_option=
 for opt
 do
        case "$opt" in
+       --) break ;;
        -*) ;;
        *) seen_non_option=t; break ;;
        esac
 done
 
-test -n "$seen_non_option" || set "save" "$@"
+test -n "$seen_non_option" || set "push" "$@"
 
 # Main command set
 case "$1" in
@@ -617,6 +686,10 @@ save)
        shift
        save_stash "$@"
        ;;
+push)
+       shift
+       push_stash "$@"
+       ;;
 apply)
        shift
        apply_stash "$@"
@@ -627,7 +700,7 @@ clear)
        ;;
 create)
        shift
-       create_stash "$*" && echo "$w_commit"
+       create_stash -m "$*" && echo "$w_commit"
        ;;
 store)
        shift
@@ -648,7 +721,7 @@ branch)
 *)
        case $# in
        0)
-               save_stash &&
+               push_stash &&
                say "$(gettext "(To restore them type \"git stash apply\")")"
                ;;
        *)
index fa423647858fede0cf63a27b2c11eb392b639a09..d2404184ba2edc1ac330898ca610ad5e5df8d6c9 100755 (executable)
@@ -1175,10 +1175,10 @@ sub cmd_branch {
        ::_req_svn();
        require SVN::Client;
 
+       my ($config, $baton, undef) = Git::SVN::Ra::prepare_config_once();
        my $ctx = SVN::Client->new(
-               config => SVN::Core::config_get_config(
-                       $Git::SVN::Ra::config_dir
-               ),
+               auth => $baton,
+               config => $config,
                log_msg => sub {
                        ${ $_[0] } = defined $_message
                                ? $_message
diff --git a/git.c b/git.c
index c887272b129968db161a152ce8a91aca4439ca3c..33f52acbcc8647e10e5d675806ca03d33fed899d 100644 (file)
--- a/git.c
+++ b/git.c
@@ -473,6 +473,7 @@ static struct cmd_struct commands[] = {
        { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
        { "push", cmd_push, RUN_SETUP },
        { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
+       { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
        { "receive-pack", cmd_receive_pack },
        { "reflog", cmd_reflog, RUN_SETUP },
        { "remote", cmd_remote, RUN_SETUP },
diff --git a/http.c b/http.c
index 90a1c0f1131c4a5fcbc50d6cc81c1be9cef3cd71..96d84bbed3f66153cc3d817f2b342dc5d1600574 100644 (file)
--- a/http.c
+++ b/http.c
@@ -109,7 +109,7 @@ static int curl_save_cookies;
 struct credential http_auth = CREDENTIAL_INIT;
 static int http_proactive_auth;
 static const char *user_agent;
-static int curl_empty_auth;
+static int curl_empty_auth = -1;
 
 enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;
 
@@ -125,6 +125,14 @@ static struct credential cert_auth = CREDENTIAL_INIT;
 static int ssl_cert_password_required;
 #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
 static unsigned long http_auth_methods = CURLAUTH_ANY;
+static int http_auth_methods_restricted;
+/* Modes for which empty_auth cannot actually help us. */
+static unsigned long empty_auth_useless =
+       CURLAUTH_BASIC
+#ifdef CURLAUTH_DIGEST_IE
+       | CURLAUTH_DIGEST_IE
+#endif
+       | CURLAUTH_DIGEST;
 #endif
 
 static struct curl_slist *pragma_header;
@@ -333,7 +341,10 @@ static int http_options(const char *var, const char *value, void *cb)
                return git_config_string(&user_agent, var, value);
 
        if (!strcmp("http.emptyauth", var)) {
-               curl_empty_auth = git_config_bool(var, value);
+               if (value && !strcmp("auto", value))
+                       curl_empty_auth = -1;
+               else
+                       curl_empty_auth = git_config_bool(var, value);
                return 0;
        }
 
@@ -382,10 +393,37 @@ static int http_options(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
+static int curl_empty_auth_enabled(void)
+{
+       if (curl_empty_auth >= 0)
+               return curl_empty_auth;
+
+#ifndef LIBCURL_CAN_HANDLE_AUTH_ANY
+       /*
+        * Our libcurl is too old to do AUTH_ANY in the first place;
+        * just default to turning the feature off.
+        */
+#else
+       /*
+        * In the automatic case, kick in the empty-auth
+        * hack as long as we would potentially try some
+        * method more exotic than "Basic" or "Digest".
+        *
+        * But only do this when this is our second or
+        * subsequent request, as by then we know what
+        * methods are available.
+        */
+       if (http_auth_methods_restricted &&
+           (http_auth_methods & ~empty_auth_useless))
+               return 1;
+#endif
+       return 0;
+}
+
 static void init_curl_http_auth(CURL *result)
 {
        if (!http_auth.username || !*http_auth.username) {
-               if (curl_empty_auth)
+               if (curl_empty_auth_enabled())
                        curl_easy_setopt(result, CURLOPT_USERPWD, ":");
                return;
        }
@@ -1079,7 +1117,7 @@ struct active_request_slot *get_active_slot(void)
 #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
        curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
 #endif
-       if (http_auth.password || curl_empty_auth)
+       if (http_auth.password || curl_empty_auth_enabled())
                init_curl_http_auth(slot->curl);
 
        return slot;
@@ -1347,6 +1385,10 @@ static int handle_curl_result(struct slot_results *results)
                } else {
 #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
                        http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
+                       if (results->auth_avail) {
+                               http_auth_methods &= results->auth_avail;
+                               http_auth_methods_restricted = 1;
+                       }
 #endif
                        return HTTP_REAUTH;
                }
@@ -1727,6 +1769,9 @@ static int http_request_reauth(const char *url,
 {
        int ret = http_request(url, result, target, options);
 
+       if (ret != HTTP_OK && ret != HTTP_REAUTH)
+               return ret;
+
        if (options && options->effective_url && options->base_url) {
                if (update_url_from_redirect(options->base_url,
                                             url, options->effective_url)) {
diff --git a/ident.c b/ident.c
index ac4ae02b485637002869b3fc7a78033d7a5b6aec..c0364fe3a1630086e0e3752fe3e7e51e2027ea44 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -153,7 +153,7 @@ static void copy_email(const struct passwd *pw, struct strbuf *email,
 
 const char *ident_default_name(void)
 {
-       if (!git_default_name.len) {
+       if (!(ident_config_given & IDENT_NAME_GIVEN) && !git_default_name.len) {
                copy_gecos(xgetpwuid_self(&default_name_is_bogus), &git_default_name);
                strbuf_trim(&git_default_name);
        }
@@ -162,7 +162,7 @@ const char *ident_default_name(void)
 
 const char *ident_default_email(void)
 {
-       if (!git_default_email.len) {
+       if (!(ident_config_given & IDENT_MAIL_GIVEN) && !git_default_email.len) {
                const char *email = getenv("EMAIL");
 
                if (email && email[0]) {
@@ -203,6 +203,15 @@ static int crud(unsigned char c)
                c == '\'';
 }
 
+static int has_non_crud(const char *str)
+{
+       for (; *str; str++) {
+               if (!crud(*str))
+                       return 1;
+       }
+       return 0;
+}
+
 /*
  * Copy over a string to the destination, but avoid special
  * characters ('\n', '<' and '>') and remove crud at the end
@@ -351,19 +360,32 @@ const char *fmt_ident(const char *name, const char *email,
        int want_date = !(flag & IDENT_NO_DATE);
        int want_name = !(flag & IDENT_NO_NAME);
 
+       if (!email) {
+               if (strict && ident_use_config_only
+                   && !(ident_config_given & IDENT_MAIL_GIVEN)) {
+                       fputs(_(env_hint), stderr);
+                       die(_("no email was given and auto-detection is disabled"));
+               }
+               email = ident_default_email();
+               if (strict && default_email_is_bogus) {
+                       fputs(_(env_hint), stderr);
+                       die(_("unable to auto-detect email address (got '%s')"), email);
+               }
+       }
+
        if (want_name) {
                int using_default = 0;
                if (!name) {
                        if (strict && ident_use_config_only
                            && !(ident_config_given & IDENT_NAME_GIVEN)) {
                                fputs(_(env_hint), stderr);
-                               die("no name was given and auto-detection is disabled");
+                               die(_("no name was given and auto-detection is disabled"));
                        }
                        name = ident_default_name();
                        using_default = 1;
                        if (strict && default_name_is_bogus) {
                                fputs(_(env_hint), stderr);
-                               die("unable to auto-detect name (got '%s')", name);
+                               die(_("unable to auto-detect name (got '%s')"), name);
                        }
                }
                if (!*name) {
@@ -371,24 +393,13 @@ const char *fmt_ident(const char *name, const char *email,
                        if (strict) {
                                if (using_default)
                                        fputs(_(env_hint), stderr);
-                               die("empty ident name (for <%s>) not allowed", email);
+                               die(_("empty ident name (for <%s>) not allowed"), email);
                        }
                        pw = xgetpwuid_self(NULL);
                        name = pw->pw_name;
                }
-       }
-
-       if (!email) {
-               if (strict && ident_use_config_only
-                   && !(ident_config_given & IDENT_MAIL_GIVEN)) {
-                       fputs(_(env_hint), stderr);
-                       die("no email was given and auto-detection is disabled");
-               }
-               email = ident_default_email();
-               if (strict && default_email_is_bogus) {
-                       fputs(_(env_hint), stderr);
-                       die("unable to auto-detect email address (got '%s')", email);
-               }
+               if (strict && !has_non_crud(name))
+                       die(_("name consists only of disallowed characters: %s"), name);
        }
 
        strbuf_reset(&ident);
@@ -403,7 +414,7 @@ const char *fmt_ident(const char *name, const char *email,
                strbuf_addch(&ident, ' ');
                if (date_str && date_str[0]) {
                        if (parse_date(date_str, &ident) < 0)
-                               die("invalid date format: %s", date_str);
+                               die(_("invalid date format: %s"), date_str);
                }
                else
                        strbuf_addstr(&ident, ident_default_date());
index 65f3558b3be695ce5259df9a2da4f28e95b35b71..a23b910471b6c3195e18aad3cbdf225ae200cca0 100644 (file)
@@ -43,9 +43,10 @@ void range_set_release(struct range_set *rs)
 static void range_set_copy(struct range_set *dst, struct range_set *src)
 {
        range_set_init(dst, src->nr);
-       memcpy(dst->ranges, src->ranges, src->nr*sizeof(struct range_set));
+       COPY_ARRAY(dst->ranges, src->ranges, src->nr);
        dst->nr = src->nr;
 }
+
 static void range_set_move(struct range_set *dst, struct range_set *src)
 {
        range_set_release(dst);
@@ -144,7 +145,7 @@ void sort_and_merge_range_set(struct range_set *rs)
 static void range_set_union(struct range_set *out,
                             struct range_set *a, struct range_set *b)
 {
-       int i = 0, j = 0, o = 0;
+       int i = 0, j = 0;
        struct range *ra = a->ranges;
        struct range *rb = b->ranges;
        /* cannot make an alias of out->ranges: it may change during grow */
@@ -167,16 +168,15 @@ static void range_set_union(struct range_set *out,
                        new = &rb[j++];
                if (new->start == new->end)
                        ; /* empty range */
-               else if (!o || out->ranges[o-1].end < new->start) {
+               else if (!out->nr || out->ranges[out->nr-1].end < new->start) {
                        range_set_grow(out, 1);
-                       out->ranges[o].start = new->start;
-                       out->ranges[o].end = new->end;
-                       o++;
-               } else if (out->ranges[o-1].end < new->end) {
-                       out->ranges[o-1].end = new->end;
+                       out->ranges[out->nr].start = new->start;
+                       out->ranges[out->nr].end = new->end;
+                       out->nr++;
+               } else if (out->ranges[out->nr-1].end < new->end) {
+                       out->ranges[out->nr-1].end = new->end;
                }
        }
-       out->nr = o;
 }
 
 /*
index ad8be42f912b456492ca1704ce067fea4713401a..ac0d4a5d78d956f09aa0082b286babc36f04b593 100644 (file)
@@ -336,15 +336,6 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
        return &ll_merge_drv[LL_TEXT_MERGE];
 }
 
-static int git_path_check_merge(const char *path, struct git_attr_check check[2])
-{
-       if (!check[0].attr) {
-               check[0].attr = git_attr("merge");
-               check[1].attr = git_attr("conflict-marker-size");
-       }
-       return git_check_attr(path, 2, check);
-}
-
 static void normalize_file(mmfile_t *mm, const char *path)
 {
        struct strbuf strbuf = STRBUF_INIT;
@@ -362,7 +353,7 @@ int ll_merge(mmbuffer_t *result_buf,
             mmfile_t *theirs, const char *their_label,
             const struct ll_merge_options *opts)
 {
-       static struct git_attr_check check[2];
+       static struct attr_check *check;
        static const struct ll_merge_options default_opts;
        const char *ll_driver_name = NULL;
        int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
@@ -376,10 +367,14 @@ int ll_merge(mmbuffer_t *result_buf,
                normalize_file(ours, path);
                normalize_file(theirs, path);
        }
-       if (!git_path_check_merge(path, check)) {
-               ll_driver_name = check[0].value;
-               if (check[1].value) {
-                       marker_size = atoi(check[1].value);
+
+       if (!check)
+               check = attr_check_initl("merge", "conflict-marker-size", NULL);
+
+       if (!git_check_attr(path, check)) {
+               ll_driver_name = check->items[0].value;
+               if (check->items[1].value) {
+                       marker_size = atoi(check->items[1].value);
                        if (marker_size <= 0)
                                marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
                }
@@ -398,13 +393,13 @@ int ll_merge(mmbuffer_t *result_buf,
 
 int ll_merge_marker_size(const char *path)
 {
-       static struct git_attr_check check;
+       static struct attr_check *check;
        int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
 
-       if (!check.attr)
-               check.attr = git_attr("conflict-marker-size");
-       if (!git_check_attr(path, 1, &check) && check.value) {
-               marker_size = atoi(check.value);
+       if (!check)
+               check = attr_check_initl("conflict-marker-size", NULL);
+       if (!git_check_attr(path, check) && check->items[0].value) {
+               marker_size = atoi(check->items[0].value);
                if (marker_size <= 0)
                        marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
        }
index 8c2415747a2606aefb29fd6e0092c86da3c72589..4618dd04ca1b6bd2bf53481f39851fb4e5d02336 100644 (file)
@@ -332,35 +332,31 @@ void fmt_output_commit(struct strbuf *filename,
        strbuf_release(&subject);
 }
 
+void fmt_output_email_subject(struct strbuf *sb, struct rev_info *opt)
+{
+       if (opt->total > 0) {
+               strbuf_addf(sb, "Subject: [%s%s%0*d/%d] ",
+                           opt->subject_prefix,
+                           *opt->subject_prefix ? " " : "",
+                           digits_in_number(opt->total),
+                           opt->nr, opt->total);
+       } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
+               strbuf_addf(sb, "Subject: [%s] ",
+                           opt->subject_prefix);
+       } else {
+               strbuf_addstr(sb, "Subject: ");
+       }
+}
+
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
-                            const char **subject_p,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p)
 {
-       const char *subject = NULL;
        const char *extra_headers = opt->extra_headers;
        const char *name = oid_to_hex(opt->zero_commit ?
                                      &null_oid : &commit->object.oid);
 
        *need_8bit_cte_p = 0; /* unknown */
-       if (opt->total > 0) {
-               static char buffer[64];
-               snprintf(buffer, sizeof(buffer),
-                        "Subject: [%s%s%0*d/%d] ",
-                        opt->subject_prefix,
-                        *opt->subject_prefix ? " " : "",
-                        digits_in_number(opt->total),
-                        opt->nr, opt->total);
-               subject = buffer;
-       } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
-               static char buffer[256];
-               snprintf(buffer, sizeof(buffer),
-                        "Subject: [%s] ",
-                        opt->subject_prefix);
-               subject = buffer;
-       } else {
-               subject = "Subject: ";
-       }
 
        fprintf(opt->diffopt.file, "From %s Mon Sep 17 00:00:00 2001\n", name);
        graph_show_oneline(opt->graph);
@@ -417,7 +413,6 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                opt->diffopt.stat_sep = buffer;
                strbuf_release(&filename);
        }
-       *subject_p = subject;
        *extra_headers_p = extra_headers;
 }
 
@@ -602,8 +597,10 @@ void show_log(struct rev_info *opt)
         */
 
        if (cmit_fmt_is_mail(opt->commit_format)) {
-               log_write_email_headers(opt, commit, &ctx.subject, &extra_headers,
+               log_write_email_headers(opt, commit, &extra_headers,
                                        &ctx.need_8bit_cte);
+               ctx.rev = opt;
+               ctx.print_email_subject = 1;
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), opt->diffopt.file);
                if (opt->commit_format != CMIT_FMT_ONELINE)
index c8116e60cde34032da6f2044881d5a4970cd96fe..48f11fb740ddf670cac819cb733cbc736cc30d0e 100644 (file)
@@ -22,7 +22,6 @@ void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
                             format_decorations_extended((strbuf), (commit), (color), " (", ", ", ")")
 void show_decorations(struct rev_info *opt, struct commit *commit);
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
-                            const char **subject_p,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p);
 void load_ref_decorations(int flags);
@@ -30,5 +29,6 @@ void load_ref_decorations(int flags);
 #define FORMAT_PATCH_NAME_MAX 64
 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);
 void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *);
+void fmt_output_email_subject(struct strbuf *, struct rev_info *);
 
 #endif
index b7ff1ada3c350dce75ff09709395e97a60c96b96..62decd51cc74a9197abbda28dc8efd6f83287a6b 100644 (file)
@@ -1061,16 +1061,20 @@ static int merge_file_one(struct merge_options *o,
 }
 
 static int handle_change_delete(struct merge_options *o,
-                                const char *path,
+                                const char *path, const char *old_path,
                                 const struct object_id *o_oid, int o_mode,
-                                const struct object_id *a_oid, int a_mode,
-                                const struct object_id *b_oid, int b_mode,
+                                const struct object_id *changed_oid,
+                                int changed_mode,
+                                const char *change_branch,
+                                const char *delete_branch,
                                 const char *change, const char *change_past)
 {
-       char *renamed = NULL;
+       char *alt_path = NULL;
+       const char *update_path = path;
        int ret = 0;
+
        if (dir_in_way(path, !o->call_depth, 0)) {
-               renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2);
+               update_path = alt_path = unique_path(o, path, change_branch);
        }
 
        if (o->call_depth) {
@@ -1081,43 +1085,43 @@ static int handle_change_delete(struct merge_options *o,
                 */
                ret = remove_file_from_cache(path);
                if (!ret)
-                       ret = update_file(o, 0, o_oid, o_mode,
-                                         renamed ? renamed : path);
-       } else if (!a_oid) {
-               if (!renamed) {
-                       output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
-                              "and %s in %s. Version %s of %s left in tree."),
-                              change, path, o->branch1, change_past,
-                              o->branch2, o->branch2, path);
-                       ret = update_file(o, 0, b_oid, b_mode, path);
-               } else {
-                       output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
-                              "and %s in %s. Version %s of %s left in tree at %s."),
-                              change, path, o->branch1, change_past,
-                              o->branch2, o->branch2, path, renamed);
-                       ret = update_file(o, 0, b_oid, b_mode, renamed);
-               }
+                       ret = update_file(o, 0, o_oid, o_mode, update_path);
        } else {
-               if (!renamed) {
-                       output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
-                              "and %s in %s. Version %s of %s left in tree."),
-                              change, path, o->branch2, change_past,
-                              o->branch1, o->branch1, path);
+               if (!alt_path) {
+                       if (!old_path) {
+                               output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+                                      "and %s in %s. Version %s of %s left in tree."),
+                                      change, path, delete_branch, change_past,
+                                      change_branch, change_branch, path);
+                       } else {
+                               output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+                                      "and %s to %s in %s. Version %s of %s left in tree."),
+                                      change, old_path, delete_branch, change_past, path,
+                                      change_branch, change_branch, path);
+                       }
                } else {
-                       output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
-                              "and %s in %s. Version %s of %s left in tree at %s."),
-                              change, path, o->branch2, change_past,
-                              o->branch1, o->branch1, path, renamed);
-                       ret = update_file(o, 0, a_oid, a_mode, renamed);
+                       if (!old_path) {
+                               output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+                                      "and %s in %s. Version %s of %s left in tree at %s."),
+                                      change, path, delete_branch, change_past,
+                                      change_branch, change_branch, path, alt_path);
+                       } else {
+                               output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+                                      "and %s to %s in %s. Version %s of %s left in tree at %s."),
+                                      change, old_path, delete_branch, change_past, path,
+                                      change_branch, change_branch, path, alt_path);
+                       }
                }
                /*
-                * No need to call update_file() on path when !renamed, since
-                * that would needlessly touch path.  We could call
-                * update_file_flags() with update_cache=0 and update_wd=0,
-                * but that's a no-op.
+                * No need to call update_file() on path when change_branch ==
+                * o->branch1 && !alt_path, since that would needlessly touch
+                * path.  We could call update_file_flags() with update_cache=0
+                * and update_wd=0, but that's a no-op.
                 */
+               if (change_branch != o->branch1 || alt_path)
+                       ret = update_file(o, 0, changed_oid, changed_mode, update_path);
        }
-       free(renamed);
+       free(alt_path);
 
        return ret;
 }
@@ -1125,28 +1129,17 @@ static int handle_change_delete(struct merge_options *o,
 static int conflict_rename_delete(struct merge_options *o,
                                   struct diff_filepair *pair,
                                   const char *rename_branch,
-                                  const char *other_branch)
+                                  const char *delete_branch)
 {
        const struct diff_filespec *orig = pair->one;
        const struct diff_filespec *dest = pair->two;
-       const struct object_id *a_oid = NULL;
-       const struct object_id *b_oid = NULL;
-       int a_mode = 0;
-       int b_mode = 0;
-
-       if (rename_branch == o->branch1) {
-               a_oid = &dest->oid;
-               a_mode = dest->mode;
-       } else {
-               b_oid = &dest->oid;
-               b_mode = dest->mode;
-       }
 
        if (handle_change_delete(o,
                                 o->call_depth ? orig->path : dest->path,
+                                o->call_depth ? NULL : orig->path,
                                 &orig->oid, orig->mode,
-                                a_oid, a_mode,
-                                b_oid, b_mode,
+                                &dest->oid, dest->mode,
+                                rename_branch, delete_branch,
                                 _("rename"), _("renamed")))
                return -1;
 
@@ -1662,11 +1655,27 @@ static int handle_modify_delete(struct merge_options *o,
                                 struct object_id *a_oid, int a_mode,
                                 struct object_id *b_oid, int b_mode)
 {
+       const char *modify_branch, *delete_branch;
+       struct object_id *changed_oid;
+       int changed_mode;
+
+       if (a_oid) {
+               modify_branch = o->branch1;
+               delete_branch = o->branch2;
+               changed_oid = a_oid;
+               changed_mode = a_mode;
+       } else {
+               modify_branch = o->branch2;
+               delete_branch = o->branch1;
+               changed_oid = b_oid;
+               changed_mode = b_mode;
+       }
+
        return handle_change_delete(o,
-                                   path,
+                                   path, NULL,
                                    o_oid, o_mode,
-                                   a_oid, a_mode,
-                                   b_oid, b_mode,
+                                   changed_oid, changed_mode,
+                                   modify_branch, delete_branch,
                                    _("modify"), _("modified"));
 }
 
index 614a0067566733dc91998423f6dc05634cbedc61..f52957dcb34a3340de2d398014ad4605e7620360 100644 (file)
--- a/object.h
+++ b/object.h
@@ -29,7 +29,7 @@ struct object_array {
 /*
  * object flag allocation:
  * revision.h:      0---------10                                26
- * fetch-pack.c:    0---4
+ * fetch-pack.c:    0---5
  * walker.c:        0-2
  * upload-pack.c:       4       11----------------19
  * builtin/blame.c:               12-13
diff --git a/oidset.c b/oidset.c
new file mode 100644 (file)
index 0000000..ac169f0
--- /dev/null
+++ b/oidset.c
@@ -0,0 +1,49 @@
+#include "cache.h"
+#include "oidset.h"
+
+struct oidset_entry {
+       struct hashmap_entry hash;
+       struct object_id oid;
+};
+
+static int oidset_hashcmp(const void *va, const void *vb,
+                         const void *vkey)
+{
+       const struct oidset_entry *a = va, *b = vb;
+       const struct object_id *key = vkey;
+       return oidcmp(&a->oid, key ? key : &b->oid);
+}
+
+int oidset_contains(const struct oidset *set, const struct object_id *oid)
+{
+       struct hashmap_entry key;
+
+       if (!set->map.cmpfn)
+               return 0;
+
+       hashmap_entry_init(&key, sha1hash(oid->hash));
+       return !!hashmap_get(&set->map, &key, oid);
+}
+
+int oidset_insert(struct oidset *set, const struct object_id *oid)
+{
+       struct oidset_entry *entry;
+
+       if (!set->map.cmpfn)
+               hashmap_init(&set->map, oidset_hashcmp, 0);
+
+       if (oidset_contains(set, oid))
+               return 1;
+
+       entry = xmalloc(sizeof(*entry));
+       hashmap_entry_init(&entry->hash, sha1hash(oid->hash));
+       oidcpy(&entry->oid, oid);
+
+       hashmap_add(&set->map, entry);
+       return 0;
+}
+
+void oidset_clear(struct oidset *set)
+{
+       hashmap_free(&set->map, 1);
+}
diff --git a/oidset.h b/oidset.h
new file mode 100644 (file)
index 0000000..b7eaab5
--- /dev/null
+++ b/oidset.h
@@ -0,0 +1,45 @@
+#ifndef OIDSET_H
+#define OIDSET_H
+
+/**
+ * This API is similar to sha1-array, in that it maintains a set of object ids
+ * in a memory-efficient way. The major differences are:
+ *
+ *   1. It uses a hash, so we can do online duplicate removal, rather than
+ *      sort-and-uniq at the end. This can reduce memory footprint if you have
+ *      a large list of oids with many duplicates.
+ *
+ *   2. The per-unique-oid memory footprint is slightly higher due to hash
+ *      table overhead.
+ */
+
+/**
+ * A single oidset; should be zero-initialized (or use OIDSET_INIT).
+ */
+struct oidset {
+       struct hashmap map;
+};
+
+#define OIDSET_INIT { { NULL } }
+
+/**
+ * Returns true iff `set` contains `oid`.
+ */
+int oidset_contains(const struct oidset *set, const struct object_id *oid);
+
+/**
+ * Insert the oid into the set; a copy is made, so "oid" does not need
+ * to persist after this function is called.
+ *
+ * Returns 1 if the oid was already in the set, 0 otherwise. This can be used
+ * to perform an efficient check-and-add.
+ */
+int oidset_insert(struct oidset *set, const struct object_id *oid);
+
+/**
+ * Remove all entries from the oidset, freeing any resources associated with
+ * it.
+ */
+void oidset_clear(struct oidset *set);
+
+#endif /* OIDSET_H */
index cc9b9a9b90ca62e7a6e638928511a465fbb9974b..03f1191659dab55b2c4c440c347101a3cdbd4650 100644 (file)
@@ -30,12 +30,16 @@ struct object_entry {
 
        /*
         * State flags for depth-first search used for analyzing delta cycles.
+        *
+        * The depth is measured in delta-links to the base (so if A is a delta
+        * against B, then A has a depth of 1, and B a depth of 0).
         */
        enum {
                DFS_NONE = 0,
                DFS_ACTIVE,
                DFS_DONE
        } dfs_state;
+       int depth;
 };
 
 struct packing_data {
index 7ababb315944d5536ec251b19937beef5def2538..b961f00c8cd0eb68c56b33aa829579e8609edba6 100644 (file)
@@ -224,6 +224,12 @@ static const char *parse_short_magic(unsigned *magic, const char *elem)
                char ch = *pos;
                int i;
 
+               /* Special case alias for '!' */
+               if (ch == '^') {
+                       *magic |= PATHSPEC_EXCLUDE;
+                       continue;
+               }
+
                if (!is_pathspec_magic(ch))
                        break;
 
@@ -516,7 +522,7 @@ void parse_pathspec(struct pathspec *pathspec,
        }
 
        pathspec->nr = n;
-       ALLOC_ARRAY(pathspec->items, n);
+       ALLOC_ARRAY(pathspec->items, n + 1);
        item = pathspec->items;
        prefixlen = prefix ? strlen(prefix) : 0;
 
@@ -540,10 +546,15 @@ void parse_pathspec(struct pathspec *pathspec,
                pathspec->magic |= item[i].magic;
        }
 
-       if (nr_exclude == n)
-               die(_("There is nothing to exclude from by :(exclude) patterns.\n"
-                     "Perhaps you forgot to add either ':/' or '.' ?"));
-
+       /*
+        * If everything is an exclude pattern, add one positive pattern
+        * that matches everyting. We allocated an extra one for this.
+        */
+       if (nr_exclude == n) {
+               int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
+               init_pathspec_item(item + n, 0, prefix, plen, "");
+               pathspec->nr++;
+       }
 
        if (pathspec->magic & PATHSPEC_MAXDEPTH) {
                if (flags & PATHSPEC_KEEP_ORDER)
index c1fe3a3ef9cba0c155d02475e24315784bd27ae4..70a4c808783bab92b4b6319e3dbd8e16641b1918 100644 (file)
@@ -53,6 +53,8 @@ static void *preload_thread(void *_data)
                        continue;
                if (ce_uptodate(ce))
                        continue;
+               if (ce_skip_worktree(ce))
+                       continue;
                if (!ce_path_match(ce, &p->pathspec, NULL))
                        continue;
                if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
index 5e683830d9d66a6a1b50a26ab8c3a8338b33198b..d0f86f5d85cab6c470871cdd6e5ead526259bf6a 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1607,8 +1607,9 @@ void pp_title_line(struct pretty_print_context *pp,
                                pp->preserve_subject ? "\n" : " ");
 
        strbuf_grow(sb, title.len + 1024);
-       if (pp->subject) {
-               strbuf_addstr(sb, pp->subject);
+       if (pp->print_email_subject) {
+               if (pp->rev)
+                       fmt_output_email_subject(sb, pp->rev);
                if (needs_rfc2047_encoding(title.buf, title.len, RFC2047_SUBJECT))
                        add_rfc2047(sb, title.buf, title.len,
                                                encoding, RFC2047_SUBJECT);
@@ -1818,7 +1819,7 @@ void pretty_print_commit(struct pretty_print_context *pp,
        }
 
        pp_header(pp, encoding, commit, &msg, sb);
-       if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
+       if (pp->fmt != CMIT_FMT_ONELINE && !pp->print_email_subject) {
                strbuf_addch(sb, '\n');
        }
 
index 76a88c573f7895bbf388ab4335faa1b86c3975d3..29378caa05336efe15cdf2a9473ca6cbe2bcfa14 100644 (file)
@@ -243,21 +243,18 @@ void stop_progress_msg(struct progress **p_progress, const char *msg)
        *p_progress = NULL;
        if (progress->last_value != -1) {
                /* Force the last update */
-               char buf[128], *bufp;
-               size_t len = strlen(msg) + 5;
+               char *buf;
                struct throughput *tp = progress->throughput;
 
-               bufp = (len < sizeof(buf)) ? buf : xmallocz(len);
                if (tp) {
                        unsigned int rate = !tp->avg_misecs ? 0 :
                                        tp->avg_bytes / tp->avg_misecs;
                        throughput_string(&tp->display, tp->curr_total, rate);
                }
                progress_update = 1;
-               xsnprintf(bufp, len + 1, ", %s.\n", msg);
-               display(progress, progress->last_value, bufp);
-               if (buf != bufp)
-                       free(bufp);
+               buf = xstrfmt(", %s.\n", msg);
+               display(progress, progress->last_value, buf);
+               free(buf);
        }
        clear_progress_signal();
        if (progress->throughput)
index 3820b21cc75f5e7fd4b1b1851be0aaa9e3268d7d..1ec0fb8391ba6be716e76c688a0f9e2f0208adc2 100644 (file)
 #include "git-compat-util.h"
 #include "version.h"
 #include "trailer.h"
+#include "wt-status.h"
+
+static struct ref_msg {
+       const char *gone;
+       const char *ahead;
+       const char *behind;
+       const char *ahead_behind;
+} msgs = {
+        /* Untranslated plumbing messages: */
+       "gone",
+       "ahead %d",
+       "behind %d",
+       "ahead %d, behind %d"
+};
+
+void setup_ref_filter_porcelain_msg(void)
+{
+       msgs.gone = _("gone");
+       msgs.ahead = _("ahead %d");
+       msgs.behind = _("behind %d");
+       msgs.ahead_behind = _("ahead %d, behind %d");
+}
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status;
 
 struct align {
        align_type position;
        unsigned int width;
 };
 
+struct if_then_else {
+       cmp_status cmp_status;
+       const char *str;
+       unsigned int then_atom_seen : 1,
+               else_atom_seen : 1,
+               condition_satisfied : 1;
+};
+
+struct refname_atom {
+       enum { R_NORMAL, R_SHORT, R_LSTRIP, R_RSTRIP } option;
+       int lstrip, rstrip;
+};
+
 /*
  * An atom is a valid field atom listed below, possibly prefixed with
  * a "*" to denote deref_tag().
@@ -38,13 +74,24 @@ static struct used_atom {
        union {
                char color[COLOR_MAXLEN];
                struct align align;
-               enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT }
-                       remote_ref;
+               struct {
+                       enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option;
+                       struct refname_atom refname;
+                       unsigned int nobracket : 1;
+               } remote_ref;
                struct {
                        enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
                        unsigned int nlines;
                } contents;
-               enum { O_FULL, O_SHORT } objectname;
+               struct {
+                       cmp_status cmp_status;
+                       const char *str;
+               } if_then_else;
+               struct {
+                       enum { O_FULL, O_LENGTH, O_SHORT } option;
+                       unsigned int length;
+               } objectname;
+               struct refname_atom refname;
        } u;
 } *used_atom;
 static int used_atom_cnt, need_tagged, need_symref;
@@ -58,18 +105,58 @@ static void color_atom_parser(struct used_atom *atom, const char *color_value)
                die(_("unrecognized color: %%(color:%s)"), color_value);
 }
 
-static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
+static void refname_atom_parser_internal(struct refname_atom *atom,
+                                        const char *arg, const char *name)
 {
        if (!arg)
-               atom->u.remote_ref = RR_NORMAL;
+               atom->option = R_NORMAL;
        else if (!strcmp(arg, "short"))
-               atom->u.remote_ref = RR_SHORTEN;
-       else if (!strcmp(arg, "track"))
-               atom->u.remote_ref = RR_TRACK;
-       else if (!strcmp(arg, "trackshort"))
-               atom->u.remote_ref = RR_TRACKSHORT;
-       else
-               die(_("unrecognized format: %%(%s)"), atom->name);
+               atom->option = R_SHORT;
+       else if (skip_prefix(arg, "lstrip=", &arg) ||
+                skip_prefix(arg, "strip=", &arg)) {
+               atom->option = R_LSTRIP;
+               if (strtol_i(arg, 10, &atom->lstrip))
+                       die(_("Integer value expected refname:lstrip=%s"), arg);
+       } else if (skip_prefix(arg, "rstrip=", &arg)) {
+               atom->option = R_RSTRIP;
+               if (strtol_i(arg, 10, &atom->rstrip))
+                       die(_("Integer value expected refname:rstrip=%s"), arg);
+       } else
+               die(_("unrecognized %%(%s) argument: %s"), name, arg);
+}
+
+static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
+{
+       struct string_list params = STRING_LIST_INIT_DUP;
+       int i;
+
+       if (!arg) {
+               atom->u.remote_ref.option = RR_REF;
+               refname_atom_parser_internal(&atom->u.remote_ref.refname,
+                                            arg, atom->name);
+               return;
+       }
+
+       atom->u.remote_ref.nobracket = 0;
+       string_list_split(&params, arg, ',', -1);
+
+       for (i = 0; i < params.nr; i++) {
+               const char *s = params.items[i].string;
+
+               if (!strcmp(s, "track"))
+                       atom->u.remote_ref.option = RR_TRACK;
+               else if (!strcmp(s, "trackshort"))
+                       atom->u.remote_ref.option = RR_TRACKSHORT;
+               else if (!strcmp(s, "nobracket"))
+                       atom->u.remote_ref.nobracket = 1;
+               else {
+                       atom->u.remote_ref.option = RR_REF;
+                       refname_atom_parser_internal(&atom->u.remote_ref.refname,
+                                                    arg, atom->name);
+               }
+       }
+
+       string_list_clear(&params, 0);
 }
 
 static void body_atom_parser(struct used_atom *atom, const char *arg)
@@ -116,13 +203,25 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg)
 static void objectname_atom_parser(struct used_atom *atom, const char *arg)
 {
        if (!arg)
-               atom->u.objectname = O_FULL;
+               atom->u.objectname.option = O_FULL;
        else if (!strcmp(arg, "short"))
-               atom->u.objectname = O_SHORT;
-       else
+               atom->u.objectname.option = O_SHORT;
+       else if (skip_prefix(arg, "short=", &arg)) {
+               atom->u.objectname.option = O_LENGTH;
+               if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
+                   atom->u.objectname.length == 0)
+                       die(_("positive value expected objectname:short=%s"), arg);
+               if (atom->u.objectname.length < MINIMUM_ABBREV)
+                       atom->u.objectname.length = MINIMUM_ABBREV;
+       } else
                die(_("unrecognized %%(objectname) argument: %s"), arg);
 }
 
+static void refname_atom_parser(struct used_atom *atom, const char *arg)
+{
+       return refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
+}
+
 static align_type parse_align_position(const char *s)
 {
        if (!strcmp(s, "right"))
@@ -173,12 +272,27 @@ static void align_atom_parser(struct used_atom *atom, const char *arg)
        string_list_clear(&params, 0);
 }
 
+static void if_atom_parser(struct used_atom *atom, const char *arg)
+{
+       if (!arg) {
+               atom->u.if_then_else.cmp_status = COMPARE_NONE;
+               return;
+       } else if (skip_prefix(arg, "equals=", &atom->u.if_then_else.str)) {
+               atom->u.if_then_else.cmp_status = COMPARE_EQUAL;
+       } else if (skip_prefix(arg, "notequals=", &atom->u.if_then_else.str)) {
+               atom->u.if_then_else.cmp_status = COMPARE_UNEQUAL;
+       } else {
+               die(_("unrecognized %%(if) argument: %s"), arg);
+       }
+}
+
+
 static struct {
        const char *name;
        cmp_type cmp_type;
        void (*parser)(struct used_atom *atom, const char *arg);
 } valid_atom[] = {
-       { "refname" },
+       { "refname" , FIELD_STR, refname_atom_parser },
        { "objecttype" },
        { "objectsize", FIELD_ULONG },
        { "objectname", FIELD_STR, objectname_atom_parser },
@@ -208,12 +322,15 @@ static struct {
        { "contents", FIELD_STR, contents_atom_parser },
        { "upstream", FIELD_STR, remote_ref_atom_parser },
        { "push", FIELD_STR, remote_ref_atom_parser },
-       { "symref" },
+       { "symref", FIELD_STR, refname_atom_parser },
        { "flag" },
        { "HEAD" },
        { "color", FIELD_STR, color_atom_parser },
        { "align", FIELD_STR, align_atom_parser },
        { "end" },
+       { "if", FIELD_STR, if_atom_parser },
+       { "then" },
+       { "else" },
 };
 
 #define REF_FORMATTING_STATE_INIT  { 0, NULL }
@@ -221,7 +338,7 @@ static struct {
 struct ref_formatting_stack {
        struct ref_formatting_stack *prev;
        struct strbuf output;
-       void (*at_end)(struct ref_formatting_stack *stack);
+       void (*at_end)(struct ref_formatting_stack **stack);
        void *at_end_data;
 };
 
@@ -232,11 +349,9 @@ struct ref_formatting_state {
 
 struct atom_value {
        const char *s;
-       union {
-               struct align align;
-       } u;
        void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
        unsigned long ul; /* used for sorting when not FIELD_STR */
+       struct used_atom *atom;
 };
 
 /*
@@ -293,7 +408,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
                valid_atom[i].parser(&used_atom[at], arg);
        if (*atom == '*')
                need_tagged = 1;
-       if (!strcmp(used_atom[at].name, "symref"))
+       if (!strcmp(valid_atom[i].name, "symref"))
                need_symref = 1;
        return at;
 }
@@ -354,13 +469,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
        *stack = prev;
 }
 
-static void end_align_handler(struct ref_formatting_stack *stack)
+static void end_align_handler(struct ref_formatting_stack **stack)
 {
-       struct align *align = (struct align *)stack->at_end_data;
+       struct ref_formatting_stack *cur = *stack;
+       struct align *align = (struct align *)cur->at_end_data;
        struct strbuf s = STRBUF_INIT;
 
-       strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
-       strbuf_swap(&stack->output, &s);
+       strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
+       strbuf_swap(&cur->output, &s);
        strbuf_release(&s);
 }
 
@@ -371,7 +487,115 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s
        push_stack_element(&state->stack);
        new = state->stack;
        new->at_end = end_align_handler;
-       new->at_end_data = &atomv->u.align;
+       new->at_end_data = &atomv->atom->u.align;
+}
+
+static void if_then_else_handler(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *cur = *stack;
+       struct ref_formatting_stack *prev = cur->prev;
+       struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data;
+
+       if (!if_then_else->then_atom_seen)
+               die(_("format: %%(if) atom used without a %%(then) atom"));
+
+       if (if_then_else->else_atom_seen) {
+               /*
+                * There is an %(else) atom: we need to drop one state from the
+                * stack, either the %(else) branch if the condition is satisfied, or
+                * the %(then) branch if it isn't.
+                */
+               if (if_then_else->condition_satisfied) {
+                       strbuf_reset(&cur->output);
+                       pop_stack_element(&cur);
+               } else {
+                       strbuf_swap(&cur->output, &prev->output);
+                       strbuf_reset(&cur->output);
+                       pop_stack_element(&cur);
+               }
+       } else if (!if_then_else->condition_satisfied) {
+               /*
+                * No %(else) atom: just drop the %(then) branch if the
+                * condition is not satisfied.
+                */
+               strbuf_reset(&cur->output);
+       }
+
+       *stack = cur;
+       free(if_then_else);
+}
+
+static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *new;
+       struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
+
+       if_then_else->str = atomv->atom->u.if_then_else.str;
+       if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
+
+       push_stack_element(&state->stack);
+       new = state->stack;
+       new->at_end = if_then_else_handler;
+       new->at_end_data = if_then_else;
+}
+
+static int is_empty(const char *s)
+{
+       while (*s != '\0') {
+               if (!isspace(*s))
+                       return 0;
+               s++;
+       }
+       return 1;
+}
+
+static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *cur = state->stack;
+       struct if_then_else *if_then_else = NULL;
+
+       if (cur->at_end == if_then_else_handler)
+               if_then_else = (struct if_then_else *)cur->at_end_data;
+       if (!if_then_else)
+               die(_("format: %%(then) atom used without an %%(if) atom"));
+       if (if_then_else->then_atom_seen)
+               die(_("format: %%(then) atom used more than once"));
+       if (if_then_else->else_atom_seen)
+               die(_("format: %%(then) atom used after %%(else)"));
+       if_then_else->then_atom_seen = 1;
+       /*
+        * If the 'equals' or 'notequals' attribute is used then
+        * perform the required comparison. If not, only non-empty
+        * strings satisfy the 'if' condition.
+        */
+       if (if_then_else->cmp_status == COMPARE_EQUAL) {
+               if (!strcmp(if_then_else->str, cur->output.buf))
+                       if_then_else->condition_satisfied = 1;
+       } else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
+               if (strcmp(if_then_else->str, cur->output.buf))
+                       if_then_else->condition_satisfied = 1;
+       } else if (cur->output.len && !is_empty(cur->output.buf))
+               if_then_else->condition_satisfied = 1;
+       strbuf_reset(&cur->output);
+}
+
+static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *prev = state->stack;
+       struct if_then_else *if_then_else = NULL;
+
+       if (prev->at_end == if_then_else_handler)
+               if_then_else = (struct if_then_else *)prev->at_end_data;
+       if (!if_then_else)
+               die(_("format: %%(else) atom used without an %%(if) atom"));
+       if (!if_then_else->then_atom_seen)
+               die(_("format: %%(else) atom used without a %%(then) atom"));
+       if (if_then_else->else_atom_seen)
+               die(_("format: %%(else) atom used more than once"));
+       if_then_else->else_atom_seen = 1;
+       push_stack_element(&state->stack);
+       state->stack->at_end_data = prev->at_end_data;
+       state->stack->at_end = prev->at_end;
 }
 
 static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
@@ -381,14 +605,17 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
 
        if (!current->at_end)
                die(_("format: %%(end) atom used without corresponding atom"));
-       current->at_end(current);
+       current->at_end(&state->stack);
+
+       /*  Stack may have been popped within at_end(), hence reset the current pointer */
+       current = state->stack;
 
        /*
         * Perform quote formatting when the stack element is that of
         * a supporting atom. If nested then perform quote formatting
         * only on the topmost supporting atom.
         */
-       if (!state->stack->prev->prev) {
+       if (!current->prev->prev) {
                quote_formatting(&s, current->output.buf, state->quote_style);
                strbuf_swap(&current->output, &s);
        }
@@ -465,12 +692,15 @@ static int grab_objectname(const char *name, const unsigned char *sha1,
                           struct atom_value *v, struct used_atom *atom)
 {
        if (starts_with(name, "objectname")) {
-               if (atom->u.objectname == O_SHORT) {
+               if (atom->u.objectname.option == O_SHORT) {
                        v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
                        return 1;
-               } else if (atom->u.objectname == O_FULL) {
+               } else if (atom->u.objectname.option == O_FULL) {
                        v->s = xstrdup(sha1_to_hex(sha1));
                        return 1;
+               } else if (atom->u.objectname.option == O_LENGTH) {
+                       v->s = xstrdup(find_unique_abbrev(sha1, atom->u.objectname.length));
+                       return 1;
                } else
                        die("BUG: unknown %%(objectname) option");
        }
@@ -887,50 +1117,108 @@ static inline char *copy_advance(char *dst, const char *src)
        return dst;
 }
 
-static const char *strip_ref_components(const char *refname, const char *nr_arg)
+static const char *lstrip_ref_components(const char *refname, int len)
 {
-       char *end;
-       long nr = strtol(nr_arg, &end, 10);
-       long remaining = nr;
+       long remaining = len;
        const char *start = refname;
 
-       if (nr < 1 || *end != '\0')
-               die(_(":strip= requires a positive integer argument"));
+       if (len < 0) {
+               int i;
+               const char *p = refname;
+
+               /* Find total no of '/' separated path-components */
+               for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
+                       ;
+               /*
+                * The number of components we need to strip is now
+                * the total minus the components to be left (Plus one
+                * because we count the number of '/', but the number
+                * of components is one more than the no of '/').
+                */
+               remaining = i + len + 1;
+       }
 
-       while (remaining) {
+       while (remaining > 0) {
                switch (*start++) {
                case '\0':
-                       die(_("ref '%s' does not have %ld components to :strip"),
-                           refname, nr);
+                       return "";
                case '/':
                        remaining--;
                        break;
                }
        }
+
        return start;
 }
 
+static const char *rstrip_ref_components(const char *refname, int len)
+{
+       long remaining = len;
+       char *start = xstrdup(refname);
+
+       if (len < 0) {
+               int i;
+               const char *p = refname;
+
+               /* Find total no of '/' separated path-components */
+               for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
+                       ;
+               /*
+                * The number of components we need to strip is now
+                * the total minus the components to be left (Plus one
+                * because we count the number of '/', but the number
+                * of components is one more than the no of '/').
+                */
+               remaining = i + len + 1;
+       }
+
+       while (remaining-- > 0) {
+               char *p = strrchr(start, '/');
+               if (p == NULL)
+                       return "";
+               else
+                       p[0] = '\0';
+       }
+       return start;
+}
+
+static const char *show_ref(struct refname_atom *atom, const char *refname)
+{
+       if (atom->option == R_SHORT)
+               return shorten_unambiguous_ref(refname, warn_ambiguous_refs);
+       else if (atom->option == R_LSTRIP)
+               return lstrip_ref_components(refname, atom->lstrip);
+       else if (atom->option == R_RSTRIP)
+               return rstrip_ref_components(refname, atom->rstrip);
+       else
+               return refname;
+}
+
 static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                                    struct branch *branch, const char **s)
 {
        int num_ours, num_theirs;
-       if (atom->u.remote_ref == RR_SHORTEN)
-               *s = shorten_unambiguous_ref(refname, warn_ambiguous_refs);
-       else if (atom->u.remote_ref == RR_TRACK) {
+       if (atom->u.remote_ref.option == RR_REF)
+               *s = show_ref(&atom->u.remote_ref.refname, refname);
+       else if (atom->u.remote_ref.option == RR_TRACK) {
                if (stat_tracking_info(branch, &num_ours,
-                                      &num_theirs, NULL))
-                       return;
-
-               if (!num_ours && !num_theirs)
+                                      &num_theirs, NULL)) {
+                       *s = xstrdup(msgs.gone);
+               } else if (!num_ours && !num_theirs)
                        *s = "";
                else if (!num_ours)
-                       *s = xstrfmt("[behind %d]", num_theirs);
+                       *s = xstrfmt(msgs.behind, num_theirs);
                else if (!num_theirs)
-                       *s = xstrfmt("[ahead %d]", num_ours);
+                       *s = xstrfmt(msgs.ahead, num_ours);
                else
-                       *s = xstrfmt("[ahead %d, behind %d]",
+                       *s = xstrfmt(msgs.ahead_behind,
                                     num_ours, num_theirs);
-       } else if (atom->u.remote_ref == RR_TRACKSHORT) {
+               if (!atom->u.remote_ref.nobracket && *s[0]) {
+                       const char *to_free = *s;
+                       *s = xstrfmt("[%s]", *s);
+                       free((void *)to_free);
+               }
+       } else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
                if (stat_tracking_info(branch, &num_ours,
                                       &num_theirs, NULL))
                        return;
@@ -943,8 +1231,56 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                        *s = ">";
                else
                        *s = "<>";
-       } else /* RR_NORMAL */
-               *s = refname;
+       } else
+               die("BUG: unhandled RR_* enum");
+}
+
+char *get_head_description(void)
+{
+       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);
+}
+
+static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref)
+{
+       if (!ref->symref)
+               return "";
+       else
+               return show_ref(&atom->u.refname, ref->symref);
+}
+
+static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref)
+{
+       if (ref->kind & FILTER_REFS_DETACHED_HEAD)
+               return get_head_description();
+       return show_ref(&atom->u.refname, ref->refname);
 }
 
 /*
@@ -975,10 +1311,10 @@ static void populate_value(struct ref_array_item *ref)
                struct atom_value *v = &ref->value[i];
                int deref = 0;
                const char *refname;
-               const char *formatp;
                struct branch *branch = NULL;
 
                v->handler = append_atom;
+               v->atom = atom;
 
                if (*name == '*') {
                        deref = 1;
@@ -986,9 +1322,9 @@ static void populate_value(struct ref_array_item *ref)
                }
 
                if (starts_with(name, "refname"))
-                       refname = ref->refname;
+                       refname = get_refname(atom, ref);
                else if (starts_with(name, "symref"))
-                       refname = ref->symref ? ref->symref : "";
+                       refname = get_symref(atom, ref);
                else if (starts_with(name, "upstream")) {
                        const char *branch_name;
                        /* only local branches may have an upstream */
@@ -1043,30 +1379,27 @@ static void populate_value(struct ref_array_item *ref)
                                v->s = " ";
                        continue;
                } else if (starts_with(name, "align")) {
-                       v->u.align = atom->u.align;
                        v->handler = align_atom_handler;
                        continue;
                } else if (!strcmp(name, "end")) {
                        v->handler = end_atom_handler;
                        continue;
+               } else if (starts_with(name, "if")) {
+                       const char *s;
+
+                       if (skip_prefix(name, "if:", &s))
+                               v->s = xstrdup(s);
+                       v->handler = if_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "then")) {
+                       v->handler = then_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "else")) {
+                       v->handler = else_atom_handler;
+                       continue;
                } else
                        continue;
 
-               formatp = strchr(name, ':');
-               if (formatp) {
-                       const char *arg;
-
-                       formatp++;
-                       if (!strcmp(formatp, "short"))
-                               refname = shorten_unambiguous_ref(refname,
-                                                     warn_ambiguous_refs);
-                       else if (skip_prefix(formatp, "strip=", &arg))
-                               refname = strip_ref_components(refname, arg);
-                       else
-                               die(_("unknown %.*s format %s"),
-                                   (int)(formatp - name), name, formatp);
-               }
-
                if (!deref)
                        v->s = refname;
                else
@@ -1635,10 +1968,10 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
        }
 }
 
-void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
+void format_ref_array_item(struct ref_array_item *info, const char *format,
+                          int quote_style, struct strbuf *final_buf)
 {
        const char *cp, *sp, *ep;
-       struct strbuf *final_buf;
        struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
 
        state.quote_style = quote_style;
@@ -1668,9 +2001,17 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
        }
        if (state.stack->prev)
                die(_("format: %%(end) atom missing"));
-       final_buf = &state.stack->output;
-       fwrite(final_buf->buf, 1, final_buf->len, stdout);
+       strbuf_addbuf(final_buf, &state.stack->output);
        pop_stack_element(&state.stack);
+}
+
+void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
+{
+       struct strbuf final_buf = STRBUF_INIT;
+
+       format_ref_array_item(info, format, quote_style, &final_buf);
+       fwrite(final_buf.buf, 1, final_buf.len, stdout);
+       strbuf_release(&final_buf);
        putchar('\n');
 }
 
index 7b05592baf00661a08f973d0a218f7e0afd99453..154e24c405fc9526ab1279b1c515271f0d2cfd7b 100644 (file)
@@ -100,6 +100,9 @@ int parse_ref_filter_atom(const char *atom, const char *ep);
 int verify_ref_format(const char *format);
 /*  Sort the given ref_array as per the ref_sorting provided */
 void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
+/*  Based on the given format and quote_style, fill the strbuf */
+void format_ref_array_item(struct ref_array_item *info, const char *format,
+                          int quote_style, struct strbuf *final_buf);
 /*  Print the ref using the given format and quote_style */
 void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style);
 /*  Callback function for parsing the sort option */
@@ -108,6 +111,10 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
 struct ref_sorting *ref_default_sorting(void);
 /*  Function to parse --merged and --no-merged options */
 int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
+/*  Get the current HEAD's description */
+char *get_head_description(void);
+/*  Set up translated strings in the output. */
+void setup_ref_filter_porcelain_msg(void);
 
 /*
  * Print a single ref, outside of any ref-filter. Note that the
diff --git a/refs.c b/refs.c
index cd36b64ed93f821936fd7ffb68e60ce384119898..b0d58948a5b5bb1a96c43f0363408aab070bd1a9 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -3,6 +3,7 @@
  */
 
 #include "cache.h"
+#include "hashmap.h"
 #include "lockfile.h"
 #include "refs.h"
 #include "refs/refs-internal.h"
@@ -404,7 +405,7 @@ int refname_match(const char *abbrev_name, const char *full_name)
 static char *substitute_branch_name(const char **string, int *len)
 {
        struct strbuf buf = STRBUF_INIT;
-       int ret = interpret_branch_name(*string, *len, &buf);
+       int ret = interpret_branch_name(*string, *len, &buf, 0);
 
        if (ret == *len) {
                size_t size;
@@ -591,8 +592,8 @@ static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1
        return 0;
 }
 
-int delete_ref(const char *refname, const unsigned char *old_sha1,
-              unsigned int flags)
+int delete_ref(const char *msg, const char *refname,
+              const unsigned char *old_sha1, unsigned int flags)
 {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
@@ -603,7 +604,7 @@ int delete_ref(const char *refname, const unsigned char *old_sha1,
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_delete(transaction, refname, old_sha1,
-                                  flags, NULL, &err) ||
+                                  flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
                ref_transaction_free(transaction);
@@ -1034,10 +1035,10 @@ static struct string_list *hide_refs;
 
 int parse_hide_refs_config(const char *var, const char *value, const char *section)
 {
+       const char *key;
        if (!strcmp("transfer.hiderefs", var) ||
-           /* NEEDSWORK: use parse_config_key() once both are merged */
-           (starts_with(var, section) && var[strlen(section)] == '.' &&
-            !strcmp(var + strlen(section), ".hiderefs"))) {
+           (!parse_config_key(var, section, NULL, NULL, &key) &&
+            !strcmp(key, "hiderefs"))) {
                char *ref;
                int len;
 
@@ -1234,10 +1235,10 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
 }
 
 /* This function needs to return a meaningful errno on failure */
-static const char *resolve_ref_recursively(struct ref_store *refs,
-                                          const char *refname,
-                                          int resolve_flags,
-                                          unsigned char *sha1, int *flags)
+const char *resolve_ref_recursively(struct ref_store *refs,
+                                   const char *refname,
+                                   int resolve_flags,
+                                   unsigned char *sha1, int *flags)
 {
        static struct strbuf sb_refname = STRBUF_INIT;
        int unused_flags;
@@ -1357,62 +1358,102 @@ int resolve_gitlink_ref(const char *submodule, const char *refname,
        return 0;
 }
 
+struct submodule_hash_entry
+{
+       struct hashmap_entry ent; /* must be the first member! */
+
+       struct ref_store *refs;
+
+       /* NUL-terminated name of submodule: */
+       char submodule[FLEX_ARRAY];
+};
+
+static int submodule_hash_cmp(const void *entry, const void *entry_or_key,
+                             const void *keydata)
+{
+       const struct submodule_hash_entry *e1 = entry, *e2 = entry_or_key;
+       const char *submodule = keydata ? keydata : e2->submodule;
+
+       return strcmp(e1->submodule, submodule);
+}
+
+static struct submodule_hash_entry *alloc_submodule_hash_entry(
+               const char *submodule, struct ref_store *refs)
+{
+       struct submodule_hash_entry *entry;
+
+       FLEX_ALLOC_STR(entry, submodule, submodule);
+       hashmap_entry_init(entry, strhash(submodule));
+       entry->refs = refs;
+       return entry;
+}
+
 /* A pointer to the ref_store for the main repository: */
 static struct ref_store *main_ref_store;
 
-/* A linked list of ref_stores for submodules: */
-static struct ref_store *submodule_ref_stores;
+/* A hashmap of ref_stores, stored by submodule name: */
+static struct hashmap submodule_ref_stores;
 
-void base_ref_store_init(struct ref_store *refs,
-                        const struct ref_storage_be *be,
-                        const char *submodule)
+/*
+ * Return the ref_store instance for the specified submodule (or the
+ * main repository if submodule is NULL). If that ref_store hasn't
+ * been initialized yet, return NULL.
+ */
+static struct ref_store *lookup_ref_store(const char *submodule)
+{
+       struct submodule_hash_entry *entry;
+
+       if (!submodule)
+               return main_ref_store;
+
+       if (!submodule_ref_stores.tablesize)
+               /* It's initialized on demand in register_ref_store(). */
+               return NULL;
+
+       entry = hashmap_get_from_hash(&submodule_ref_stores,
+                                     strhash(submodule), submodule);
+       return entry ? entry->refs : NULL;
+}
+
+/*
+ * Register the specified ref_store to be the one that should be used
+ * for submodule (or the main repository if submodule is NULL). It is
+ * a fatal error to call this function twice for the same submodule.
+ */
+static void register_ref_store(struct ref_store *refs, const char *submodule)
 {
-       refs->be = be;
        if (!submodule) {
                if (main_ref_store)
                        die("BUG: main_ref_store initialized twice");
 
-               refs->submodule = "";
-               refs->next = NULL;
                main_ref_store = refs;
        } else {
-               if (lookup_ref_store(submodule))
+               if (!submodule_ref_stores.tablesize)
+                       hashmap_init(&submodule_ref_stores, submodule_hash_cmp, 0);
+
+               if (hashmap_put(&submodule_ref_stores,
+                               alloc_submodule_hash_entry(submodule, refs)))
                        die("BUG: ref_store for submodule '%s' initialized twice",
                            submodule);
-
-               refs->submodule = xstrdup(submodule);
-               refs->next = submodule_ref_stores;
-               submodule_ref_stores = refs;
        }
 }
 
-struct ref_store *ref_store_init(const char *submodule)
+/*
+ * Create, record, and return a ref_store instance for the specified
+ * submodule (or the main repository if submodule is NULL).
+ */
+static struct ref_store *ref_store_init(const char *submodule)
 {
        const char *be_name = "files";
        struct ref_storage_be *be = find_ref_storage_backend(be_name);
+       struct ref_store *refs;
 
        if (!be)
                die("BUG: reference backend %s is unknown", be_name);
 
-       if (!submodule || !*submodule)
-               return be->init(NULL);
-       else
-               return be->init(submodule);
-}
-
-struct ref_store *lookup_ref_store(const char *submodule)
-{
-       struct ref_store *refs;
-
-       if (!submodule || !*submodule)
-               return main_ref_store;
-
-       for (refs = submodule_ref_stores; refs; refs = refs->next) {
-               if (!strcmp(submodule, refs->submodule))
-                       return refs;
-       }
-
-       return NULL;
+       refs = be->init(submodule);
+       register_ref_store(refs, submodule);
+       return refs;
 }
 
 struct ref_store *get_ref_store(const char *submodule)
@@ -1440,10 +1481,10 @@ struct ref_store *get_ref_store(const char *submodule)
        return refs;
 }
 
-void assert_main_repository(struct ref_store *refs, const char *caller)
+void base_ref_store_init(struct ref_store *refs,
+                        const struct ref_storage_be *be)
 {
-       if (*refs->submodule)
-               die("BUG: %s called for a submodule", caller);
+       refs->be = be;
 }
 
 /* backend functions */
diff --git a/refs.h b/refs.h
index 9fbff90e79bfc45fada65651cd01765b17376249..e529f4c3a88a79488df4b610b65a1c9d9c18c4a0 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -276,8 +276,8 @@ int reflog_exists(const char *refname);
  * exists, regardless of its old value. It is an error for old_sha1 to
  * be NULL_SHA1. flags is passed through to ref_transaction_delete().
  */
-int delete_ref(const char *refname, const unsigned char *old_sha1,
-              unsigned int flags);
+int delete_ref(const char *msg, const char *refname,
+              const unsigned char *old_sha1, unsigned int flags);
 
 /*
  * Delete the specified references. If there are any problems, emit
@@ -334,7 +334,8 @@ int create_symref(const char *refname, const char *target, const char *logmsg);
  * $GIT_DIR points to.
  * Return 0 if successful, non-zero otherwise.
  * */
-int set_worktree_head_symref(const char *gitdir, const char *target);
+int set_worktree_head_symref(const char *gitdir, const char *target,
+                            const char *logmsg);
 
 enum action_on_err {
        UPDATE_REFS_MSG_ON_ERR,
index c041d4ba21a8fe70eb6d8277358e7e3f29a69bfb..b42df147c9c4335b0e47201f751e7e05204e4b01 100644 (file)
@@ -912,6 +912,14 @@ struct packed_ref_cache {
  */
 struct files_ref_store {
        struct ref_store base;
+
+       /*
+        * The name of the submodule represented by this object, or
+        * NULL if it represents the main repository's reference
+        * store:
+        */
+       const char *submodule;
+
        struct ref_entry *loose;
        struct packed_ref_cache *packed;
 };
@@ -972,11 +980,24 @@ static struct ref_store *files_ref_store_create(const char *submodule)
        struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
        struct ref_store *ref_store = (struct ref_store *)refs;
 
-       base_ref_store_init(ref_store, &refs_be_files, submodule);
+       base_ref_store_init(ref_store, &refs_be_files);
+
+       refs->submodule = xstrdup_or_null(submodule);
 
        return ref_store;
 }
 
+/*
+ * Die if refs is for a submodule (i.e., not for the main repository).
+ * caller is used in any necessary error messages.
+ */
+static void files_assert_main_repository(struct files_ref_store *refs,
+                                        const char *caller)
+{
+       if (refs->submodule)
+               die("BUG: %s called for a submodule", caller);
+}
+
 /*
  * Downcast ref_store to files_ref_store. Die if ref_store is not a
  * files_ref_store. If submodule_allowed is not true, then also die if
@@ -987,14 +1008,18 @@ static struct files_ref_store *files_downcast(
                struct ref_store *ref_store, int submodule_allowed,
                const char *caller)
 {
+       struct files_ref_store *refs;
+
        if (ref_store->be != &refs_be_files)
                die("BUG: ref_store is type \"%s\" not \"files\" in %s",
                    ref_store->be->name, caller);
 
+       refs = (struct files_ref_store *)ref_store;
+
        if (!submodule_allowed)
-               assert_main_repository(ref_store, caller);
+               files_assert_main_repository(refs, caller);
 
-       return (struct files_ref_store *)ref_store;
+       return refs;
 }
 
 /* The length of a peeled reference line in packed-refs, including EOL: */
@@ -1133,8 +1158,8 @@ static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *ref
 {
        char *packed_refs_file;
 
-       if (*refs->base.submodule)
-               packed_refs_file = git_pathdup_submodule(refs->base.submodule,
+       if (refs->submodule)
+               packed_refs_file = git_pathdup_submodule(refs->submodule,
                                                         "packed-refs");
        else
                packed_refs_file = git_pathdup("packed-refs");
@@ -1203,8 +1228,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
        size_t path_baselen;
        int err = 0;
 
-       if (*refs->base.submodule)
-               err = strbuf_git_path_submodule(&path, refs->base.submodule, "%s", dirname);
+       if (refs->submodule)
+               err = strbuf_git_path_submodule(&path, refs->submodule, "%s", dirname);
        else
                strbuf_git_path(&path, "%s", dirname);
        path_baselen = path.len;
@@ -1242,20 +1267,10 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
                                         create_dir_entry(refs, refname.buf,
                                                          refname.len, 1));
                } else {
-                       int read_ok;
-
-                       if (*refs->base.submodule) {
-                               hashclr(sha1);
-                               flag = 0;
-                               read_ok = !resolve_gitlink_ref(refs->base.submodule,
-                                                              refname.buf, sha1);
-                       } else {
-                               read_ok = !read_ref_full(refname.buf,
-                                                        RESOLVE_REF_READING,
-                                                        sha1, &flag);
-                       }
-
-                       if (!read_ok) {
+                       if (!resolve_ref_recursively(&refs->base,
+                                                    refname.buf,
+                                                    RESOLVE_REF_READING,
+                                                    sha1, &flag)) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        } else if (is_null_sha1(sha1)) {
@@ -1358,8 +1373,8 @@ static int files_read_raw_ref(struct ref_store *ref_store,
        *type = 0;
        strbuf_reset(&sb_path);
 
-       if (*refs->base.submodule)
-               strbuf_git_path_submodule(&sb_path, refs->base.submodule, "%s", refname);
+       if (refs->submodule)
+               strbuf_git_path_submodule(&sb_path, refs->submodule, "%s", refname);
        else
                strbuf_git_path(&sb_path, "%s", refname);
 
@@ -1540,7 +1555,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
        int ret = TRANSACTION_GENERIC_ERROR;
 
        assert(err);
-       assert_main_repository(&refs->base, "lock_raw_ref");
+       files_assert_main_repository(refs, "lock_raw_ref");
 
        *type = 0;
 
@@ -1985,6 +2000,13 @@ static int remove_empty_directories(struct strbuf *path)
        return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
 }
 
+static int create_reflock(const char *path, void *cb)
+{
+       struct lock_file *lk = cb;
+
+       return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0;
+}
+
 /*
  * Locks a ref returning the lock on success and NULL on failure.
  * On failure errno is set to something meaningful.
@@ -2000,13 +2022,11 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
        int last_errno = 0;
-       int lflags = LOCK_NO_DEREF;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
        int resolve_flags = RESOLVE_REF_NO_RECURSE;
-       int attempts_remaining = 3;
        int resolved;
 
-       assert_main_repository(&refs->base, "lock_ref_sha1_basic");
+       files_assert_main_repository(refs, "lock_ref_sha1_basic");
        assert(err);
 
        lock = xcalloc(1, sizeof(struct ref_lock));
@@ -2068,35 +2088,12 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
 
        lock->ref_name = xstrdup(refname);
 
- retry:
-       switch (safe_create_leading_directories_const(ref_file.buf)) {
-       case SCLD_OK:
-               break; /* success */
-       case SCLD_VANISHED:
-               if (--attempts_remaining > 0)
-                       goto retry;
-               /* fall through */
-       default:
+       if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
                last_errno = errno;
-               strbuf_addf(err, "unable to create directory for '%s'",
-                           ref_file.buf);
+               unable_to_lock_message(ref_file.buf, errno, err);
                goto error_return;
        }
 
-       if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
-               last_errno = errno;
-               if (errno == ENOENT && --attempts_remaining > 0)
-                       /*
-                        * Maybe somebody just deleted one of the
-                        * directories leading to ref_file.  Try
-                        * again:
-                        */
-                       goto retry;
-               else {
-                       unable_to_lock_message(ref_file.buf, errno, err);
-                       goto error_return;
-               }
-       }
        if (verify_lock(lock, old_sha1, mustexist, err)) {
                last_errno = errno;
                goto error_return;
@@ -2152,7 +2149,7 @@ static int lock_packed_refs(struct files_ref_store *refs, int flags)
        static int timeout_value = 1000;
        struct packed_ref_cache *packed_ref_cache;
 
-       assert_main_repository(&refs->base, "lock_packed_refs");
+       files_assert_main_repository(refs, "lock_packed_refs");
 
        if (!timeout_configured) {
                git_config_get_int("core.packedrefstimeout", &timeout_value);
@@ -2190,7 +2187,7 @@ static int commit_packed_refs(struct files_ref_store *refs)
        int save_errno = 0;
        FILE *out;
 
-       assert_main_repository(&refs->base, "commit_packed_refs");
+       files_assert_main_repository(refs, "commit_packed_refs");
 
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
@@ -2223,7 +2220,7 @@ static void rollback_packed_refs(struct files_ref_store *refs)
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(refs);
 
-       assert_main_repository(&refs->base, "rollback_packed_refs");
+       files_assert_main_repository(refs, "rollback_packed_refs");
 
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
@@ -2298,15 +2295,25 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
        return 0;
 }
 
+enum {
+       REMOVE_EMPTY_PARENTS_REF = 0x01,
+       REMOVE_EMPTY_PARENTS_REFLOG = 0x02
+};
+
 /*
- * Remove empty parents, but spare refs/ and immediate subdirs.
- * Note: munges *name.
+ * Remove empty parent directories associated with the specified
+ * reference and/or its reflog, but spare [logs/]refs/ and immediate
+ * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or
+ * REMOVE_EMPTY_PARENTS_REFLOG.
  */
-static void try_remove_empty_parents(char *name)
+static void try_remove_empty_parents(const char *refname, unsigned int flags)
 {
+       struct strbuf buf = STRBUF_INIT;
        char *p, *q;
        int i;
-       p = name;
+
+       strbuf_addstr(&buf, refname);
+       p = buf.buf;
        for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
                while (*p && *p != '/')
                        p++;
@@ -2314,19 +2321,23 @@ static void try_remove_empty_parents(char *name)
                while (*p == '/')
                        p++;
        }
-       for (q = p; *q; q++)
-               ;
-       while (1) {
+       q = buf.buf + buf.len;
+       while (flags & (REMOVE_EMPTY_PARENTS_REF | REMOVE_EMPTY_PARENTS_REFLOG)) {
                while (q > p && *q != '/')
                        q--;
                while (q > p && *(q-1) == '/')
                        q--;
                if (q == p)
                        break;
-               *q = '\0';
-               if (rmdir(git_path("%s", name)))
-                       break;
+               strbuf_setlen(&buf, q - buf.buf);
+               if ((flags & REMOVE_EMPTY_PARENTS_REF) &&
+                   rmdir(git_path("%s", buf.buf)))
+                       flags &= ~REMOVE_EMPTY_PARENTS_REF;
+               if ((flags & REMOVE_EMPTY_PARENTS_REFLOG) &&
+                   rmdir(git_path("logs/%s", buf.buf)))
+                       flags &= ~REMOVE_EMPTY_PARENTS_REFLOG;
        }
+       strbuf_release(&buf);
 }
 
 /* make sure nobody touched the ref, and unlink */
@@ -2350,7 +2361,6 @@ static void prune_ref(struct ref_to_prune *r)
        }
        ref_transaction_free(transaction);
        strbuf_release(&err);
-       try_remove_empty_parents(r->name);
 }
 
 static void prune_refs(struct ref_to_prune *r)
@@ -2397,7 +2407,7 @@ static int repack_without_refs(struct files_ref_store *refs,
        struct string_list_item *refname;
        int ret, needs_repacking = 0, removed = 0;
 
-       assert_main_repository(&refs->base, "repack_without_refs");
+       files_assert_main_repository(refs, "repack_without_refs");
        assert(err);
 
        /* Look for a packed ref */
@@ -2439,24 +2449,6 @@ static int repack_without_refs(struct files_ref_store *refs,
        return ret;
 }
 
-static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
-{
-       assert(err);
-
-       if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
-               /*
-                * loose.  The loose file name is the same as the
-                * lockfile name, minus ".lock":
-                */
-               char *loose_filename = get_locked_file_path(lock->lk);
-               int res = unlink_or_msg(loose_filename, err);
-               free(loose_filename);
-               if (res)
-                       return 1;
-       }
-       return 0;
-}
-
 static int files_delete_refs(struct ref_store *ref_store,
                             struct string_list *refnames, unsigned int flags)
 {
@@ -2489,7 +2481,7 @@ static int files_delete_refs(struct ref_store *ref_store,
        for (i = 0; i < refnames->nr; i++) {
                const char *refname = refnames->items[i].string;
 
-               if (delete_ref(refname, NULL, flags))
+               if (delete_ref(NULL, refname, NULL, flags))
                        result |= error(_("could not remove reference %s"), refname);
        }
 
@@ -2507,55 +2499,43 @@ static int files_delete_refs(struct ref_store *ref_store,
  */
 #define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
 
-static int rename_tmp_log(const char *newrefname)
+static int rename_tmp_log_callback(const char *path, void *cb)
 {
-       int attempts_remaining = 4;
-       struct strbuf path = STRBUF_INIT;
-       int ret = -1;
+       int *true_errno = cb;
 
- retry:
-       strbuf_reset(&path);
-       strbuf_git_path(&path, "logs/%s", newrefname);
-       switch (safe_create_leading_directories_const(path.buf)) {
-       case SCLD_OK:
-               break; /* success */
-       case SCLD_VANISHED:
-               if (--attempts_remaining > 0)
-                       goto retry;
-               /* fall through */
-       default:
-               error("unable to create directory for %s", newrefname);
-               goto out;
+       if (rename(git_path(TMP_RENAMED_LOG), path)) {
+               /*
+                * rename(a, b) when b is an existing directory ought
+                * to result in ISDIR, but Solaris 5.8 gives ENOTDIR.
+                * Sheesh. Record the true errno for error reporting,
+                * but report EISDIR to raceproof_create_file() so
+                * that it knows to retry.
+                */
+               *true_errno = errno;
+               if (errno == ENOTDIR)
+                       errno = EISDIR;
+               return -1;
+       } else {
+               return 0;
        }
+}
 
-       if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
-               if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
-                       /*
-                        * rename(a, b) when b is an existing
-                        * directory ought to result in ISDIR, but
-                        * Solaris 5.8 gives ENOTDIR.  Sheesh.
-                        */
-                       if (remove_empty_directories(&path)) {
-                               error("Directory not empty: logs/%s", newrefname);
-                               goto out;
-                       }
-                       goto retry;
-               } else if (errno == ENOENT && --attempts_remaining > 0) {
-                       /*
-                        * Maybe another process just deleted one of
-                        * the directories in the path to newrefname.
-                        * Try again from the beginning.
-                        */
-                       goto retry;
-               } else {
-                       error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
-                               newrefname, strerror(errno));
-                       goto out;
-               }
+static int rename_tmp_log(const char *newrefname)
+{
+       char *path = git_pathdup("logs/%s", newrefname);
+       int ret, true_errno;
+
+       ret = raceproof_create_file(path, rename_tmp_log_callback, &true_errno);
+       if (ret) {
+               if (errno == EISDIR)
+                       error("directory not empty: %s", path);
+               else
+                       error("unable to move logfile %s to %s: %s",
+                             git_path(TMP_RENAMED_LOG), path,
+                             strerror(true_errno));
        }
-       ret = 0;
-out:
-       strbuf_release(&path);
+
+       free(path);
        return ret;
 }
 
@@ -2616,7 +2596,7 @@ static int files_rename_ref(struct ref_store *ref_store,
                return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
                        oldrefname, strerror(errno));
 
-       if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
+       if (delete_ref(logmsg, oldrefname, orig_sha1, REF_NODEREF)) {
                error("unable to delete old %s", oldrefname);
                goto rollback;
        }
@@ -2630,8 +2610,8 @@ static int files_rename_ref(struct ref_store *ref_store,
         */
        if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
                           sha1, NULL) &&
-           delete_ref(newrefname, NULL, REF_NODEREF)) {
-               if (errno==EISDIR) {
+           delete_ref(NULL, newrefname, NULL, REF_NODEREF)) {
+               if (errno == EISDIR) {
                        struct strbuf path = STRBUF_INIT;
                        int result;
 
@@ -2740,66 +2720,89 @@ static int commit_ref(struct ref_lock *lock)
        return 0;
 }
 
+static int open_or_create_logfile(const char *path, void *cb)
+{
+       int *fd = cb;
+
+       *fd = open(path, O_APPEND | O_WRONLY | O_CREAT, 0666);
+       return (*fd < 0) ? -1 : 0;
+}
+
 /*
- * Create a reflog for a ref.  If force_create = 0, the reflog will
- * only be created for certain refs (those for which
- * should_autocreate_reflog returns non-zero.  Otherwise, create it
- * regardless of the ref name.  Fill in *err and return -1 on failure.
+ * Create a reflog for a ref. If force_create = 0, only create the
+ * reflog for certain refs (those for which should_autocreate_reflog
+ * returns non-zero). Otherwise, create it regardless of the reference
+ * name. If the logfile already existed or was created, return 0 and
+ * set *logfd to the file descriptor opened for appending to the file.
+ * If no logfile exists and we decided not to create one, return 0 and
+ * set *logfd to -1. On failure, fill in *err, set *logfd to -1, and
+ * return -1.
  */
-static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
+static int log_ref_setup(const char *refname, int force_create,
+                        int *logfd, struct strbuf *err)
 {
-       int logfd, oflags = O_APPEND | O_WRONLY;
+       char *logfile = git_pathdup("logs/%s", refname);
 
-       strbuf_git_path(logfile, "logs/%s", refname);
        if (force_create || should_autocreate_reflog(refname)) {
-               if (safe_create_leading_directories(logfile->buf) < 0) {
-                       strbuf_addf(err, "unable to create directory for '%s': "
-                                   "%s", logfile->buf, strerror(errno));
-                       return -1;
-               }
-               oflags |= O_CREAT;
-       }
-
-       logfd = open(logfile->buf, oflags, 0666);
-       if (logfd < 0) {
-               if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
-                       return 0;
+               if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) {
+                       if (errno == ENOENT)
+                               strbuf_addf(err, "unable to create directory for '%s': "
+                                           "%s", logfile, strerror(errno));
+                       else if (errno == EISDIR)
+                               strbuf_addf(err, "there are still logs under '%s'",
+                                           logfile);
+                       else
+                               strbuf_addf(err, "unable to append to '%s': %s",
+                                           logfile, strerror(errno));
 
-               if (errno == EISDIR) {
-                       if (remove_empty_directories(logfile)) {
-                               strbuf_addf(err, "there are still logs under "
-                                           "'%s'", logfile->buf);
-                               return -1;
-                       }
-                       logfd = open(logfile->buf, oflags, 0666);
+                       goto error;
                }
-
-               if (logfd < 0) {
-                       strbuf_addf(err, "unable to append to '%s': %s",
-                                   logfile->buf, strerror(errno));
-                       return -1;
+       } else {
+               *logfd = open(logfile, O_APPEND | O_WRONLY, 0666);
+               if (*logfd < 0) {
+                       if (errno == ENOENT || errno == EISDIR) {
+                               /*
+                                * The logfile doesn't already exist,
+                                * but that is not an error; it only
+                                * means that we won't write log
+                                * entries to it.
+                                */
+                               ;
+                       } else {
+                               strbuf_addf(err, "unable to append to '%s': %s",
+                                           logfile, strerror(errno));
+                               goto error;
+                       }
                }
        }
 
-       adjust_shared_perm(logfile->buf);
-       close(logfd);
+       if (*logfd >= 0)
+               adjust_shared_perm(logfile);
+
+       free(logfile);
        return 0;
-}
 
+error:
+       free(logfile);
+       return -1;
+}
 
 static int files_create_reflog(struct ref_store *ref_store,
                               const char *refname, int force_create,
                               struct strbuf *err)
 {
-       int ret;
-       struct strbuf sb = STRBUF_INIT;
+       int fd;
 
        /* Check validity (but we don't need the result): */
        files_downcast(ref_store, 0, "create_reflog");
 
-       ret = log_ref_setup(refname, &sb, err, force_create);
-       strbuf_release(&sb);
-       return ret;
+       if (log_ref_setup(refname, force_create, &fd, err))
+               return -1;
+
+       if (fd >= 0)
+               close(fd);
+
+       return 0;
 }
 
 static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
@@ -2828,59 +2831,43 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
        return 0;
 }
 
-static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
-                          const unsigned char *new_sha1, const char *msg,
-                          struct strbuf *logfile, int flags,
-                          struct strbuf *err)
+int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
+                       const unsigned char *new_sha1, const char *msg,
+                       int flags, struct strbuf *err)
 {
-       int logfd, result, oflags = O_APPEND | O_WRONLY;
+       int logfd, result;
 
        if (log_all_ref_updates == LOG_REFS_UNSET)
                log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
 
-       result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
+       result = log_ref_setup(refname, flags & REF_FORCE_CREATE_REFLOG,
+                              &logfd, err);
 
        if (result)
                return result;
 
-       logfd = open(logfile->buf, oflags);
        if (logfd < 0)
                return 0;
        result = log_ref_write_fd(logfd, old_sha1, new_sha1,
                                  git_committer_info(0), msg);
        if (result) {
-               strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
-                           strerror(errno));
+               int save_errno = errno;
+
+               strbuf_addf(err, "unable to append to '%s': %s",
+                           git_path("logs/%s", refname), strerror(save_errno));
                close(logfd);
                return -1;
        }
        if (close(logfd)) {
-               strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
-                           strerror(errno));
+               int save_errno = errno;
+
+               strbuf_addf(err, "unable to append to '%s': %s",
+                           git_path("logs/%s", refname), strerror(save_errno));
                return -1;
        }
        return 0;
 }
 
-static int log_ref_write(const char *refname, const unsigned char *old_sha1,
-                        const unsigned char *new_sha1, const char *msg,
-                        int flags, struct strbuf *err)
-{
-       return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags,
-                                  err);
-}
-
-int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
-                       const unsigned char *new_sha1, const char *msg,
-                       int flags, struct strbuf *err)
-{
-       struct strbuf sb = STRBUF_INIT;
-       int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
-                                 err);
-       strbuf_release(&sb);
-       return ret;
-}
-
 /*
  * Write sha1 into the open lockfile, then close the lockfile. On
  * errors, rollback the lockfile, fill in *err and
@@ -2930,10 +2917,11 @@ static int commit_ref_update(struct files_ref_store *refs,
                             const unsigned char *sha1, const char *logmsg,
                             struct strbuf *err)
 {
-       assert_main_repository(&refs->base, "commit_ref_update");
+       files_assert_main_repository(refs, "commit_ref_update");
 
        clear_loose_ref_cache(refs);
-       if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) {
+       if (files_log_ref_write(lock->ref_name, lock->old_oid.hash, sha1,
+                               logmsg, 0, err)) {
                char *old_msg = strbuf_detach(err, NULL);
                strbuf_addf(err, "cannot update the ref '%s': %s",
                            lock->ref_name, old_msg);
@@ -2964,7 +2952,7 @@ static int commit_ref_update(struct files_ref_store *refs,
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name)) {
                        struct strbuf log_err = STRBUF_INIT;
-                       if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
+                       if (files_log_ref_write("HEAD", lock->old_oid.hash, sha1,
                                          logmsg, 0, &log_err)) {
                                error("%s", log_err.buf);
                                strbuf_release(&log_err);
@@ -3003,7 +2991,8 @@ static void update_symref_reflog(struct ref_lock *lock, const char *refname,
        struct strbuf err = STRBUF_INIT;
        unsigned char new_sha1[20];
        if (logmsg && !read_ref(target, new_sha1) &&
-           log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) {
+           files_log_ref_write(refname, lock->old_oid.hash, new_sha1,
+                               logmsg, 0, &err)) {
                error("%s", err.buf);
                strbuf_release(&err);
        }
@@ -3055,7 +3044,7 @@ static int files_create_symref(struct ref_store *ref_store,
        return ret;
 }
 
-int set_worktree_head_symref(const char *gitdir, const char *target)
+int set_worktree_head_symref(const char *gitdir, const char *target, const char *logmsg)
 {
        static struct lock_file head_lock;
        struct ref_lock *lock;
@@ -3083,7 +3072,7 @@ int set_worktree_head_symref(const char *gitdir, const char *target)
        lock->lk = &head_lock;
        lock->ref_name = xstrdup(head_rel);
 
-       ret = create_symref_locked(lock, head_rel, target, NULL);
+       ret = create_symref_locked(lock, head_rel, target, logmsg);
 
        unlock_ref(lock); /* will free lock */
        strbuf_release(&head_path);
@@ -3560,7 +3549,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
        int ret;
        struct ref_lock *lock;
 
-       assert_main_repository(&refs->base, "lock_ref_for_update");
+       files_assert_main_repository(refs, "lock_ref_for_update");
 
        if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1))
                update->flags |= REF_DELETING;
@@ -3778,9 +3767,11 @@ static int files_transaction_commit(struct ref_store *ref_store,
 
                if (update->flags & REF_NEEDS_COMMIT ||
                    update->flags & REF_LOG_ONLY) {
-                       if (log_ref_write(lock->ref_name, lock->old_oid.hash,
-                                         update->new_sha1,
-                                         update->msg, update->flags, err)) {
+                       if (files_log_ref_write(lock->ref_name,
+                                               lock->old_oid.hash,
+                                               update->new_sha1,
+                                               update->msg, update->flags,
+                                               err)) {
                                char *old_msg = strbuf_detach(err, NULL);
 
                                strbuf_addf(err, "cannot update the ref '%s': %s",
@@ -3810,9 +3801,14 @@ static int files_transaction_commit(struct ref_store *ref_store,
 
                if (update->flags & REF_DELETING &&
                    !(update->flags & REF_LOG_ONLY)) {
-                       if (delete_ref_loose(lock, update->type, err)) {
-                               ret = TRANSACTION_GENERIC_ERROR;
-                               goto cleanup;
+                       if (!(update->type & REF_ISPACKED) ||
+                           update->type & REF_ISSYMREF) {
+                               /* It is a loose reference. */
+                               if (unlink_or_msg(git_path("%s", lock->ref_name), err)) {
+                                       ret = TRANSACTION_GENERIC_ERROR;
+                                       goto cleanup;
+                               }
+                               update->flags |= REF_DELETED_LOOSE;
                        }
 
                        if (!(update->flags & REF_ISPRUNING))
@@ -3825,16 +3821,38 @@ static int files_transaction_commit(struct ref_store *ref_store,
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
-       for_each_string_list_item(ref_to_delete, &refs_to_delete)
-               unlink_or_warn(git_path("logs/%s", ref_to_delete->string));
+
+       /* Delete the reflogs of any references that were deleted: */
+       for_each_string_list_item(ref_to_delete, &refs_to_delete) {
+               if (!unlink_or_warn(git_path("logs/%s", ref_to_delete->string)))
+                       try_remove_empty_parents(ref_to_delete->string,
+                                                REMOVE_EMPTY_PARENTS_REFLOG);
+       }
+
        clear_loose_ref_cache(refs);
 
 cleanup:
        transaction->state = REF_TRANSACTION_CLOSED;
 
-       for (i = 0; i < transaction->nr; i++)
-               if (transaction->updates[i]->backend_data)
-                       unlock_ref(transaction->updates[i]->backend_data);
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
+               struct ref_lock *lock = update->backend_data;
+
+               if (lock)
+                       unlock_ref(lock);
+
+               if (update->flags & REF_DELETED_LOOSE) {
+                       /*
+                        * The loose reference was deleted. Delete any
+                        * empty parent directories. (Note that this
+                        * can only work because we have already
+                        * removed the lockfile.)
+                        */
+                       try_remove_empty_parents(update->refname,
+                                                REMOVE_EMPTY_PARENTS_REF);
+               }
+       }
+
        string_list_clear(&refs_to_delete, 0);
        free(head_ref);
        string_list_clear(&affected_refnames, 0);
index 25444cf5b0f6448e4a1ada56d48f09ab3017a759..fa93c9a32ec4c52b9cd6597b9c2ebb4b4501206b 100644 (file)
  */
 #define REF_UPDATE_VIA_HEAD 0x100
 
+/*
+ * Used as a flag in ref_update::flags when the loose reference has
+ * been deleted.
+ */
+#define REF_DELETED_LOOSE 0x200
+
 /*
  * Return true iff refname is minimally safe. "Safe" here means that
  * deleting a loose reference by this name will not do any damage, for
  * This function does not check that the reference name is legal; for
  * that, use check_refname_format().
  *
- * We consider a refname that starts with "refs/" to be safe as long
- * as any ".." components that it might contain do not escape "refs/".
- * Names that do not start with "refs/" are considered safe iff they
- * consist entirely of upper case characters and '_' (like "HEAD" and
- * "MERGE_HEAD" but not "config" or "FOO/BAR").
+ * A refname that starts with "refs/" is considered safe iff it
+ * doesn't contain any "." or ".." components or consecutive '/'
+ * characters, end with '/', or (on Windows) contain any '\'
+ * characters. Names that do not start with "refs/" are considered
+ * safe iff they consist entirely of upper case characters and '_'
+ * (like "HEAD" and "MERGE_HEAD" but not "config" or "FOO/BAR").
  */
 int refname_is_safe(const char *refname);
 
@@ -155,8 +162,9 @@ struct ref_update {
 
        /*
         * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
-        * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and
-        * REF_UPDATE_VIA_HEAD:
+        * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY,
+        * REF_UPDATE_VIA_HEAD, REF_NEEDS_COMMIT, and
+        * REF_DELETED_LOOSE:
         */
        unsigned int flags;
 
@@ -627,47 +635,14 @@ extern struct ref_storage_be refs_be_files;
 struct ref_store {
        /* The backend describing this ref_store's storage scheme: */
        const struct ref_storage_be *be;
-
-       /*
-        * The name of the submodule represented by this object, or
-        * the empty string if it represents the main repository's
-        * reference store:
-        */
-       const char *submodule;
-
-       /*
-        * Submodule reference store instances are stored in a linked
-        * list using this pointer.
-        */
-       struct ref_store *next;
 };
 
 /*
- * Fill in the generic part of refs for the specified submodule and
- * add it to our collection of reference stores.
+ * Fill in the generic part of refs and add it to our collection of
+ * reference stores.
  */
 void base_ref_store_init(struct ref_store *refs,
-                        const struct ref_storage_be *be,
-                        const char *submodule);
-
-/*
- * Create, record, and return a ref_store instance for the specified
- * submodule (or the main repository if submodule is NULL).
- *
- * For backwards compatibility, submodule=="" is treated the same as
- * submodule==NULL.
- */
-struct ref_store *ref_store_init(const char *submodule);
-
-/*
- * Return the ref_store instance for the specified submodule (or the
- * main repository if submodule is NULL). If that ref_store hasn't
- * been initialized yet, return NULL.
- *
- * For backwards compatibility, submodule=="" is treated the same as
- * submodule==NULL.
- */
-struct ref_store *lookup_ref_store(const char *submodule);
+                        const struct ref_storage_be *be);
 
 /*
  * Return the ref_store instance for the specified submodule. For the
@@ -681,10 +656,9 @@ struct ref_store *lookup_ref_store(const char *submodule);
  */
 struct ref_store *get_ref_store(const char *submodule);
 
-/*
- * Die if refs is for a submodule (i.e., not for the main repository).
- * caller is used in any necessary error messages.
- */
-void assert_main_repository(struct ref_store *refs, const char *caller);
+const char *resolve_ref_recursively(struct ref_store *refs,
+                                   const char *refname,
+                                   int resolve_flags,
+                                   unsigned char *sha1, int *flags);
 
 #endif /* REFS_REFS_INTERNAL_H */
index bf1bf2309128bf886eac16959b8162b9990c98d7..9f83fe2c4cbd477e47fc6c81eab324be7aa088c6 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -693,7 +693,7 @@ static struct remote *remote_get_1(const char *name,
                name = get_default(current_branch, &name_given);
 
        ret = make_remote(name, 0);
-       if (valid_remote_nick(name)) {
+       if (valid_remote_nick(name) && have_git_dir()) {
                if (!valid_remote(ret))
                        read_remotes_file(ret);
                if (!valid_remote(ret))
index a5bbbe0ef965c5e62be50e8ee0c03794e393cc8c..dd8c5175776baabbac2bd735e21bc859fb62a111 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -89,8 +89,13 @@ struct ref {
                force:1,
                forced_update:1,
                expect_old_sha1:1,
-               deletion:1,
-               matched:1;
+               deletion:1;
+
+       enum {
+               REF_NOT_MATCHED = 0, /* initial value */
+               REF_MATCHED,
+               REF_UNADVERTISED_NOT_ALLOWED
+       } match_status;
 
        /*
         * Order is important here, as we write to FETCH_HEAD
index b37dbec378f3ae1a33d00d591c1042c609e45fa4..771d079f6edca39f8fa0d28db9bf697a99deeea6 100644 (file)
@@ -147,7 +147,7 @@ static void add_pending_object_with_path(struct rev_info *revs,
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
                struct strbuf buf = STRBUF_INIT;
-               int len = interpret_branch_name(name, 0, &buf);
+               int len = interpret_branch_name(name, 0, &buf, 0);
                int st;
 
                if (0 < len && name[len] && buf.len)
diff --git a/setup.c b/setup.c
index 967f289f1ef07d78f4b680e1d880e2fa86215371..8f64fbdfb28fc2e487cfdba76561e5b3a25766f0 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -254,7 +254,7 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
                if (!is_absolute_path(data.buf))
                        strbuf_addf(&path, "%s/", gitdir);
                strbuf_addbuf(&path, &data);
-               strbuf_addstr(sb, real_path(path.buf));
+               strbuf_add_real_path(sb, path.buf);
                ret = 1;
        } else {
                strbuf_addstr(sb, gitdir);
@@ -698,7 +698,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
                if (offset != cwd->len && !is_absolute_path(gitdir))
-                       gitdir = real_pathdup(gitdir);
+                       gitdir = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
                return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
@@ -806,7 +806,7 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
                /* Keep entry but do not canonicalize it */
                return 1;
        } else {
-               char *real_path = real_pathdup(ceil);
+               char *real_path = real_pathdup(ceil, 0);
                if (!real_path) {
                        return 0;
                }
index ec957db5e1c2620b4a95e4f4d028f01794cef02a..2ee3c617a6b45bc0dd52b634bbecec4d011a74bf 100644 (file)
@@ -129,8 +129,10 @@ enum scld_error safe_create_leading_directories(char *path)
                *slash = '\0';
                if (!stat(path, &st)) {
                        /* path exists */
-                       if (!S_ISDIR(st.st_mode))
+                       if (!S_ISDIR(st.st_mode)) {
+                               errno = ENOTDIR;
                                ret = SCLD_EXISTS;
+                       }
                } else if (mkdir(path, 0777)) {
                        if (errno == EEXIST &&
                            !stat(path, &st) && S_ISDIR(st.st_mode))
@@ -158,13 +160,85 @@ enum scld_error safe_create_leading_directories(char *path)
 
 enum scld_error safe_create_leading_directories_const(const char *path)
 {
+       int save_errno;
        /* path points to cache entries, so xstrdup before messing with it */
        char *buf = xstrdup(path);
        enum scld_error result = safe_create_leading_directories(buf);
+
+       save_errno = errno;
        free(buf);
+       errno = save_errno;
        return result;
 }
 
+int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
+{
+       /*
+        * The number of times we will try to remove empty directories
+        * in the way of path. This is only 1 because if another
+        * process is racily creating directories that conflict with
+        * us, we don't want to fight against them.
+        */
+       int remove_directories_remaining = 1;
+
+       /*
+        * The number of times that we will try to create the
+        * directories containing path. We are willing to attempt this
+        * more than once, because another process could be trying to
+        * clean up empty directories at the same time as we are
+        * trying to create them.
+        */
+       int create_directories_remaining = 3;
+
+       /* A scratch copy of path, filled lazily if we need it: */
+       struct strbuf path_copy = STRBUF_INIT;
+
+       int ret, save_errno;
+
+       /* Sanity check: */
+       assert(*path);
+
+retry_fn:
+       ret = fn(path, cb);
+       save_errno = errno;
+       if (!ret)
+               goto out;
+
+       if (errno == EISDIR && remove_directories_remaining-- > 0) {
+               /*
+                * A directory is in the way. Maybe it is empty; try
+                * to remove it:
+                */
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+
+               if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
+                       goto retry_fn;
+       } else if (errno == ENOENT && create_directories_remaining-- > 0) {
+               /*
+                * Maybe the containing directory didn't exist, or
+                * maybe it was just deleted by a process that is
+                * racing with us to clean up empty directories. Try
+                * to create it:
+                */
+               enum scld_error scld_result;
+
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+
+               do {
+                       scld_result = safe_create_leading_directories(path_copy.buf);
+                       if (scld_result == SCLD_OK)
+                               goto retry_fn;
+               } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
+       }
+
+out:
+       strbuf_release(&path_copy);
+       errno = save_errno;
+       return ret;
+}
+
 static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1)
 {
        int i;
@@ -2532,6 +2606,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
        while (delta_stack_nr) {
                void *delta_data;
                void *base = data;
+               void *external_base = NULL;
                unsigned long delta_size, base_size = size;
                int i;
 
@@ -2558,6 +2633,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
                                      p->pack_name);
                                mark_bad_packed_object(p, base_sha1);
                                base = read_object(base_sha1, &type, &base_size);
+                               external_base = base;
                        }
                }
 
@@ -2576,6 +2652,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
                              "at offset %"PRIuMAX" from %s",
                              (uintmax_t)curpos, p->pack_name);
                        data = NULL;
+                       free(external_base);
                        continue;
                }
 
@@ -2595,6 +2672,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
                        error("failed to apply delta");
 
                free(delta_data);
+               free(external_base);
        }
 
        *final_type = type;
index 73a915ff1b3278f08ef4f327a55fe61d238f720a..26ceec1d799afad6c6831cba00ca4d0ffab7979f 100644 (file)
@@ -1176,7 +1176,8 @@ static int interpret_empty_at(const char *name, int namelen, int len, struct str
        return 1;
 }
 
-static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
+static int reinterpret(const char *name, int namelen, int len,
+                      struct strbuf *buf, unsigned allowed)
 {
        /* we have extra data, which might need further processing */
        struct strbuf tmp = STRBUF_INIT;
@@ -1184,7 +1185,7 @@ static int reinterpret(const char *name, int namelen, int len, struct strbuf *bu
        int ret;
 
        strbuf_add(buf, name + len, namelen - len);
-       ret = interpret_branch_name(buf->buf, buf->len, &tmp);
+       ret = interpret_branch_name(buf->buf, buf->len, &tmp, allowed);
        /* that data was not interpreted, remove our cruft */
        if (ret < 0) {
                strbuf_setlen(buf, used);
@@ -1205,11 +1206,27 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref)
        free(s);
 }
 
+static int branch_interpret_allowed(const char *refname, unsigned allowed)
+{
+       if (!allowed)
+               return 1;
+
+       if ((allowed & INTERPRET_BRANCH_LOCAL) &&
+           starts_with(refname, "refs/heads/"))
+               return 1;
+       if ((allowed & INTERPRET_BRANCH_REMOTE) &&
+           starts_with(refname, "refs/remotes/"))
+               return 1;
+
+       return 0;
+}
+
 static int interpret_branch_mark(const char *name, int namelen,
                                 int at, struct strbuf *buf,
                                 int (*get_mark)(const char *, int),
                                 const char *(*get_data)(struct branch *,
-                                                        struct strbuf *))
+                                                        struct strbuf *),
+                                unsigned allowed)
 {
        int len;
        struct branch *branch;
@@ -1234,64 +1251,55 @@ static int interpret_branch_mark(const char *name, int namelen,
        if (!value)
                die("%s", err.buf);
 
+       if (!branch_interpret_allowed(value, allowed))
+               return -1;
+
        set_shortened_ref(buf, value);
        return len + at;
 }
 
-/*
- * This reads short-hand syntax that not only evaluates to a commit
- * object name, but also can act as if the end user spelled the name
- * of the branch from the command line.
- *
- * - "@{-N}" finds the name of the Nth previous branch we were on, and
- *   places the name of the branch in the given buf and returns the
- *   number of characters parsed if successful.
- *
- * - "<branch>@{upstream}" finds the name of the other ref that
- *   <branch> is configured to merge with (missing <branch> defaults
- *   to the current branch), and places the name of the branch in the
- *   given buf and returns the number of characters parsed if
- *   successful.
- *
- * If the input is not of the accepted format, it returns a negative
- * number to signal an error.
- *
- * If the input was ok but there are not N branch switches in the
- * reflog, it returns 0.
- */
-int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
+int interpret_branch_name(const char *name, int namelen, struct strbuf *buf,
+                         unsigned allowed)
 {
        char *at;
        const char *start;
-       int len = interpret_nth_prior_checkout(name, namelen, buf);
+       int len;
 
        if (!namelen)
                namelen = strlen(name);
 
-       if (!len) {
-               return len; /* syntax Ok, not enough switches */
-       } else if (len > 0) {
-               if (len == namelen)
-                       return len; /* consumed all */
-               else
-                       return reinterpret(name, namelen, len, buf);
+       if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) {
+               len = interpret_nth_prior_checkout(name, namelen, buf);
+               if (!len) {
+                       return len; /* syntax Ok, not enough switches */
+               } else if (len > 0) {
+                       if (len == namelen)
+                               return len; /* consumed all */
+                       else
+                               return reinterpret(name, namelen, len, buf, allowed);
+               }
        }
 
        for (start = name;
             (at = memchr(start, '@', namelen - (start - name)));
             start = at + 1) {
 
-               len = interpret_empty_at(name, namelen, at - name, buf);
-               if (len > 0)
-                       return reinterpret(name, namelen, len, buf);
+               if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) {
+                       len = interpret_empty_at(name, namelen, at - name, buf);
+                       if (len > 0)
+                               return reinterpret(name, namelen, len, buf,
+                                                  allowed);
+               }
 
                len = interpret_branch_mark(name, namelen, at - name, buf,
-                                           upstream_mark, branch_get_upstream);
+                                           upstream_mark, branch_get_upstream,
+                                           allowed);
                if (len > 0)
                        return len;
 
                len = interpret_branch_mark(name, namelen, at - name, buf,
-                                           push_mark, branch_get_push);
+                                           push_mark, branch_get_push,
+                                           allowed);
                if (len > 0)
                        return len;
        }
@@ -1299,22 +1307,19 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
        return -1;
 }
 
-int strbuf_branchname(struct strbuf *sb, const char *name)
+void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)
 {
        int len = strlen(name);
-       int used = interpret_branch_name(name, len, sb);
+       int used = interpret_branch_name(name, len, sb, allowed);
 
-       if (used == len)
-               return 0;
        if (used < 0)
                used = 0;
        strbuf_add(sb, name + used, len - used);
-       return len;
 }
 
 int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
 {
-       strbuf_branchname(sb, name);
+       strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
        if (name[0] == '-')
                return -1;
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
index 8fec6579f70cae4bc6b330e4e0ca3e7cc595f366..ace58e7367300975e118660ed2004c7d0885481c 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -707,6 +707,17 @@ void strbuf_add_absolute_path(struct strbuf *sb, const char *path)
        strbuf_addstr(sb, path);
 }
 
+void strbuf_add_real_path(struct strbuf *sb, const char *path)
+{
+       if (sb->len) {
+               struct strbuf resolved = STRBUF_INIT;
+               strbuf_realpath(&resolved, path, 1);
+               strbuf_addbuf(sb, &resolved);
+               strbuf_release(&resolved);
+       } else
+               strbuf_realpath(sb, path, 1);
+}
+
 int printf_ln(const char *fmt, ...)
 {
        int ret;
index cf1b5409e7c39eba4981a0a23e8250249a9202f1..80047b1bb7b826699deff3e8c2590a2a00a5c121 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -441,6 +441,20 @@ extern int strbuf_getcwd(struct strbuf *sb);
  */
 extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path);
 
+/**
+ * Canonize `path` (make it absolute, resolve symlinks, remove extra
+ * slashes) and append it to `sb`.  Die with an informative error
+ * message if there is a problem.
+ *
+ * The directory part of `path` (i.e., everything up to the last
+ * dir_sep) must denote a valid, existing directory, but the last
+ * component need not exist.
+ *
+ * Callers that don't mind links should use the more lightweight
+ * strbuf_add_absolute_path() instead.
+ */
+extern void strbuf_add_real_path(struct strbuf *sb, const char *path);
+
 
 /**
  * Normalize in-place the path contained in the strbuf. See
@@ -560,7 +574,26 @@ static inline void strbuf_complete_line(struct strbuf *sb)
        strbuf_complete(sb, '\n');
 }
 
-extern int strbuf_branchname(struct strbuf *sb, const char *name);
+/*
+ * Copy "name" to "sb", expanding any special @-marks as handled by
+ * interpret_branch_name(). The result is a non-qualified branch name
+ * (so "foo" or "origin/master" instead of "refs/heads/foo" or
+ * "refs/remotes/origin/master").
+ *
+ * Note that the resulting name may not be a syntactically valid refname.
+ *
+ * If "allowed" is non-zero, restrict the set of allowed expansions. See
+ * interpret_branch_name() for details.
+ */
+extern void strbuf_branchname(struct strbuf *sb, const char *name,
+                             unsigned allowed);
+
+/*
+ * Like strbuf_branchname() above, but confirm that the result is
+ * syntactically valid to be used as a local branch name in refs/heads/.
+ *
+ * The return value is "0" if the result is valid, and "-1" otherwise.
+ */
 extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
 
 extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
index 3b98766a6bcfe983e5f94e84edd6fcec7ce74f8d..0a2831d846d27e694ada4bc557a7d54a8bf2d8df 100644 (file)
@@ -1403,7 +1403,7 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
                /* If it is an actual gitfile, it doesn't need migration. */
                return;
 
-       real_old_git_dir = real_pathdup(old_git_dir);
+       real_old_git_dir = real_pathdup(old_git_dir, 1);
 
        sub = submodule_from_path(null_sha1, path);
        if (!sub)
@@ -1412,7 +1412,7 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
        new_git_dir = git_path("modules/%s", sub->name);
        if (safe_create_leading_directories_const(new_git_dir) < 0)
                die(_("could not create directory '%s'"), new_git_dir);
-       real_new_git_dir = real_pathdup(new_git_dir);
+       real_new_git_dir = real_pathdup(new_git_dir, 1);
 
        if (!prefix)
                prefix = get_super_prefix();
@@ -1472,14 +1472,14 @@ void absorb_git_dir_into_superproject(const char *prefix,
                new_git_dir = git_path("modules/%s", sub->name);
                if (safe_create_leading_directories_const(new_git_dir) < 0)
                        die(_("could not create directory '%s'"), new_git_dir);
-               real_new_git_dir = real_pathdup(new_git_dir);
+               real_new_git_dir = real_pathdup(new_git_dir, 1);
                connect_work_tree_and_git_dir(path, real_new_git_dir);
 
                free(real_new_git_dir);
        } else {
                /* Is it already absorbed into the superprojects git dir? */
-               char *real_sub_git_dir = real_pathdup(sub_git_dir);
-               char *real_common_git_dir = real_pathdup(get_git_common_dir());
+               char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
+               char *real_common_git_dir = real_pathdup(get_git_common_dir(), 1);
 
                if (!starts_with(real_sub_git_dir, real_common_git_dir))
                        relocate_single_git_dir_into_superproject(prefix, path);
index d5dab5a94f1d881b8b103f6290ba91e4ec596ec6..006d2a8152dc499f3588b130470285703b60dc92 100644 (file)
@@ -110,7 +110,12 @@ perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || {
 }
 
 perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || {
-       skip_all='skipping gitweb tests, CGI module unusable'
+       skip_all='skipping gitweb tests, CGI & CGI::Util & CGI::Carp modules not available'
+       test_done
+}
+
+perl -mTime::HiRes -e 0 >/dev/null 2>&1 || {
+       skip_all='skipping gitweb tests, Time::HiRes module not available'
        test_done
 }
 
index 69174c6e3110d5e214c048aceccf07232b813ce7..0642ae7e6ef0fbbd9e7eacdebd061d258c8b4222 100644 (file)
@@ -133,6 +133,15 @@ RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302]
 RewriteRule ^/loop-redir/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-(.*) /$1 [R=302]
 RewriteRule ^/loop-redir/(.*)$ /loop-redir/x-$1 [R=302]
 
+# redir-to/502/x?y -> really-redir-to?path=502/x&qs=y which returns 502
+# redir-to/x?y -> really-redir-to?path=x&qs=y -> x?y
+RewriteCond %{QUERY_STRING} ^(.*)$
+RewriteRule ^/redir-to/(.*)$ /really-redir-to?path=$1&qs=%1 [R=302]
+RewriteCond %{QUERY_STRING} ^path=502/(.*)&qs=(.*)$
+RewriteRule ^/really-redir-to$ - [R=502,L]
+RewriteCond %{QUERY_STRING} ^path=(.*)&qs=(.*)$
+RewriteRule ^/really-redir-to$ /%1?%2 [R=302]
+
 # The first rule issues a client-side redirect to something
 # that _doesn't_ look like a git repo. The second rule is a
 # server-side rewrite, so that it turns out the odd-looking
index 16359d51ae5a58ac629391b27d6b42acfc2ddb5e..ebf172401b0ff9c280ad14991323bd8aaf85b5e0 100755 (executable)
@@ -15,7 +15,8 @@ test_perf 'rev-list --all --objects' '
 '
 
 test_expect_success 'create new unreferenced commit' '
-       commit=$(git commit-tree HEAD^{tree} -p HEAD)
+       commit=$(git commit-tree HEAD^{tree} -p HEAD) &&
+       test_export commit
 '
 
 test_perf 'rev-list $commit --not --all' '
index 15ee5d1d53391f38017cfd26bd23d94082a47365..b029586ccb2c8b3fd746a6c0bbdaca3966d42fa7 100755 (executable)
@@ -16,4 +16,9 @@ test_perf 'noop filter' '
        git filter-branch -f base..HEAD
 '
 
+test_perf 'noop prune-empty' '
+       git checkout --detach tip &&
+       git filter-branch -f --prune-empty base..HEAD
+'
+
 test_done
index 46f08ee0870ffd2b1e0eb7d159b159d25ef61ffb..ab4b8b06ae50c5e4ee7e128ded55dea7dddf7865 100644 (file)
@@ -83,7 +83,7 @@ test_perf_create_repo_from () {
        error "bug in the test script: not 2 parameters to test-create-repo"
        repo="$1"
        source="$2"
-       source_git="$(git -C "$source" rev-parse --git-dir)"
+       source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)"
        objects_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-path objects)"
        mkdir -p "$repo/.git"
        (
@@ -102,7 +102,7 @@ test_perf_create_repo_from () {
        ) &&
        (
                cd "$repo" &&
-               git init -q && {
+               "$MODERN_GIT" init -q && {
                        test_have_prereq SYMLINKS ||
                        git config core.symlinks false
                } &&
index e8adedadfdca8549b3c4f8f8a19d383a7611f4ad..c788d713ae927007866b5b73920de2674f816207 100755 (executable)
@@ -63,6 +63,9 @@ run_dirs_helper () {
                unset GIT_TEST_INSTALLED
        else
                GIT_TEST_INSTALLED="$mydir/bin-wrappers"
+               # Older versions of git lacked bin-wrappers; fallback to the
+               # files in the root.
+               test -d "$GIT_TEST_INSTALLED" || GIT_TEST_INSTALLED=$mydir
                export GIT_TEST_INSTALLED
        fi
        run_one_dir "$@"
index f0fbb425545019a6275a347a5826f6ba2977d394..f19ae4f8ccddacee84e04fa50663672f5f6174c1 100755 (executable)
@@ -13,10 +13,31 @@ attr_check () {
        test_line_count = 0 err
 }
 
+attr_check_quote () {
+
+       path="$1"
+       quoted_path="$2"
+       expect="$3"
+
+       git check-attr test -- "$path" >actual &&
+       echo "\"$quoted_path\": test: $expect" >expect &&
+       test_cmp expect actual
+
+}
+
+test_expect_success 'open-quoted pathname' '
+       echo "\"a test=a" >.gitattributes &&
+       test_must_fail attr_check a a
+'
+
+
 test_expect_success 'setup' '
        mkdir -p a/b/d a/c b &&
        (
                echo "[attr]notest !test"
+               echo "\" d \"   test=d"
+               echo " e        test=e"
+               echo " e\"      test=e"
                echo "f test=f"
                echo "a/i test=a/i"
                echo "onoff test -test"
@@ -69,6 +90,11 @@ test_expect_success 'command line checks' '
 '
 
 test_expect_success 'attribute test' '
+
+       attr_check " d " d &&
+       attr_check e e &&
+       attr_check_quote e\" e\\\" e &&
+
        attr_check f f &&
        attr_check a/f f &&
        attr_check a/c/f f &&
index e0a69402324fed3c2fd7ca9495f5735742766108..58c0b7e9b6d99a6ea8a507da89eb66f3c8780c31 100755 (executable)
@@ -56,5 +56,13 @@ test_expect_success 'merge @{-100} before checking out that many branches yet' '
        test_must_fail git merge @{-100}
 '
 
+test_expect_success 'log -g @{-1}' '
+       git checkout -b last_branch &&
+       git checkout -b new_branch &&
+       echo "last_branch@{0}" >expect &&
+       git log -g --format=%gd @{-1} >actual &&
+       test_cmp expect actual
+'
+
 test_done
 
index 923bfc5a2606588f30bebbcfd4c34788976c354c..afcca0d52c377cfabd6463f060d1b183937ba9de 100755 (executable)
@@ -1097,6 +1097,68 @@ test_expect_success 'multiple git -c appends config' '
        test_cmp expect actual
 '
 
+test_expect_success 'last one wins: two level vars' '
+
+       # sec.var and sec.VAR are the same variable, as the first
+       # and the last level of a configuration variable name is
+       # case insensitive.
+
+       echo VAL >expect &&
+
+       git -c sec.var=val -c sec.VAR=VAL config --get sec.var >actual &&
+       test_cmp expect actual &&
+       git -c SEC.var=val -c sec.var=VAL config --get sec.var >actual &&
+       test_cmp expect actual &&
+
+       git -c sec.var=val -c sec.VAR=VAL config --get SEC.var >actual &&
+       test_cmp expect actual &&
+       git -c SEC.var=val -c sec.var=VAL config --get sec.VAR >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'last one wins: three level vars' '
+
+       # v.a.r and v.A.r are not the same variable, as the middle
+       # level of a three-level configuration variable name is
+       # case sensitive.
+
+       echo val >expect &&
+       git -c v.a.r=val -c v.A.r=VAL config --get v.a.r >actual &&
+       test_cmp expect actual &&
+       git -c v.a.r=val -c v.A.r=VAL config --get V.a.R >actual &&
+       test_cmp expect actual &&
+
+       # v.a.r and V.a.R are the same variable, as the first
+       # and the last level of a configuration variable name is
+       # case insensitive.
+
+       echo VAL >expect &&
+       git -c v.a.r=val -c v.a.R=VAL config --get v.a.r >actual &&
+       test_cmp expect actual &&
+       git -c v.a.r=val -c V.a.r=VAL config --get v.a.r >actual &&
+       test_cmp expect actual &&
+       git -c v.a.r=val -c v.a.R=VAL config --get V.a.R >actual &&
+       test_cmp expect actual &&
+       git -c v.a.r=val -c V.a.r=VAL config --get V.a.R >actual &&
+       test_cmp expect actual
+'
+
+for VAR in a .a a. a.0b a."b c". a."b c".0d
+do
+       test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
+               test_must_fail git -c "$VAR=VAL" config -l
+       '
+done
+
+for VAR in a.b a."b c".d
+do
+       test_expect_success "git -c $VAR=VAL works with valid '$VAR'" '
+               echo VAL >expect &&
+               git -c "$VAR=VAL" config --get "$VAR" >actual &&
+               test_cmp expect actual
+       '
+done
+
 test_expect_success 'git -c is not confused by empty environment' '
        GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
 '
@@ -1177,6 +1239,111 @@ test_expect_success 'urlmatch' '
        test_cmp expect actual
 '
 
+test_expect_success 'urlmatch favors more specific URLs' '
+       cat >.git/config <<-\EOF &&
+       [http "https://example.com/"]
+               cookieFile = /tmp/root.txt
+       [http "https://example.com/subdirectory"]
+               cookieFile = /tmp/subdirectory.txt
+       [http "https://user@example.com/"]
+               cookieFile = /tmp/user.txt
+       [http "https://averylonguser@example.com/"]
+               cookieFile = /tmp/averylonguser.txt
+       [http "https://preceding.example.com"]
+               cookieFile = /tmp/preceding.txt
+       [http "https://*.example.com"]
+               cookieFile = /tmp/wildcard.txt
+       [http "https://*.example.com/wildcardwithsubdomain"]
+               cookieFile = /tmp/wildcardwithsubdomain.txt
+       [http "https://trailing.example.com"]
+               cookieFile = /tmp/trailing.txt
+       [http "https://user@*.example.com/"]
+               cookieFile = /tmp/wildcardwithuser.txt
+       [http "https://sub.example.com/"]
+               cookieFile = /tmp/sub.txt
+       EOF
+
+       echo http.cookiefile /tmp/root.txt >expect &&
+       git config --get-urlmatch HTTP https://example.com >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/subdirectory.txt >expect &&
+       git config --get-urlmatch HTTP https://example.com/subdirectory >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/subdirectory.txt >expect &&
+       git config --get-urlmatch HTTP https://example.com/subdirectory/nested >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/user.txt >expect &&
+       git config --get-urlmatch HTTP https://user@example.com/ >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/subdirectory.txt >expect &&
+       git config --get-urlmatch HTTP https://averylonguser@example.com/subdirectory >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/preceding.txt >expect &&
+       git config --get-urlmatch HTTP https://preceding.example.com >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/wildcard.txt >expect &&
+       git config --get-urlmatch HTTP https://wildcard.example.com >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/sub.txt >expect &&
+       git config --get-urlmatch HTTP https://sub.example.com/wildcardwithsubdomain >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/trailing.txt >expect &&
+       git config --get-urlmatch HTTP https://trailing.example.com >actual &&
+       test_cmp expect actual &&
+
+       echo http.cookiefile /tmp/sub.txt >expect &&
+       git config --get-urlmatch HTTP https://user@sub.example.com >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'urlmatch with wildcard' '
+       cat >.git/config <<-\EOF &&
+       [http]
+               sslVerify
+       [http "https://*.example.com"]
+               sslVerify = false
+               cookieFile = /tmp/cookie.txt
+       EOF
+
+       test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual &&
+       test_must_be_empty actual &&
+
+       echo true >expect &&
+       git config --bool --get-urlmatch http.SSLverify https://example.com >actual &&
+       test_cmp expect actual &&
+
+       echo true >expect &&
+       git config --bool --get-urlmatch http.SSLverify https://good-example.com >actual &&
+       test_cmp expect actual &&
+
+       echo true >expect &&
+       git config --bool --get-urlmatch http.sslverify https://deep.nested.example.com >actual &&
+       test_cmp expect actual &&
+
+       echo false >expect &&
+       git config --bool --get-urlmatch http.sslverify https://good.example.com >actual &&
+       test_cmp expect actual &&
+
+       {
+               echo http.cookiefile /tmp/cookie.txt &&
+               echo http.sslverify false
+       } >expect &&
+       git config --get-urlmatch HTTP https://good.example.com >actual &&
+       test_cmp expect actual &&
+
+       echo http.sslverify >expect &&
+       git config --get-urlmatch HTTP https://more.example.com.au >actual &&
+       test_cmp expect actual
+'
+
 # good section hygiene
 test_expect_failure 'unsetting the last key in a section removes header' '
        cat >.git/config <<-\EOF &&
index b0ffc0b5731dd746ed170604784287d3e48e9c30..825422341de5d80247d4a2ac689d6fc799f9a9a0 100755 (executable)
@@ -85,6 +85,24 @@ test_expect_success "delete $m (by HEAD)" '
 '
 rm -f .git/$m
 
+test_expect_success "deleting current branch adds message to HEAD's log" '
+       git update-ref $m $A &&
+       git symbolic-ref HEAD $m &&
+       git update-ref -m delete-$m -d $m &&
+       ! test -f .git/$m &&
+       grep "delete-$m$" .git/logs/HEAD
+'
+rm -f .git/$m
+
+test_expect_success "deleting by HEAD adds message to HEAD's log" '
+       git update-ref $m $A &&
+       git symbolic-ref HEAD $m &&
+       git update-ref -m delete-by-head -d HEAD &&
+       ! test -f .git/$m &&
+       grep "delete-by-head$" .git/logs/HEAD
+'
+rm -f .git/$m
+
 test_expect_success 'update-ref does not create reflogs by default' '
        test_when_finished "git update-ref -d $outside" &&
        git update-ref $outside $A &&
@@ -256,6 +274,33 @@ test_expect_success \
         git update-ref HEAD'" $A &&
         test $A"' = $(cat .git/'"$m"')'
 
+test_expect_success "empty directory removal" '
+       git branch d1/d2/r1 HEAD &&
+       git branch d1/r2 HEAD &&
+       test -f .git/refs/heads/d1/d2/r1 &&
+       test -f .git/logs/refs/heads/d1/d2/r1 &&
+       git branch -d d1/d2/r1 &&
+       ! test -e .git/refs/heads/d1/d2 &&
+       ! test -e .git/logs/refs/heads/d1/d2 &&
+       test -f .git/refs/heads/d1/r2 &&
+       test -f .git/logs/refs/heads/d1/r2
+'
+
+test_expect_success "symref empty directory removal" '
+       git branch e1/e2/r1 HEAD &&
+       git branch e1/r2 HEAD &&
+       git checkout e1/e2/r1 &&
+       test_when_finished "git checkout master" &&
+       test -f .git/refs/heads/e1/e2/r1 &&
+       test -f .git/logs/refs/heads/e1/e2/r1 &&
+       git update-ref -d HEAD &&
+       ! test -e .git/refs/heads/e1/e2 &&
+       ! test -e .git/logs/refs/heads/e1/e2 &&
+       test -f .git/refs/heads/e1/r2 &&
+       test -f .git/logs/refs/heads/e1/r2 &&
+       test -f .git/logs/HEAD
+'
+
 cat >expect <<EOF
 $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000      Initial Creation
 $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000      Switch
index 038e24c401451d61e9c93fb5c233d132a3d51acd..9ed8b8ccba5db702eaf7b872bd4a1e1af1fd2192 100755 (executable)
@@ -3,7 +3,7 @@
 test_description='test git rev-parse'
 . ./test-lib.sh
 
-# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir
+# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir
 test_rev_parse () {
        d=
        bare=
@@ -29,7 +29,8 @@ test_rev_parse () {
                 --is-inside-git-dir \
                 --is-inside-work-tree \
                 --show-prefix \
-                --git-dir
+                --git-dir \
+                --absolute-git-dir
        do
                test $# -eq 0 && break
                expect="$1"
@@ -62,29 +63,57 @@ test_expect_success 'setup' '
        cp -R .git repo.git
 '
 
-test_rev_parse toplevel false false true '' .git
+test_rev_parse toplevel false false true '' .git "$ROOT/.git"
 
-test_rev_parse -C .git .git/ false true false '' .
-test_rev_parse -C .git/objects .git/objects/ false true false '' "$ROOT/.git"
+test_rev_parse -C .git .git/ false true false '' . "$ROOT/.git"
+test_rev_parse -C .git/objects .git/objects/ false true false '' "$ROOT/.git" "$ROOT/.git"
 
-test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git"
+test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git" "$ROOT/.git"
 
 test_rev_parse -b t 'core.bare = true' true false false
 
 test_rev_parse -b u 'core.bare undefined' false false true
 
 
-test_rev_parse -C work -g ../.git -b f 'GIT_DIR=../.git, core.bare = false' false false true ''
+test_rev_parse -C work -g ../.git -b f 'GIT_DIR=../.git, core.bare = false' false false true '' "../.git" "$ROOT/.git"
 
 test_rev_parse -C work -g ../.git -b t 'GIT_DIR=../.git, core.bare = true' true false false ''
 
 test_rev_parse -C work -g ../.git -b u 'GIT_DIR=../.git, core.bare undefined' false false true ''
 
 
-test_rev_parse -C work -g ../repo.git -b f 'GIT_DIR=../repo.git, core.bare = false' false false true ''
+test_rev_parse -C work -g ../repo.git -b f 'GIT_DIR=../repo.git, core.bare = false' false false true '' "../repo.git" "$ROOT/repo.git"
 
 test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = true' true false false ''
 
 test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true ''
 
+test_expect_success 'git-common-dir from worktree root' '
+       echo .git >expect &&
+       git rev-parse --git-common-dir >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git-common-dir inside sub-dir' '
+       mkdir -p path/to/child &&
+       test_when_finished "rm -rf path" &&
+       echo "$(git -C path/to/child rev-parse --show-cdup).git" >expect &&
+       git -C path/to/child rev-parse --git-common-dir >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git-path from worktree root' '
+       echo .git/objects >expect &&
+       git rev-parse --git-path objects >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git-path inside sub-dir' '
+       mkdir -p path/to/child &&
+       test_when_finished "rm -rf path" &&
+       echo "$(git -C path/to/child rev-parse --show-cdup).git/objects" >expect &&
+       git -C path/to/child rev-parse --git-path objects >actual &&
+       test_cmp expect actual
+'
+
 test_done
index cc5b870e5875bab8326393aea3a8d9f944210de6..b06210ec5e8ffa30b59314b1c79ebc9bdb8f6476 100755 (executable)
@@ -423,4 +423,12 @@ test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
        )
 '
 
+test_expect_success 'error out gracefully on invalid $GIT_WORK_TREE' '
+       (
+               GIT_WORK_TREE=/.invalid/work/tree &&
+               export GIT_WORK_TREE &&
+               test_expect_code 128 git rev-parse
+       )
+'
+
 test_done
index 292a0720fccb0becca47fad2945634cbffeb89d6..6096f2c630947241a2dee410255aeebb818b14f9 100755 (executable)
@@ -200,4 +200,20 @@ EOF
        test_cmp expect actual
 '
 
+test_expect_success 'rev-parse --shared-index-path' '
+       test_create_repo split-index &&
+       (
+               cd split-index &&
+               git update-index --split-index &&
+               echo .git/sharedindex* >expect &&
+               git rev-parse --shared-index-path >actual &&
+               test_cmp expect actual &&
+               mkdir subdirectory &&
+               cd subdirectory &&
+               echo ../.git/sharedindex* >expect &&
+               git rev-parse --shared-index-path >actual &&
+               test_cmp expect actual
+       )
+'
+
 test_done
index 465eeeacd3de1971c7256c84583377b3b47d520b..848da5f3684ac5382063a64588c49a9128235cf1 100755 (executable)
@@ -14,10 +14,18 @@ test_expect_success 'rev-parse --git-common-dir on main worktree' '
        test_cmp expected actual &&
        mkdir sub &&
        git -C sub rev-parse --git-common-dir >actual2 &&
-       echo sub/.git >expected2 &&
+       echo ../.git >expected2 &&
        test_cmp expected2 actual2
 '
 
+test_expect_success 'rev-parse --git-path objects linked worktree' '
+       echo "$(git rev-parse --show-toplevel)/.git/objects" >expect &&
+       test_when_finished "rm -rf linked-tree && git worktree prune" &&
+       git worktree add --detach linked-tree master &&
+       git -C linked-tree rev-parse --git-path objects >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success '"list" all worktrees from main' '
        echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
        test_when_finished "rm -rf here && git worktree prune" &&
index 8a833f354e331966f5f927ae94ac8686dd6d166f..e36ed3b4e18e22986233f72e016b491c801117db 100755 (executable)
@@ -139,6 +139,12 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
        test $(git rev-parse --abbrev-ref HEAD) = bam
 '
 
+test_expect_success 'git branch -M baz bam should add entries to .git/logs/HEAD' '
+       msg="Branch: renamed refs/heads/baz to refs/heads/bam" &&
+       grep " 0\{40\}.*$msg$" .git/logs/HEAD &&
+       grep "^0\{40\}.*$msg$" .git/logs/HEAD
+'
+
 test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '
        git checkout master &&
        git worktree add -b baz bazdir &&
index 52283dfc8cdafabba485fa6896279d600230abf9..5778c0afe12be2495b58a5251fb9bc8dc395a143 100755 (executable)
@@ -194,7 +194,7 @@ test_expect_success 'local-branch symrefs shortened properly' '
        git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one &&
        cat >expect <<-\EOF &&
          ref-to-branch -> branch-one
-         ref-to-remote -> refs/remotes/origin/branch-one
+         ref-to-remote -> origin/branch-one
        EOF
        git branch >actual.raw &&
        grep ref-to <actual.raw >actual &&
@@ -225,4 +225,18 @@ test_expect_success 'sort branches, ignore case' '
        )
 '
 
+test_expect_success 'git branch --format option' '
+       cat >expect <<-\EOF &&
+       Refname is (HEAD detached from fromtag)
+       Refname is refs/heads/ambiguous
+       Refname is refs/heads/branch-one
+       Refname is refs/heads/branch-two
+       Refname is refs/heads/master
+       Refname is refs/heads/ref-to-branch
+       Refname is refs/heads/ref-to-remote
+       EOF
+       git branch --format="Refname is %(refname)" >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t3204-branch-name-interpretation.sh b/t/t3204-branch-name-interpretation.sh
new file mode 100755 (executable)
index 0000000..698d9cc
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/sh
+
+test_description='interpreting exotic branch name arguments
+
+Branch name arguments are usually names which are taken to be inside of
+refs/heads/, but we interpret some magic syntax like @{-1}, @{upstream}, etc.
+This script aims to check the behavior of those corner cases.
+'
+. ./test-lib.sh
+
+expect_branch() {
+       git log -1 --format=%s "$1" >actual &&
+       echo "$2" >expect &&
+       test_cmp expect actual
+}
+
+expect_deleted() {
+       test_must_fail git rev-parse --verify "$1"
+}
+
+test_expect_success 'set up repo' '
+       test_commit one &&
+       test_commit two &&
+       git remote add origin foo.git
+'
+
+test_expect_success 'update branch via @{-1}' '
+       git branch previous one &&
+
+       git checkout previous &&
+       git checkout master &&
+
+       git branch -f @{-1} two &&
+       expect_branch previous two
+'
+
+test_expect_success 'update branch via local @{upstream}' '
+       git branch local one &&
+       git branch --set-upstream-to=local &&
+
+       git branch -f @{upstream} two &&
+       expect_branch local two
+'
+
+test_expect_success 'disallow updating branch via remote @{upstream}' '
+       git update-ref refs/remotes/origin/remote one &&
+       git branch --set-upstream-to=origin/remote &&
+
+       test_must_fail git branch -f @{upstream} two
+'
+
+test_expect_success 'create branch with pseudo-qualified name' '
+       git branch refs/heads/qualified two &&
+       expect_branch refs/heads/refs/heads/qualified two
+'
+
+test_expect_success 'delete branch via @{-1}' '
+       git branch previous-del &&
+
+       git checkout previous-del &&
+       git checkout master &&
+
+       git branch -D @{-1} &&
+       expect_deleted previous-del
+'
+
+test_expect_success 'delete branch via local @{upstream}' '
+       git branch local-del &&
+       git branch --set-upstream-to=local-del &&
+
+       git branch -D @{upstream} &&
+       expect_deleted local-del
+'
+
+test_expect_success 'delete branch via remote @{upstream}' '
+       git update-ref refs/remotes/origin/remote-del two &&
+       git branch --set-upstream-to=origin/remote-del &&
+
+       git branch -r -D @{upstream} &&
+       expect_deleted origin/remote-del
+'
+
+# Note that we create two oddly named local branches here. We want to make
+# sure that we do not accidentally delete either of them, even if
+# shorten_unambiguous_ref() tweaks the name to avoid ambiguity.
+test_expect_success 'delete @{upstream} expansion matches -r option' '
+       git update-ref refs/remotes/origin/remote-del two &&
+       git branch --set-upstream-to=origin/remote-del &&
+       git update-ref refs/heads/origin/remote-del two &&
+       git update-ref refs/heads/remotes/origin/remote-del two &&
+
+       test_must_fail git branch -D @{upstream} &&
+       expect_branch refs/heads/origin/remote-del two &&
+       expect_branch refs/heads/remotes/origin/remote-del two
+'
+
+test_expect_success 'disallow deleting remote branch via @{-1}' '
+       git update-ref refs/remotes/origin/previous one &&
+
+       git checkout -b origin/previous two &&
+       git checkout master &&
+
+       test_must_fail git branch -r -D @{-1} &&
+       expect_branch refs/remotes/origin/previous one &&
+       expect_branch refs/heads/origin/previous two
+'
+
+# The thing we are testing here is that "@" is the real branch refs/heads/@,
+# and not refs/heads/HEAD. These tests should not imply that refs/heads/@ is a
+# sane thing, but it _is_ technically allowed for now. If we disallow it, these
+# can be switched to test_must_fail.
+test_expect_success 'create branch named "@"' '
+       git branch -f @ one &&
+       expect_branch refs/heads/@ one
+'
+
+test_expect_success 'delete branch named "@"' '
+       git update-ref refs/heads/@ two &&
+       git branch -D @ &&
+       expect_deleted refs/heads/@
+'
+
+test_expect_success 'checkout does not treat remote @{upstream} as a branch' '
+       git update-ref refs/remotes/origin/checkout one &&
+       git branch --set-upstream-to=origin/checkout &&
+       git update-ref refs/heads/origin/checkout two &&
+       git update-ref refs/heads/remotes/origin/checkout two &&
+
+       git checkout @{upstream} &&
+       expect_branch HEAD one
+'
+
+test_done
index e2f18d11f66afe19bf28b8218042a06f452f0aa1..33d392ba112e2c130a02d2229967842ca257e837 100755 (executable)
@@ -556,7 +556,7 @@ test_expect_success 'clean error after failed "exec"' '
        echo "edited again" > file7 &&
        git add file7 &&
        test_must_fail git rebase --continue 2>error &&
-       test_i18ngrep "You have staged changes in your working tree." error
+       test_i18ngrep "you have staged changes in your working tree" error
 '
 
 test_expect_success 'rebase a detached HEAD' '
index 5ffe78e920c4aa292a6c16e2aa8078ede4ab065c..aaa258daa376ab6d18d71a04a517840e1b55d094 100755 (executable)
@@ -394,4 +394,22 @@ test_expect_success 'diffs can be colorized' '
        grep "$(printf "\\033")" output
 '
 
+test_expect_success 'patch-mode via -i prompts for files' '
+       git reset --hard &&
+
+       echo one >file &&
+       echo two >test &&
+       git add -i <<-\EOF &&
+       patch
+       test
+
+       y
+       quit
+       EOF
+
+       echo test >expect &&
+       git diff --cached --name-only >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 2de3e18ce68a94111c8dfcca858b8515ea8919c1..89877e4b52a4aa09b1e1925005a1a09826e2c7a0 100755 (executable)
@@ -274,9 +274,7 @@ test_expect_success 'stash --invalid-option' '
        git add file2 &&
        test_must_fail git stash --invalid-option &&
        test_must_fail git stash save --invalid-option &&
-       test bar5,bar6 = $(cat file),$(cat file2) &&
-       git stash -- -message-starting-with-dash &&
-       test bar,bar2 = $(cat file),$(cat file2)
+       test bar5,bar6 = $(cat file),$(cat file2)
 '
 
 test_expect_success 'stash an added file' '
@@ -775,4 +773,138 @@ test_expect_success 'stash is not confused by partial renames' '
        test_path_is_missing file
 '
 
+test_expect_success 'push -m shows right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m "test message" &&
+       echo "stash@{0}: On master: test message" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create stores correct message' '
+       >foo &&
+       git add foo &&
+       STASH_ID=$(git stash create "create test message") &&
+       echo "On master: create test message" >expect &&
+       git show --pretty=%s -s ${STASH_ID} >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create with multiple arguments for the message' '
+       >foo &&
+       git add foo &&
+       STASH_ID=$(git stash create test untracked) &&
+       echo "On master: test untracked" >expect &&
+       git show --pretty=%s -s ${STASH_ID} >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'stash -- <pathspec> stashes and restores the file' '
+       >foo &&
+       >bar &&
+       git add foo bar &&
+       git stash push -- foo &&
+       test_path_is_file bar &&
+       test_path_is_missing foo &&
+       git stash pop &&
+       test_path_is_file foo &&
+       test_path_is_file bar
+'
+
+test_expect_success 'stash with multiple pathspec arguments' '
+       >foo &&
+       >bar &&
+       >extra &&
+       git add foo bar extra &&
+       git stash push -- foo bar &&
+       test_path_is_missing bar &&
+       test_path_is_missing foo &&
+       test_path_is_file extra &&
+       git stash pop &&
+       test_path_is_file foo &&
+       test_path_is_file bar &&
+       test_path_is_file extra
+'
+
+test_expect_success 'stash with file including $IFS character' '
+       >"foo bar" &&
+       >foo &&
+       >bar &&
+       git add foo* &&
+       git stash push -- "foo b*" &&
+       test_path_is_missing "foo bar" &&
+       test_path_is_file foo &&
+       test_path_is_file bar &&
+       git stash pop &&
+       test_path_is_file "foo bar" &&
+       test_path_is_file foo &&
+       test_path_is_file bar
+'
+
+test_expect_success 'stash with pathspec matching multiple paths' '
+       echo original >file &&
+       echo original >other-file &&
+       git commit -m "two" file other-file &&
+       echo modified >file &&
+       echo modified >other-file &&
+       git stash push -- "*file" &&
+       echo original >expect &&
+       test_cmp expect file &&
+       test_cmp expect other-file &&
+       git stash pop &&
+       echo modified >expect &&
+       test_cmp expect file &&
+       test_cmp expect other-file
+'
+
+test_expect_success 'stash push -p with pathspec shows no changes only once' '
+       >foo &&
+       git add foo &&
+       git commit -m "tmp" &&
+       git stash push -p foo >actual &&
+       echo "No local changes to save" >expect &&
+       git reset --hard HEAD~ &&
+       test_cmp expect actual
+'
+
+test_expect_success 'stash push with pathspec shows no changes when there are none' '
+       >foo &&
+       git add foo &&
+       git commit -m "tmp" &&
+       git stash push foo >actual &&
+       echo "No local changes to save" >expect &&
+       git reset --hard HEAD~ &&
+       test_cmp expect actual
+'
+
+test_expect_success 'stash push with pathspec not in the repository errors out' '
+       >untracked &&
+       test_must_fail git stash push untracked &&
+       test_path_is_file untracked
+'
+
+test_expect_success 'untracked files are left in place when -u is not given' '
+       >file &&
+       git add file &&
+       >untracked &&
+       git stash push file &&
+       test_path_is_file untracked
+'
+
+test_expect_success 'stash without verb with pathspec' '
+       >"foo bar" &&
+       >foo &&
+       >bar &&
+       git add foo* &&
+       git stash -- "foo b*" &&
+       test_path_is_missing "foo bar" &&
+       test_path_is_file foo &&
+       test_path_is_file bar &&
+       git stash pop &&
+       test_path_is_file "foo bar" &&
+       test_path_is_file foo &&
+       test_path_is_file bar
+'
+
 test_done
index f372fc8ca8e3b311b03313a46db1aa8f35de2ea9..193adc7b68d691526b804fa7ce02e1f42f21b8a4 100755 (executable)
@@ -185,4 +185,30 @@ test_expect_success 'stash save --all is stash poppable' '
        test -s .gitignore
 '
 
+test_expect_success 'stash push --include-untracked with pathspec' '
+       >foo &&
+       >bar &&
+       git stash push --include-untracked -- foo &&
+       test_path_is_file bar &&
+       test_path_is_missing foo &&
+       git stash pop &&
+       test_path_is_file bar &&
+       test_path_is_file foo
+'
+
+test_expect_success 'stash push with $IFS character' '
+       >"foo bar" &&
+       >foo &&
+       >bar &&
+       git add foo* &&
+       git stash push --include-untracked -- "foo b*" &&
+       test_path_is_missing "foo bar" &&
+       test_path_is_file foo &&
+       test_path_is_file bar &&
+       git stash pop &&
+       test_path_is_file "foo bar" &&
+       test_path_is_file foo &&
+       test_path_is_file bar
+'
+
 test_done
index 461f4bb583d081c58c8ab55bf4cedae8a361cd43..2f1737fcef185486dc626d616c37fdadc11deba1 100755 (executable)
@@ -152,4 +152,13 @@ test_expect_success 'git diff --quiet ignores stat-change only entries' '
        test_expect_code 1 git diff --quiet
 '
 
+test_expect_success 'git diff --quiet on a path that need conversion' '
+       echo "crlf.txt text=auto" >.gitattributes &&
+       printf "Hello\r\nWorld\r\n" >crlf.txt &&
+       git add .gitattributes crlf.txt &&
+
+       printf "Hello\r\nWorld\n" >crlf.txt &&
+       git diff --quiet crlf.txt
+'
+
 test_done
index 9d87777b5994910dda971b57fd67b733ee9b5398..d0377fae5c832bcd4df37f3bc2ab4a8708f70251 100755 (executable)
@@ -106,4 +106,14 @@ test_expect_success '-L with --output' '
        test_line_count = 70 log
 '
 
+test_expect_success 'range_set_union' '
+       test_seq 500 > c.c &&
+       git add c.c &&
+       git commit -m "many lines" &&
+       test_seq 1000 > c.c &&
+       git add c.c &&
+       git commit -m "modify many lines" &&
+       git log $(for x in $(test_seq 200); do echo -L $((2*x)),+1:c.c; done)
+'
+
 test_done
diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh
new file mode 100755 (executable)
index 0000000..37143ea
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description='pack-objects breaks long cross-pack delta chains'
+. ./test-lib.sh
+
+# This mirrors a repeated push setup:
+#
+# 1. A client repeatedly modifies some files, makes a
+#      commit, and pushes the result. It does this N times
+#      before we get around to repacking.
+#
+# 2. Each push generates a thin pack with the new version of
+#    various objects. Let's consider some file in the root tree
+#    which is updated in each commit.
+#
+#    When generating push number X, we feed commit X-1 (and
+#    thus blob X-1) as a preferred base. The resulting pack has
+#    blob X as a thin delta against blob X-1.
+#
+#    On the receiving end, "index-pack --fix-thin" will
+#    complete the pack with a base copy of blob X-1.
+#
+# 3. In older versions of git, if we used the delta from
+#    pack X, then we'd always find blob X-1 as a base in the
+#    same pack (and generate a fresh delta).
+#
+#    But with the pack mru, we jump from delta to delta
+#    following the traversal order:
+#
+#      a. We grab blob X from pack X as a delta, putting it at
+#         the tip of our mru list.
+#
+#      b. Eventually we move onto commit X-1. We need other
+#         objects which are only in pack X-1 (in the test code
+#         below, it's the containing tree). That puts pack X-1
+#         at the tip of our mru list.
+#
+#      c. Eventually we look for blob X-1, and we find the
+#         version in pack X-1 (because it's the mru tip).
+#
+# Now we have blob X as a delta against X-1, which is a delta
+# against X-2, and so forth.
+#
+# In the real world, these small pushes would get exploded by
+# unpack-objects rather than "index-pack --fix-thin", but the
+# same principle applies to larger pushes (they only need one
+# repeatedly-modified file to generate the delta chain).
+
+test_expect_success 'create series of packs' '
+       test-genrandom foo 4096 >content &&
+       prev= &&
+       for i in $(test_seq 1 10)
+       do
+               cat content >file &&
+               echo $i >>file &&
+               git add file &&
+               git commit -m $i &&
+               cur=$(git rev-parse HEAD^{tree}) &&
+               {
+                       test -n "$prev" && echo "-$prev"
+                       echo $cur
+                       echo "$(git rev-parse :file) file"
+               } | git pack-objects --stdout >tmp &&
+               git index-pack --stdin --fix-thin <tmp || return 1
+               prev=$cur
+       done
+'
+
+max_chain() {
+       git index-pack --verify-stat-only "$1" >output &&
+       perl -lne '
+         /chain length = (\d+)/ and $len = $1;
+         END { print $len }
+       ' output
+}
+
+# Note that this whole setup is pretty reliant on the current
+# packing heuristics. We double-check that our test case
+# actually produces a long chain. If it doesn't, it should be
+# adjusted (or scrapped if the heuristics have become too unreliable)
+test_expect_success 'packing produces a long delta' '
+       # Use --window=0 to make sure we are seeing reused deltas,
+       # not computing a new long chain.
+       pack=$(git pack-objects --all --window=0 </dev/null pack) &&
+       test 9 = "$(max_chain pack-$pack.pack)"
+'
+
+test_expect_success '--depth limits depth' '
+       pack=$(git pack-objects --all --depth=5 </dev/null pack) &&
+       test 5 = "$(max_chain pack-$pack.pack)"
+'
+
+test_done
index 305ca7a9308685c7d040ac254fd34cebbbc86cc8..3331e0f53443f9a8566d01ac08dd0cbc5aa2d33b 100755 (executable)
@@ -255,4 +255,42 @@ test_expect_success 'deny pushing to delete current branch' '
        )
 '
 
+extract_ref_advertisement () {
+       perl -lne '
+               # \\ is there to skip capabilities after \0
+               /push< ([^\\]+)/ or next;
+               exit 0 if $1 eq "0000";
+               print $1;
+       '
+}
+
+test_expect_success 'receive-pack de-dupes .have lines' '
+       git init shared &&
+       git -C shared commit --allow-empty -m both &&
+       git clone -s shared fork &&
+       (
+               cd shared &&
+               git checkout -b only-shared &&
+               git commit --allow-empty -m only-shared &&
+               git update-ref refs/heads/foo HEAD
+       ) &&
+
+       # Notable things in this expectation:
+       #  - local refs are not de-duped
+       #  - .have does not duplicate locals
+       #  - .have does not duplicate itself
+       local=$(git -C fork rev-parse HEAD) &&
+       shared=$(git -C shared rev-parse only-shared) &&
+       cat >expect <<-EOF &&
+       $local refs/heads/master
+       $local refs/remotes/origin/HEAD
+       $local refs/remotes/origin/master
+       $shared .have
+       EOF
+
+       GIT_TRACE_PACKET=$(pwd)/trace git push fork HEAD:foo &&
+       extract_ref_advertisement <trace >refs &&
+       test_cmp expect refs
+'
+
 test_done
index 505e1b4a7f421d377aa3798e94c90c14249bce91..b5865b385d3a0ca175dc683c75b7f650958a0469 100755 (executable)
@@ -484,7 +484,7 @@ test_expect_success 'test lonely missing ref' '
                cd client &&
                test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy
        ) >/dev/null 2>error-m &&
-       test_cmp expect-error error-m
+       test_i18ncmp expect-error error-m
 '
 
 test_expect_success 'test missing ref after existing' '
@@ -492,7 +492,7 @@ test_expect_success 'test missing ref after existing' '
                cd client &&
                test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy
        ) >/dev/null 2>error-em &&
-       test_cmp expect-error error-em
+       test_i18ncmp expect-error error-em
 '
 
 test_expect_success 'test missing ref before existing' '
@@ -500,7 +500,7 @@ test_expect_success 'test missing ref before existing' '
                cd client &&
                test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A
        ) >/dev/null 2>error-me &&
-       test_cmp expect-error error-me
+       test_i18ncmp expect-error error-me
 '
 
 test_expect_success 'test --all, --depth, and explicit head' '
index ba46e86de0a7285bf88ff5fe05e378c8bfd256f9..a6c0178f3af6c3e3f01816197e51e9bca546b00a 100755 (executable)
@@ -153,6 +153,25 @@ test_expect_success 'remove errors out early when deleting non-existent branch'
        )
 '
 
+test_expect_success 'remove remote with a branch without configured merge' '
+       test_when_finished "(
+               git -C test checkout master;
+               git -C test branch -D two;
+               git -C test config --remove-section remote.two;
+               git -C test config --remove-section branch.second;
+               true
+       )" &&
+       (
+               cd test &&
+               git remote add two ../two &&
+               git fetch two &&
+               git checkout -b second two/master^0 &&
+               git config branch.second.remote two &&
+               git checkout master &&
+               git remote rm two
+       )
+'
+
 test_expect_success 'rename errors out early when deleting non-existent branch' '
        (
                cd test &&
@@ -725,7 +744,7 @@ test_expect_success 'rename a remote' '
        (
                cd four &&
                git remote rename origin upstream &&
-               rmdir .git/refs/remotes/origin &&
+               test -z "$(git for-each-ref refs/remotes/origin)" &&
                test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
                test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
                test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
index 55fc83fc0624fa9bf1a024c4bc4aede25999fae0..94fc9be9ced3c89214b29e11c95201a38cc99d74 100755 (executable)
@@ -248,4 +248,13 @@ test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-complian
        test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git
 '
 
+test_expect_success 'ls-remote works outside repository' '
+       # It is important for this repo to be inside the nongit
+       # area, as we want a repo name that does not include
+       # slashes (because those inhibit some of our configuration
+       # lookups).
+       nongit git init --bare dst.git &&
+       nongit git ls-remote dst.git
+'
+
 test_done
index 0fc5a7c596b5d5e01ecb43f81d3d6eaafe611b7b..177897ea0b1e00cc4ec0f0e43510411ab19f1681 100755 (executable)
@@ -1098,7 +1098,8 @@ test_expect_success 'fetch exact SHA1' '
                test_must_fail git cat-file -t $the_commit &&
 
                # fetching the hidden object should fail by default
-               test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy &&
+               test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy 2>err &&
+               test_i18ngrep "Server does not allow request for unadvertised object" err &&
                test_must_fail git rev-parse --verify refs/heads/copy &&
 
                # the server side can allow it to succeed
index aeb3a63f7c07caa3f53ff4da5096dc51493dd4e3..87308cdced9af11365d5eb0644b9ac86fac21177 100755 (executable)
@@ -34,6 +34,15 @@ test_expect_success 'clone http repository' '
        test_cmp file clone/file
 '
 
+test_expect_success 'list refs from outside any repository' '
+       cat >expect <<-EOF &&
+       $(git rev-parse master) HEAD
+       $(git rev-parse master) refs/heads/master
+       EOF
+       nongit git ls-remote "$HTTPD_URL/dumb/repo.git" >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'create password-protected repository' '
        mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" &&
        cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
@@ -378,5 +387,14 @@ test_expect_success 'http-alternates triggers not-from-user protocol check' '
                clone $HTTPD_URL/dumb/evil.git evil-user
 '
 
+test_expect_success 'can redirect through non-"info/refs?service=git-upload-pack" URL' '
+       git clone "$HTTPD_URL/redir-to/dumb/repo.git"
+'
+
+test_expect_success 'print HTTP error when any intermediate redirect throws error' '
+       test_must_fail git clone "$HTTPD_URL/redir-to/502" 2> stderr &&
+       test_i18ngrep "unable to access.*/redir-to/502" stderr
+'
+
 stop_httpd
 test_done
index 4241ea5b32db4a0a85810dfe6421a161ce55fc46..b52b8acf9859b0084ea10ebf184ed95cafb0b2d8 100755 (executable)
@@ -386,6 +386,47 @@ test_expect_success 'tortoiseplink is like putty, with extra arguments' '
        expect_ssh "-batch -P 123" myhost src
 '
 
+test_expect_success 'double quoted plink.exe in GIT_SSH_COMMAND' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" &&
+       GIT_SSH_COMMAND="\"$TRASH_DIRECTORY/plink.exe\" -v" \
+               git clone "[myhost:123]:src" ssh-bracket-clone-plink-3 &&
+       expect_ssh "-v -P 123" myhost src
+'
+
+SQ="'"
+test_expect_success 'single quoted plink.exe in GIT_SSH_COMMAND' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" &&
+       GIT_SSH_COMMAND="$SQ$TRASH_DIRECTORY/plink.exe$SQ -v" \
+               git clone "[myhost:123]:src" ssh-bracket-clone-plink-4 &&
+       expect_ssh "-v -P 123" myhost src
+'
+
+test_expect_success 'GIT_SSH_VARIANT overrides plink detection' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
+       GIT_SSH_VARIANT=ssh \
+       git clone "[myhost:123]:src" ssh-bracket-clone-variant-1 &&
+       expect_ssh "-p 123" myhost src
+'
+
+test_expect_success 'ssh.variant overrides plink detection' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
+       git -c ssh.variant=ssh \
+               clone "[myhost:123]:src" ssh-bracket-clone-variant-2 &&
+       expect_ssh "-p 123" myhost src
+'
+
+test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' '
+       GIT_SSH_VARIANT=plink \
+       git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 &&
+       expect_ssh "-P 123" myhost src
+'
+
+test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' '
+       GIT_SSH_VARIANT=tortoiseplink \
+       git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 &&
+       expect_ssh "-batch -P 123" myhost src
+'
+
 # Reset the GIT_SSH environment variable for clone tests.
 setup_ssh_wrapper
 
index 1408b608eb030f0e6bcc3d531aad426da5f06b12..29597451967dd7e4847a5114b79a91399e1b3ab3 100755 (executable)
@@ -99,6 +99,44 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' '
        test_cmp actual.named expect
 '
 
+test_expect_success 'name-rev multiple --refs combine inclusive' '
+       git rev-list --left-right --cherry-pick F...E -- bar >actual &&
+       git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \
+               <actual >actual.named &&
+       test_cmp actual.named expect
+'
+
+cat >expect <<EOF
+<tags/F
+EOF
+
+test_expect_success 'name-rev --refs excludes non-matched patterns' '
+       git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
+       git rev-list --left-right --cherry-pick F...E -- bar >actual &&
+       git name-rev --stdin --name-only --refs="*tags/F" \
+               <actual >actual.named &&
+       test_cmp actual.named expect
+'
+
+cat >expect <<EOF
+<tags/F
+EOF
+
+test_expect_success 'name-rev --exclude excludes matched patterns' '
+       git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
+       git rev-list --left-right --cherry-pick F...E -- bar >actual &&
+       git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \
+               <actual >actual.named &&
+       test_cmp actual.named expect
+'
+
+test_expect_success 'name-rev --no-refs clears the refs list' '
+       git rev-list --left-right --cherry-pick F...E -- bar >expect &&
+       git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
+               <expect >actual &&
+       test_cmp actual expect
+'
+
 cat >expect <<EOF
 +tags/F
 =tags/D
index 3d5c238c8150c13b1244f8c4208856714265245a..97a07655a0e0d74747259498bfbb8d03ea6c7480 100755 (executable)
@@ -44,7 +44,7 @@ b1 [ahead 1, behind 1] d
 b2 [ahead 1, behind 1] d
 b3 [behind 1] b
 b4 [ahead 2] f
-b5 g
+b5 [gone] g
 b6 c
 EOF
 
diff --git a/t/t6045-merge-rename-delete.sh b/t/t6045-merge-rename-delete.sh
new file mode 100755 (executable)
index 0000000..5d33577
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='Merge-recursive rename/delete conflict message'
+. ./test-lib.sh
+
+test_expect_success 'rename/delete' '
+       echo foo >A &&
+       git add A &&
+       git commit -m "initial" &&
+
+       git checkout -b rename &&
+       git mv A B &&
+       git commit -m "rename" &&
+
+       git checkout master &&
+       git rm A &&
+       git commit -m "delete" &&
+
+       test_must_fail git merge --strategy=recursive rename >output &&
+       test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B in rename. Version rename of B left in tree." output
+'
+
+test_done
index 85f269411cb3aed461f6894dc5d1cc780debc24b..167491fd5b0d0c5ed3744c51cd0f05723c9f964b 100755 (executable)
@@ -182,6 +182,10 @@ check_describe "test2-lightweight-*" --tags --match="test2-*"
 
 check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^
 
+check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^
+
+check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^
+
 test_expect_success 'name-rev with exact tags' '
        echo A >expect &&
        tag_object=$(git rev-parse refs/tags/A) &&
@@ -206,4 +210,27 @@ test_expect_success 'describe --contains with the exact tags' '
        test_cmp expect actual
 '
 
+test_expect_success 'describe --contains and --match' '
+       echo "A^0" >expect &&
+       tagged_commit=$(git rev-parse "refs/tags/A^0") &&
+       test_must_fail git describe --contains --match="B" $tagged_commit &&
+       git describe --contains --match="B" --match="A" $tagged_commit >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'describe --exclude' '
+       echo "c~1" >expect &&
+       tagged_commit=$(git rev-parse "refs/tags/A^0") &&
+       test_must_fail git describe --contains --match="B" $tagged_commit &&
+       git describe --contains --match="?" --exclude="A" $tagged_commit >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'describe --contains and --no-match' '
+       echo "A^0" >expect &&
+       tagged_commit=$(git rev-parse "refs/tags/A^0") &&
+       git describe --contains --match="B" --no-match $tagged_commit >actual &&
+       test_cmp expect actual
+'
+
 test_done
index d51595cf6b189a59d271dcc69c3f245b72dcb8f6..9dd5cde5fc53deb365234e815d7142de9a975005 100755 (executable)
@@ -25,8 +25,10 @@ EOF
        test_cmp expect actual
 '
 
-test_expect_success 'exclude only should error out' '
-       test_must_fail git log --oneline --format=%s -- ":(exclude)sub"
+test_expect_success 'exclude only no longer errors out' '
+       git log --oneline --format=%s -- . ":(exclude)sub" >expect &&
+       git log --oneline --format=%s -- ":(exclude)sub" >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 't_e_i() exclude sub' '
index aea1dfc7148e4885e52a579ca60ee622c49b5693..834a9ed168fad35258955447326e7d3d1f09b7d1 100755 (executable)
@@ -38,6 +38,7 @@ test_atom() {
        case "$1" in
                head) ref=refs/heads/master ;;
                 tag) ref=refs/tags/testtag ;;
+                sym) ref=refs/heads/sym ;;
                   *) ref=$1 ;;
        esac
        printf '%s\n' "$3" >expected
@@ -50,16 +51,40 @@ test_atom() {
 
 test_atom head refname refs/heads/master
 test_atom head refname:short master
+test_atom head refname:lstrip=1 heads/master
+test_atom head refname:lstrip=2 master
+test_atom head refname:lstrip=-1 master
+test_atom head refname:lstrip=-2 heads/master
+test_atom head refname:rstrip=1 refs/heads
+test_atom head refname:rstrip=2 refs
+test_atom head refname:rstrip=-1 refs
+test_atom head refname:rstrip=-2 refs/heads
 test_atom head refname:strip=1 heads/master
 test_atom head refname:strip=2 master
+test_atom head refname:strip=-1 master
+test_atom head refname:strip=-2 heads/master
 test_atom head upstream refs/remotes/origin/master
 test_atom head upstream:short origin/master
+test_atom head upstream:lstrip=2 origin/master
+test_atom head upstream:lstrip=-2 origin/master
+test_atom head upstream:rstrip=2 refs/remotes
+test_atom head upstream:rstrip=-2 refs/remotes
+test_atom head upstream:strip=2 origin/master
+test_atom head upstream:strip=-2 origin/master
 test_atom head push refs/remotes/myfork/master
 test_atom head push:short myfork/master
+test_atom head push:lstrip=1 remotes/myfork/master
+test_atom head push:lstrip=-1 master
+test_atom head push:rstrip=1 refs/remotes/myfork
+test_atom head push:rstrip=-1 refs
+test_atom head push:strip=1 remotes/myfork/master
+test_atom head push:strip=-1 master
 test_atom head objecttype commit
 test_atom head objectsize 171
 test_atom head objectname $(git rev-parse refs/heads/master)
 test_atom head objectname:short $(git rev-parse --short refs/heads/master)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
 test_atom head tree $(git rev-parse refs/heads/master^{tree})
 test_atom head parent ''
 test_atom head numparent 0
@@ -99,6 +124,8 @@ test_atom tag objecttype tag
 test_atom tag objectsize 154
 test_atom tag objectname $(git rev-parse refs/tags/testtag)
 test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
 test_atom tag tree ''
 test_atom tag parent ''
 test_atom tag numparent ''
@@ -134,16 +161,6 @@ test_expect_success 'Check invalid atoms names are errors' '
        test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
 '
 
-test_expect_success 'arguments to :strip must be positive integers' '
-       test_must_fail git for-each-ref --format="%(refname:strip=0)" &&
-       test_must_fail git for-each-ref --format="%(refname:strip=-1)" &&
-       test_must_fail git for-each-ref --format="%(refname:strip=foo)"
-'
-
-test_expect_success 'stripping refnames too far gives an error' '
-       test_must_fail git for-each-ref --format="%(refname:strip=3)"
-'
-
 test_expect_success 'Check format specifiers are ignored in naming date atoms' '
        git for-each-ref --format="%(authordate)" refs/heads &&
        git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
@@ -164,6 +181,12 @@ test_expect_success 'Check invalid format specifiers are errors' '
        test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
 '
 
+test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
+       test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
+       test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
+       test_must_fail git for-each-ref --format="%(objectname:short=foo)"
+'
+
 test_date () {
        f=$1 &&
        committer_date=$2 &&
@@ -362,6 +385,8 @@ test_expect_success 'setup for upstream:track[short]' '
 
 test_atom head upstream:track '[ahead 1]'
 test_atom head upstream:trackshort '>'
+test_atom head upstream:track,nobracket 'ahead 1'
+test_atom head upstream:nobracket,track 'ahead 1'
 test_atom head push:track '[ahead 1]'
 test_atom head push:trackshort '>'
 
@@ -372,7 +397,7 @@ test_expect_success 'Check that :track[short] cannot be used with other atoms' '
 
 test_expect_success 'Check that :track[short] works when upstream is invalid' '
        cat >expected <<-\EOF &&
-
+       [gone]
 
        EOF
        test_when_finished "git config branch.master.merge refs/heads/master" &&
@@ -554,11 +579,12 @@ test_expect_success 'Verify sort with multiple keys' '
        test_cmp expected actual
 '
 
+
 test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
        test_when_finished "git checkout master" &&
        git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
        sed -e "s/^\* /  /" actual >expect &&
-       git checkout --orphan HEAD &&
+       git checkout --orphan orphaned-branch &&
        git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
        test_cmp expect actual
 '
@@ -588,4 +614,52 @@ test_expect_success 'basic atom: head contents:trailers' '
        test_cmp expect actual.clean
 '
 
+test_expect_success 'Add symbolic ref for the following tests' '
+       git symbolic-ref refs/heads/sym refs/heads/master
+'
+
+cat >expected <<EOF
+refs/heads/master
+EOF
+
+test_expect_success 'Verify usage of %(symref) atom' '
+       git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+heads/master
+EOF
+
+test_expect_success 'Verify usage of %(symref:short) atom' '
+       git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+master
+heads/master
+EOF
+
+test_expect_success 'Verify usage of %(symref:lstrip) atom' '
+       git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+       git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+       test_cmp expected actual &&
+
+       git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
+       git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+refs
+refs/heads
+EOF
+
+test_expect_success 'Verify usage of %(symref:rstrip) atom' '
+       git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+       git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+       test_cmp expected actual
+'
+
 test_done
index d0ab09f4bd804730eb049ea982e5724653583f06..a09a1a46eff5357c3939f5715f4a623effcb6428 100755 (executable)
@@ -327,4 +327,98 @@ test_expect_success 'reverse version sort' '
        test_cmp expect actual
 '
 
+test_expect_success 'improper usage of %(if), %(then), %(else) and %(end) atoms' '
+       test_must_fail git for-each-ref --format="%(if)" &&
+       test_must_fail git for-each-ref --format="%(then) %(end)" &&
+       test_must_fail git for-each-ref --format="%(else) %(end)" &&
+       test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
+       test_must_fail git for-each-ref --format="%(if) %(then) %(then) %(end)" &&
+       test_must_fail git for-each-ref --format="%(then) %(else) %(end)" &&
+       test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
+       test_must_fail git for-each-ref --format="%(if) %(then) %(else)" &&
+       test_must_fail git for-each-ref --format="%(if) %(else) %(then) %(end)" &&
+       test_must_fail git for-each-ref --format="%(if) %(then) %(else) %(else) %(end)" &&
+       test_must_fail git for-each-ref --format="%(if) %(end)"
+'
+
+test_expect_success 'check %(if)...%(then)...%(end) atoms' '
+       git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Author: %(authorname)%(end)" >actual &&
+       cat >expect <<-\EOF &&
+       refs/heads/master Author: A U Thor
+       refs/heads/side Author: A U Thor
+       refs/odd/spot Author: A U Thor
+       refs/tags/annotated-tag
+       refs/tags/doubly-annotated-tag
+       refs/tags/doubly-signed-tag
+       refs/tags/foo1.10 Author: A U Thor
+       refs/tags/foo1.3 Author: A U Thor
+       refs/tags/foo1.6 Author: A U Thor
+       refs/tags/four Author: A U Thor
+       refs/tags/one Author: A U Thor
+       refs/tags/signed-tag
+       refs/tags/three Author: A U Thor
+       refs/tags/two Author: A U Thor
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'check %(if)...%(then)...%(else)...%(end) atoms' '
+       git for-each-ref --format="%(if)%(authorname)%(then)%(authorname)%(else)No author%(end): %(refname)" >actual &&
+       cat >expect <<-\EOF &&
+       A U Thor: refs/heads/master
+       A U Thor: refs/heads/side
+       A U Thor: refs/odd/spot
+       No author: refs/tags/annotated-tag
+       No author: refs/tags/doubly-annotated-tag
+       No author: refs/tags/doubly-signed-tag
+       A U Thor: refs/tags/foo1.10
+       A U Thor: refs/tags/foo1.3
+       A U Thor: refs/tags/foo1.6
+       A U Thor: refs/tags/four
+       A U Thor: refs/tags/one
+       No author: refs/tags/signed-tag
+       A U Thor: refs/tags/three
+       A U Thor: refs/tags/two
+       EOF
+       test_cmp expect actual
+'
+test_expect_success 'ignore spaces in %(if) atom usage' '
+       git for-each-ref --format="%(refname:short): %(if)%(HEAD)%(then)Head ref%(else)Not Head ref%(end)" >actual &&
+       cat >expect <<-\EOF &&
+       master: Head ref
+       side: Not Head ref
+       odd/spot: Not Head ref
+       annotated-tag: Not Head ref
+       doubly-annotated-tag: Not Head ref
+       doubly-signed-tag: Not Head ref
+       foo1.10: Not Head ref
+       foo1.3: Not Head ref
+       foo1.6: Not Head ref
+       four: Not Head ref
+       one: Not Head ref
+       signed-tag: Not Head ref
+       three: Not Head ref
+       two: Not Head ref
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'check %(if:equals=<string>)' '
+       git for-each-ref --format="%(if:equals=master)%(refname:short)%(then)Found master%(else)Not master%(end)" refs/heads/ >actual &&
+       cat >expect <<-\EOF &&
+       Found master
+       Not master
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'check %(if:notequals=<string>)' '
+       git for-each-ref --format="%(if:notequals=master)%(refname:short)%(then)Not master%(else)Found master%(end)" refs/heads/ >actual &&
+       cat >expect <<-\EOF &&
+       Found master
+       Not master
+       EOF
+       test_cmp expect actual
+'
+
 test_done
index 1762dfa6a306ca672e3d3430b020f1b01a16da7e..08de2e8ab09435c535f8aab5b455a074453e91a8 100755 (executable)
@@ -67,5 +67,20 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre
        test_line_count = 2 new # There is one new pack and its .idx
 '
 
+test_expect_success 'background auto gc does not run if gc.log is present and recent but does if it is old' '
+       test_commit foo &&
+       test_commit bar &&
+       git repack &&
+       test_config gc.autopacklimit 1 &&
+       test_config gc.autodetach true &&
+       echo fleem >.git/gc.log &&
+       test_must_fail git gc --auto 2>err &&
+       test_i18ngrep "^error:" err &&
+       test_config gc.logexpiry 5.days &&
+       test-chmtime =-345600 .git/gc.log &&
+       test_must_fail git gc --auto &&
+       test_config gc.logexpiry 2.days &&
+       git gc --auto
+'
 
 test_done
index cb8fbd8e5e03a0ff5a176cc947419c357fd65d99..7cb60799be1a109e2210350137c8754a5bc4bb7d 100755 (executable)
@@ -313,6 +313,27 @@ test_expect_success 'Tag name filtering allows slashes in tag names' '
        git cat-file tag X/2 > actual &&
        test_cmp expect actual
 '
+test_expect_success 'setup --prune-empty comparisons' '
+       git checkout --orphan master-no-a &&
+       git rm -rf . &&
+       unset test_tick &&
+       test_tick &&
+       GIT_COMMITTER_DATE="@0 +0000" GIT_AUTHOR_DATE="@0 +0000" &&
+       test_commit --notick B B.t B Bx &&
+       git checkout -b branch-no-a Bx &&
+       test_commit D D.t D Dx &&
+       mkdir dir &&
+       test_commit dir/D dir/D.t dir/D dir/Dx &&
+       test_commit E E.t E Ex &&
+       git checkout master-no-a &&
+       test_commit C C.t C Cx &&
+       git checkout branch-no-a &&
+       git merge Cx -m "Merge tag '\''C'\'' into branch" &&
+       git tag Fx &&
+       test_commit G G.t G Gx &&
+       test_commit H H.t H Hx &&
+       git checkout branch
+'
 
 test_expect_success 'Prune empty commits' '
        git rev-list HEAD > expect &&
@@ -341,6 +362,22 @@ test_expect_success 'prune empty works even without index/tree filters' '
        test_cmp expect actual
 '
 
+test_expect_success '--prune-empty is able to prune root commit' '
+       git rev-list branch-no-a >expect &&
+       git branch testing H &&
+       git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t" testing &&
+       git rev-list testing >actual &&
+       git branch -D testing &&
+       test_cmp expect actual
+'
+
+test_expect_success '--prune-empty is able to prune entire branch' '
+       git branch prune-entire B &&
+       git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t B.t" prune-entire &&
+       test_path_is_missing .git/refs/heads/prune-entire &&
+       test_must_fail git reflog exists refs/heads/prune-entire
+'
+
 test_expect_success '--remap-to-ancestor with filename filters' '
        git checkout master &&
        git reset --hard A &&
index 072e6c6b88447f21e89a0087d894ae8ab2868ad7..b4698ab5f53c2ce92bd349fe4fa336e5fc5ca3e5 100755 (executable)
@@ -81,9 +81,25 @@ test_expect_success 'creating a tag using default HEAD should succeed' '
 '
 
 test_expect_success 'creating a tag with --create-reflog should create reflog' '
+       git log -1 \
+               --format="format:tag: tagging %h (%s, %cd)%n" \
+               --date=format:%Y-%m-%d >expected &&
        test_when_finished "git tag -d tag_with_reflog" &&
        git tag --create-reflog tag_with_reflog &&
-       git reflog exists refs/tags/tag_with_reflog
+       git reflog exists refs/tags/tag_with_reflog &&
+       sed -e "s/^.*   //" .git/logs/refs/tags/tag_with_reflog >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'annotated tag with --create-reflog has correct message' '
+       git log -1 \
+               --format="format:tag: tagging %h (%s, %cd)%n" \
+               --date=format:%Y-%m-%d >expected &&
+       test_when_finished "git tag -d tag_with_reflog" &&
+       git tag -m "annotated tag" --create-reflog tag_with_reflog &&
+       git reflog exists refs/tags/tag_with_reflog &&
+       sed -e "s/^.*   //" .git/logs/refs/tags/tag_with_reflog >actual &&
+       test_cmp expected actual
 '
 
 test_expect_success '--create-reflog does not create reflog on failure' '
diff --git a/t/t7518-ident-corner-cases.sh b/t/t7518-ident-corner-cases.sh
new file mode 100755 (executable)
index 0000000..b22f631
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='corner cases in ident strings'
+. ./test-lib.sh
+
+# confirm that we do not segfault _and_ that we do not say "(null)", as
+# glibc systems will quietly handle our NULL pointer
+#
+# Note also that we can't use "env" here because we need to unset a variable,
+# and "-u" is not portable.
+test_expect_success 'empty name and missing email' '
+       (
+               sane_unset GIT_AUTHOR_EMAIL &&
+               GIT_AUTHOR_NAME= &&
+               test_must_fail git commit --allow-empty -m foo 2>err &&
+               test_i18ngrep ! null err
+       )
+'
+
+test_expect_success 'commit rejects all-crud name' '
+       test_must_fail env GIT_AUTHOR_NAME=" .;<>" \
+               git commit --allow-empty -m foo
+'
+
+# We must test the actual error message here, as an unwanted
+# auto-detection could fail for other reasons.
+test_expect_success 'empty configured name does not auto-detect' '
+       (
+               sane_unset GIT_AUTHOR_NAME &&
+               test_must_fail \
+                       git -c user.name= commit --allow-empty -m foo 2>err &&
+               test_i18ngrep "empty ident name" err
+       )
+'
+
+test_done
index 19f0108f8a2c67c518a92d996be7a335dbb69af2..cee42097b077378f42910666a7895347b0787b1a 100755 (executable)
@@ -982,6 +982,72 @@ test_expect_success 'grep -e -- -- path' '
        test_cmp expected actual
 '
 
+test_expect_success 'dashdash disambiguates rev as rev' '
+       test_when_finished "rm -f master" &&
+       echo content >master &&
+       echo master:hello.c >expect &&
+       git grep -l o master -- hello.c >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'dashdash disambiguates pathspec as pathspec' '
+       test_when_finished "git rm -f master" &&
+       echo content >master &&
+       git add master &&
+       echo master:content >expect &&
+       git grep o -- master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'report bogus arg without dashdash' '
+       test_must_fail git grep o does-not-exist
+'
+
+test_expect_success 'report bogus rev with dashdash' '
+       test_must_fail git grep o hello.c --
+'
+
+test_expect_success 'allow non-existent path with dashdash' '
+       # We need a real match so grep exits with success.
+       tree=$(git ls-tree HEAD |
+              sed s/hello.c/not-in-working-tree/ |
+              git mktree) &&
+       git grep o "$tree" -- not-in-working-tree
+'
+
+test_expect_success 'grep --no-index pattern -- path' '
+       rm -fr non &&
+       mkdir -p non/git &&
+       (
+               GIT_CEILING_DIRECTORIES="$(pwd)/non" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd non/git &&
+               echo hello >hello &&
+               echo goodbye >goodbye &&
+               echo hello:hello >expect &&
+               git grep --no-index o -- hello >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'grep --no-index complains of revs' '
+       test_must_fail git grep --no-index o master -- 2>err &&
+       test_i18ngrep "cannot be used with revs" err
+'
+
+test_expect_success 'grep --no-index prefers paths to revs' '
+       test_when_finished "rm -f master" &&
+       echo content >master &&
+       echo master:content >expect &&
+       git grep --no-index o master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'grep --no-index does not "diagnose" revs' '
+       test_must_fail git grep --no-index o :1:hello.c 2>err &&
+       test_i18ngrep ! -i "did you mean" err
+'
+
 cat >expected <<EOF
 hello.c:int main(int argc, const char **argv)
 hello.c:       printf("Hello world.\n");
index 0f398dd1603d941e158fd39faa0a218428224872..60a80f60b2685c461f80cdbf4256eb17dc18b85b 100755 (executable)
@@ -148,7 +148,6 @@ cat >expected-cc <<\EOF
 !two@example.com!
 !three@example.com!
 !four@example.com!
-!five@example.com!
 EOF
 "
 
@@ -159,9 +158,9 @@ test_expect_success $PREREQ 'cc trailer with various syntax' '
        Test Cc: trailers.
 
        Cc: one@example.com
-       Cc: <two@example.com> # this is part of the name
-       Cc: <three@example.com>, <four@example.com> # not.five@example.com
-       Cc: "Some # Body" <five@example.com> [part.of.name.too]
+       Cc: <two@example.com> # trailing comments are ignored
+       Cc: <three@example.com>, <not.four@example.com> one address per line
+       Cc: "Some # Body" <four@example.com> [ <also.a.comment> ]
        EOF
        clean_fake_sendmail &&
        git send-email -1 --to=recipient@example.com \
index bb879a527d94e7329dccef15525421917676f56b..1319415ba8ec04f2b9d34ec984d3f8afb61fb551 100755 (executable)
@@ -18,6 +18,11 @@ then
     test_done
 fi
 
+if ! test_have_prereq NOT_ROOT; then
+       skip_all='When cvs is compiled with CVS_BADROOT commits as root fail'
+       test_done
+fi
+
 CVSROOT=$PWD/tmpcvsroot
 CVSWORK=$PWD/cvswork
 GIT_DIR=$PWD/.git
index 4c384ff02333e1413cc67b1b4a6bed47ae3d00db..804ce3850ff3f7838035fd316e26ee301b525ff0 100755 (executable)
@@ -3,6 +3,11 @@
 test_description='git cvsimport basic tests'
 . ./lib-cvs.sh
 
+if ! test_have_prereq NOT_ROOT; then
+       skip_all='When cvs is compiled with CVS_BADROOT commits as root fail'
+       test_done
+fi
+
 test_expect_success PERL 'setup cvsroot environment' '
        CVSROOT=$(pwd)/cvsroot &&
        export CVSROOT
index a34e55f874ca06da4cae6ab5d46bc3263c58d521..d711bef2402cb7abd69c0bbcec49f7faba2530dd 100755 (executable)
@@ -98,7 +98,7 @@ test_gitcomp ()
 {
        local -a COMPREPLY &&
        sed -e 's/Z$//' >expected &&
-       cur="$1" &&
+       local cur="$1" &&
        shift &&
        __gitcomp "$@" &&
        print_comp &&
@@ -113,7 +113,7 @@ test_gitcomp_nl ()
 {
        local -a COMPREPLY &&
        sed -e 's/Z$//' >expected &&
-       cur="$1" &&
+       local cur="$1" &&
        shift &&
        __gitcomp_nl "$@" &&
        print_comp &&
@@ -124,140 +124,280 @@ invalid_variable_name='${foo.bar}'
 
 actual="$TRASH_DIRECTORY/actual"
 
-test_expect_success 'setup for __gitdir tests' '
+if test_have_prereq MINGW
+then
+       ROOT="$(pwd -W)"
+else
+       ROOT="$(pwd)"
+fi
+
+test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '
        mkdir -p subdir/subsubdir &&
+       mkdir -p non-repo &&
        git init otherrepo
 '
 
-test_expect_success '__gitdir - from command line (through $__git_dir)' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '
+       echo "$ROOT/otherrepo/.git" >expected &&
        (
-               __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
-               __gitdir >"$actual"
+               __git_dir="$ROOT/otherrepo/.git" &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - repo as argument' '
-       echo "otherrepo/.git" >expected &&
-       __gitdir "otherrepo" >"$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success '__gitdir - remote as argument' '
-       echo "remote" >expected &&
-       __gitdir "remote" >"$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success '__gitdir - .git directory in cwd' '
+test_expect_success '__git_find_repo_path - .git directory in cwd' '
        echo ".git" >expected &&
-       __gitdir >"$actual" &&
+       (
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - .git directory in parent' '
-       echo "$(pwd -P)/.git" >expected &&
+test_expect_success '__git_find_repo_path - .git directory in parent' '
+       echo "$ROOT/.git" >expected &&
        (
                cd subdir/subsubdir &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - cwd is a .git directory' '
+test_expect_success '__git_find_repo_path - cwd is a .git directory' '
        echo "." >expected &&
        (
                cd .git &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - parent is a .git directory' '
-       echo "$(pwd -P)/.git" >expected &&
+test_expect_success '__git_find_repo_path - parent is a .git directory' '
+       echo "$ROOT/.git" >expected &&
        (
                cd .git/refs/heads &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in cwd' '
+       echo "$ROOT/otherrepo/.git" >expected &&
        (
-               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               GIT_DIR="$ROOT/otherrepo/.git" &&
                export GIT_DIR &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in parent' '
+       echo "$ROOT/otherrepo/.git" >expected &&
        (
-               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               GIT_DIR="$ROOT/otherrepo/.git" &&
                export GIT_DIR &&
                cd subdir &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_find_repo_path - from command line while "git -C"' '
+       echo "$ROOT/.git" >expected &&
+       (
+               __git_dir="$ROOT/.git" &&
+               __git_C_args=(-C otherrepo) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - non-existing $GIT_DIR' '
+test_expect_success '__git_find_repo_path - relative dir from command line and "git -C"' '
+       echo "$ROOT/otherrepo/.git" >expected &&
        (
-               GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
+               cd subdir &&
+               __git_dir="otherrepo/.git" &&
+               __git_C_args=(-C ..) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_find_repo_path - $GIT_DIR set while "git -C"' '
+       echo "$ROOT/.git" >expected &&
+       (
+               GIT_DIR="$ROOT/.git" &&
                export GIT_DIR &&
-               test_must_fail __gitdir
-       )
+               __git_C_args=(-C otherrepo) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
 '
 
-function pwd_P_W () {
-       if test_have_prereq MINGW
-       then
-               pwd -W
-       else
-               pwd -P
-       fi
-}
+test_expect_success '__git_find_repo_path - relative dir in $GIT_DIR and "git -C"' '
+       echo "$ROOT/otherrepo/.git" >expected &&
+       (
+               cd subdir &&
+               GIT_DIR="otherrepo/.git" &&
+               export GIT_DIR &&
+               __git_C_args=(-C ..) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
 
-test_expect_success '__gitdir - gitfile in cwd' '
-       echo "$(pwd_P_W)/otherrepo/.git" >expected &&
-       echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git &&
+test_expect_success '__git_find_repo_path - "git -C" while .git directory in cwd' '
+       echo "$ROOT/otherrepo/.git" >expected &&
+       (
+               __git_C_args=(-C otherrepo) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_find_repo_path - "git -C" while cwd is a .git directory' '
+       echo "$ROOT/otherrepo/.git" >expected &&
+       (
+               cd .git &&
+               __git_C_args=(-C .. -C otherrepo) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_find_repo_path - "git -C" while .git directory in parent' '
+       echo "$ROOT/otherrepo/.git" >expected &&
+       (
+               cd subdir &&
+               __git_C_args=(-C .. -C otherrepo) &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_find_repo_path - non-existing path in "git -C"' '
+       (
+               __git_C_args=(-C non-existing) &&
+               test_must_fail __git_find_repo_path &&
+               printf "$__git_repo_path" >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_find_repo_path - non-existing path in $__git_dir' '
+       (
+               __git_dir="non-existing" &&
+               test_must_fail __git_find_repo_path &&
+               printf "$__git_repo_path" >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_find_repo_path - non-existing $GIT_DIR' '
+       (
+               GIT_DIR="$ROOT/non-existing" &&
+               export GIT_DIR &&
+               test_must_fail __git_find_repo_path &&
+               printf "$__git_repo_path" >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_find_repo_path - gitfile in cwd' '
+       echo "$ROOT/otherrepo/.git" >expected &&
+       echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git &&
        test_when_finished "rm -f subdir/.git" &&
        (
                cd subdir &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - gitfile in parent' '
-       echo "$(pwd_P_W)/otherrepo/.git" >expected &&
-       echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git &&
+test_expect_success '__git_find_repo_path - gitfile in parent' '
+       echo "$ROOT/otherrepo/.git" >expected &&
+       echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git &&
        test_when_finished "rm -f subdir/.git" &&
        (
                cd subdir/subsubdir &&
-               __gitdir >"$actual"
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' '
-       echo "$(pwd -P)/otherrepo/.git" >expected &&
+test_expect_success SYMLINKS '__git_find_repo_path - resulting path avoids symlinks' '
+       echo "$ROOT/otherrepo/.git" >expected &&
        mkdir otherrepo/dir &&
        test_when_finished "rm -rf otherrepo/dir" &&
        ln -s otherrepo/dir link &&
        test_when_finished "rm -f link" &&
        (
                cd link &&
+               __git_find_repo_path &&
+               echo "$__git_repo_path" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_find_repo_path - not a git repository' '
+       (
+               cd non-repo &&
+               GIT_CEILING_DIRECTORIES="$ROOT" &&
+               export GIT_CEILING_DIRECTORIES &&
+               test_must_fail __git_find_repo_path &&
+               printf "$__git_repo_path" >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__gitdir - finds repo' '
+       echo "$ROOT/.git" >expected &&
+       (
+               cd subdir/subsubdir &&
                __gitdir >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - not a git repository' '
-       nongit test_must_fail __gitdir
+
+test_expect_success '__gitdir - returns error when cant find repo' '
+       (
+               __git_dir="non-existing" &&
+               test_must_fail __gitdir >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__gitdir - repo as argument' '
+       echo "otherrepo/.git" >expected &&
+       (
+               __gitdir "otherrepo" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - remote as argument' '
+       echo "remote" >expected &&
+       (
+               __gitdir "remote" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
 '
 
 test_expect_success '__gitcomp - trailing space - options' '
@@ -361,10 +501,286 @@ test_expect_success '__git_remotes - list remotes from $GIT_DIR/remotes and from
        git remote add remote_in_config_1 git://remote_1 &&
        test_when_finished "git remote remove remote_in_config_2" &&
        git remote add remote_in_config_2 git://remote_2 &&
-       __git_remotes >actual &&
+       (
+               __git_remotes >actual
+       ) &&
        test_cmp expect actual
 '
 
+test_expect_success '__git_is_configured_remote' '
+       test_when_finished "git remote remove remote_1" &&
+       git remote add remote_1 git://remote_1 &&
+       test_when_finished "git remote remove remote_2" &&
+       git remote add remote_2 git://remote_2 &&
+       (
+               verbose __git_is_configured_remote remote_2 &&
+               test_must_fail __git_is_configured_remote non-existent
+       )
+'
+
+test_expect_success 'setup for ref completion' '
+       git commit --allow-empty -m initial &&
+       git branch matching-branch &&
+       git tag matching-tag &&
+       (
+               cd otherrepo &&
+               git commit --allow-empty -m initial &&
+               git branch -m master master-in-other &&
+               git branch branch-in-other &&
+               git tag tag-in-other
+       ) &&
+       git remote add other "$ROOT/otherrepo/.git" &&
+       git fetch --no-tags other &&
+       rm -f .git/FETCH_HEAD &&
+       git init thirdrepo
+'
+
+test_expect_success '__git_refs - simple' '
+       cat >expected <<-EOF &&
+       HEAD
+       master
+       matching-branch
+       other/branch-in-other
+       other/master-in-other
+       matching-tag
+       EOF
+       (
+               cur= &&
+               __git_refs >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - full refs' '
+       cat >expected <<-EOF &&
+       refs/heads/master
+       refs/heads/matching-branch
+       EOF
+       (
+               cur=refs/heads/ &&
+               __git_refs >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - repo given on the command line' '
+       cat >expected <<-EOF &&
+       HEAD
+       branch-in-other
+       master-in-other
+       tag-in-other
+       EOF
+       (
+               __git_dir="$ROOT/otherrepo/.git" &&
+               cur= &&
+               __git_refs >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - remote on local file system' '
+       cat >expected <<-EOF &&
+       HEAD
+       branch-in-other
+       master-in-other
+       tag-in-other
+       EOF
+       (
+               cur= &&
+               __git_refs otherrepo >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - remote on local file system - full refs' '
+       cat >expected <<-EOF &&
+       refs/heads/branch-in-other
+       refs/heads/master-in-other
+       refs/tags/tag-in-other
+       EOF
+       (
+               cur=refs/ &&
+               __git_refs otherrepo >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - configured remote' '
+       cat >expected <<-EOF &&
+       HEAD
+       branch-in-other
+       master-in-other
+       EOF
+       (
+               cur= &&
+               __git_refs other >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - configured remote - full refs' '
+       cat >expected <<-EOF &&
+       refs/heads/branch-in-other
+       refs/heads/master-in-other
+       refs/tags/tag-in-other
+       EOF
+       (
+               cur=refs/ &&
+               __git_refs other >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - configured remote - repo given on the command line' '
+       cat >expected <<-EOF &&
+       HEAD
+       branch-in-other
+       master-in-other
+       EOF
+       (
+               cd thirdrepo &&
+               __git_dir="$ROOT/.git" &&
+               cur= &&
+               __git_refs other >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - configured remote - full refs - repo given on the command line' '
+       cat >expected <<-EOF &&
+       refs/heads/branch-in-other
+       refs/heads/master-in-other
+       refs/tags/tag-in-other
+       EOF
+       (
+               cd thirdrepo &&
+               __git_dir="$ROOT/.git" &&
+               cur=refs/ &&
+               __git_refs other >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - configured remote - remote name matches a directory' '
+       cat >expected <<-EOF &&
+       HEAD
+       branch-in-other
+       master-in-other
+       EOF
+       mkdir other &&
+       test_when_finished "rm -rf other" &&
+       (
+               cur= &&
+               __git_refs other >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - URL remote' '
+       cat >expected <<-EOF &&
+       HEAD
+       branch-in-other
+       master-in-other
+       tag-in-other
+       EOF
+       (
+               cur= &&
+               __git_refs "file://$ROOT/otherrepo/.git" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - URL remote - full refs' '
+       cat >expected <<-EOF &&
+       refs/heads/branch-in-other
+       refs/heads/master-in-other
+       refs/tags/tag-in-other
+       EOF
+       (
+               cur=refs/ &&
+               __git_refs "file://$ROOT/otherrepo/.git" >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__git_refs - non-existing remote' '
+       (
+               cur= &&
+               __git_refs non-existing >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_refs - non-existing remote - full refs' '
+       (
+               cur=refs/ &&
+               __git_refs non-existing >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_refs - non-existing URL remote' '
+       (
+               cur= &&
+               __git_refs "file://$ROOT/non-existing" >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_refs - non-existing URL remote - full refs' '
+       (
+               cur=refs/ &&
+               __git_refs "file://$ROOT/non-existing" >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_refs - not in a git repository' '
+       (
+               GIT_CEILING_DIRECTORIES="$ROOT" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd subdir &&
+               cur= &&
+               __git_refs >"$actual"
+       ) &&
+       test_must_be_empty "$actual"
+'
+
+test_expect_success '__git_refs - unique remote branches for git checkout DWIMery' '
+       cat >expected <<-EOF &&
+       HEAD
+       master
+       matching-branch
+       other/ambiguous
+       other/branch-in-other
+       other/master-in-other
+       remote/ambiguous
+       remote/branch-in-remote
+       matching-tag
+       branch-in-other
+       branch-in-remote
+       master-in-other
+       EOF
+       for remote_ref in refs/remotes/other/ambiguous \
+               refs/remotes/remote/ambiguous \
+               refs/remotes/remote/branch-in-remote
+       do
+               git update-ref $remote_ref master &&
+               test_when_finished "git update-ref -d $remote_ref"
+       done &&
+       (
+               cur= &&
+               __git_refs "" 1 >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'teardown after ref completion' '
+       git branch -d matching-branch &&
+       git tag -d matching-tag &&
+       git remote remove other
+'
+
 test_expect_success '__git_get_config_variables' '
        cat >expect <<-EOF &&
        name-1
@@ -475,7 +891,12 @@ test_expect_success 'general options plus command' '
        test_completion "git --namespace=foo check" "checkout " &&
        test_completion "git --paginate check" "checkout " &&
        test_completion "git --info-path check" "checkout " &&
-       test_completion "git --no-replace-objects check" "checkout "
+       test_completion "git --no-replace-objects check" "checkout " &&
+       test_completion "git --git-dir some/path check" "checkout " &&
+       test_completion "git -c conf.var=value check" "checkout " &&
+       test_completion "git -C some/path check" "checkout " &&
+       test_completion "git --work-tree some/path check" "checkout " &&
+       test_completion "git --namespace name/space check" "checkout "
 '
 
 test_expect_success 'git --help completion' '
@@ -483,10 +904,10 @@ test_expect_success 'git --help completion' '
        test_completion "git --help core" "core-tutorial "
 '
 
-test_expect_success 'setup for ref completion' '
+test_expect_success 'setup for integration tests' '
        echo content >file1 &&
        echo more >file2 &&
-       git add . &&
+       git add file1 file2 &&
        git commit -m one &&
        git branch mybranch &&
        git tag mytag
@@ -500,6 +921,12 @@ test_expect_success 'checkout completes ref names' '
        EOF
 '
 
+test_expect_success 'git -C <path> checkout uses the right repo' '
+       test_completion "git -C subdir -C subsubdir -C .. -C ../otherrepo checkout b" <<-\EOF
+       branch-in-other Z
+       EOF
+'
+
 test_expect_success 'show completes all refs' '
        test_completion "git show m" <<-\EOF
        master Z
@@ -517,7 +944,7 @@ test_expect_success '<ref>: completes paths' '
 
 test_expect_success 'complete tree filename with spaces' '
        echo content >"name with spaces" &&
-       git add . &&
+       git add "name with spaces" &&
        git commit -m spaces &&
        test_completion "git show HEAD:nam" <<-\EOF
        name with spaces Z
@@ -526,7 +953,7 @@ test_expect_success 'complete tree filename with spaces' '
 
 test_expect_success 'complete tree filename with metacharacters' '
        echo content >"name with \${meta}" &&
-       git add . &&
+       git add "name with \${meta}" &&
        git commit -m meta &&
        test_completion "git show HEAD:nam" <<-\EOF
        name with ${meta} Z
index ffcc2723755f1b97dd6626ed7c13801f905fdf18..68437106703470f39a6079aaad7d3bb407ac3389 100644 (file)
@@ -247,8 +247,13 @@ int close_tempfile(struct tempfile *tempfile)
        tempfile->fd = -1;
        if (fp) {
                tempfile->fp = NULL;
-               err = ferror(fp);
-               err |= fclose(fp);
+               if (ferror(fp)) {
+                       err = -1;
+                       if (!fclose(fp))
+                               errno = EIO;
+               } else {
+                       err = fclose(fp);
+               }
        } else {
                err = close(fd);
        }
index 1258d6aedd26538f55c60495a61608236262cfc1..dc90a1fb769c078be2fbf77018242b6fc209acf6 100644 (file)
@@ -124,8 +124,9 @@ static struct child_process *get_helper(struct transport *transport)
        helper->git_cmd = 0;
        helper->silent_exec_failure = 1;
 
-       argv_array_pushf(&helper->env_array, "%s=%s", GIT_DIR_ENVIRONMENT,
-                        get_git_dir());
+       if (have_git_dir())
+               argv_array_pushf(&helper->env_array, "%s=%s",
+                                GIT_DIR_ENVIRONMENT, get_git_dir());
 
        code = start_command(helper);
        if (code < 0 && errno == ENOENT)
index d72e0894840fc384d67339b549a9a6bce7ba03ec..ea1feac9dcd83dac05cf0e2c35cf5f7a022810aa 100644 (file)
@@ -204,6 +204,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
 static int fetch_refs_via_pack(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
 {
+       int ret = 0;
        struct git_transport_data *data = transport->data;
        struct ref *refs;
        char *dest = xstrdup(transport->url);
@@ -241,19 +242,22 @@ static int fetch_refs_via_pack(struct transport *transport,
                          &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
-       if (finish_connect(data->conn)) {
-               free_refs(refs);
-               refs = NULL;
-       }
+       if (finish_connect(data->conn))
+               ret = -1;
        data->conn = NULL;
        data->got_remote_heads = 0;
        data->options.self_contained_and_connected =
                args.self_contained_and_connected;
 
+       if (refs == NULL)
+               ret = -1;
+       if (report_unmatched_refs(to_fetch, nr_heads))
+               ret = -1;
+
        free_refs(refs_tmp);
        free_refs(refs);
        free(dest);
-       return (refs ? 0 : -1);
+       return ret;
 }
 
 static int push_had_errors(struct ref *ref)
@@ -299,7 +303,7 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v
                if (verbose)
                        fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
                if (ref->deletion) {
-                       delete_ref(rs.dst, NULL, 0);
+                       delete_ref(NULL, rs.dst, NULL, 0);
                } else
                        update_ref("update by push", rs.dst,
                                        ref->new_oid.hash, NULL, 0, 0);
@@ -1206,6 +1210,42 @@ char *transport_anonymize_url(const char *url)
        return xstrdup(url);
 }
 
+static void read_alternate_refs(const char *path,
+                               alternate_ref_fn *cb,
+                               void *data)
+{
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       struct strbuf line = STRBUF_INIT;
+       FILE *fh;
+
+       cmd.git_cmd = 1;
+       argv_array_pushf(&cmd.args, "--git-dir=%s", path);
+       argv_array_push(&cmd.args, "for-each-ref");
+       argv_array_push(&cmd.args, "--format=%(objectname) %(refname)");
+       cmd.env = local_repo_env;
+       cmd.out = -1;
+
+       if (start_command(&cmd))
+               return;
+
+       fh = xfdopen(cmd.out, "r");
+       while (strbuf_getline_lf(&line, fh) != EOF) {
+               struct object_id oid;
+
+               if (get_oid_hex(line.buf, &oid) ||
+                   line.buf[GIT_SHA1_HEXSZ] != ' ') {
+                       warning("invalid line while parsing alternate refs: %s",
+                               line.buf);
+                       break;
+               }
+
+               cb(line.buf + GIT_SHA1_HEXSZ + 1, &oid, data);
+       }
+
+       fclose(fh);
+       finish_command(&cmd);
+}
+
 struct alternate_refs_data {
        alternate_ref_fn *fn;
        void *data;
@@ -1214,34 +1254,26 @@ struct alternate_refs_data {
 static int refs_from_alternate_cb(struct alternate_object_database *e,
                                  void *data)
 {
-       char *other;
-       size_t len;
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
+       struct strbuf path = STRBUF_INIT;
+       size_t base_len;
        struct alternate_refs_data *cb = data;
 
-       other = real_pathdup(e->path);
-       len = strlen(other);
-
-       while (other[len-1] == '/')
-               other[--len] = '\0';
-       if (len < 8 || memcmp(other + len - 8, "/objects", 8))
+       if (!strbuf_realpath(&path, e->path, 0))
+               goto out;
+       if (!strbuf_strip_suffix(&path, "/objects"))
                goto out;
+       base_len = path.len;
+
        /* Is this a git repository with refs? */
-       memcpy(other + len - 8, "/refs", 6);
-       if (!is_directory(other))
+       strbuf_addstr(&path, "/refs");
+       if (!is_directory(path.buf))
                goto out;
-       other[len - 8] = '\0';
-       remote = remote_get(other);
-       transport = transport_get(remote, other);
-       for (extra = transport_get_remote_refs(transport);
-            extra;
-            extra = extra->next)
-               cb->fn(extra, cb->data);
-       transport_disconnect(transport);
+       strbuf_setlen(&path, base_len);
+
+       read_alternate_refs(path.buf, cb->fn, cb->data);
+
 out:
-       free(other);
+       strbuf_release(&path);
        return 0;
 }
 
index e597b31b38d9d7bb374af203b80e4b6e83abbc3d..bc5571574b67803249567bcee501dfcc342b0b0f 100644 (file)
@@ -255,6 +255,6 @@ int transport_refs_pushed(struct ref *ref);
 void transport_print_push_status(const char *dest, struct ref *refs,
                  int verbose, int porcelain, unsigned int *reject_reasons);
 
-typedef void alternate_ref_fn(const struct ref *, void *);
+typedef void alternate_ref_fn(const char *refname, const struct object_id *oid, void *);
 extern void for_each_alternate_ref(alternate_ref_fn, void *);
 #endif
index 7597ba3405e161d59608e18ec580946c60f9e6d2..ffb028d6231e24877b5b6af616d0c3de59516d95 100644 (file)
@@ -822,9 +822,13 @@ static void receive_needs(void)
                        use_include_tag = 1;
 
                o = parse_object(sha1_buf);
-               if (!o)
+               if (!o) {
+                       packet_write_fmt(1,
+                                        "ERR upload-pack: not our ref %s",
+                                        sha1_to_hex(sha1_buf));
                        die("git upload-pack: not our ref %s",
                            sha1_to_hex(sha1_buf));
+               }
                if (!(o->flags & WANTED)) {
                        o->flags |= WANTED;
                        if (!((allow_unadvertised_object_request & ALLOW_ANY_SHA1) == ALLOW_ANY_SHA1
index 132d342bc12bf790f9964f179385aa4f08e6ae00..4bbde924e8bf7e73f4f696d0319235ec56263a8f 100644 (file)
@@ -63,7 +63,50 @@ static int append_normalized_escapes(struct strbuf *buf,
        return 1;
 }
 
-char *url_normalize(const char *url, struct url_info *out_info)
+static const char *end_of_token(const char *s, int c, size_t n)
+{
+       const char *next = memchr(s, c, n);
+       if (!next)
+               next = s + n;
+       return next;
+}
+
+static int match_host(const struct url_info *url_info,
+                     const struct url_info *pattern_info)
+{
+       const char *url = url_info->url + url_info->host_off;
+       const char *pat = pattern_info->url + pattern_info->host_off;
+       int url_len = url_info->host_len;
+       int pat_len = pattern_info->host_len;
+
+       while (url_len && pat_len) {
+               const char *url_next = end_of_token(url, '.', url_len);
+               const char *pat_next = end_of_token(pat, '.', pat_len);
+
+               if (pat_next == pat + 1 && pat[0] == '*')
+                       /* wildcard matches anything */
+                       ;
+               else if ((pat_next - pat) == (url_next - url) &&
+                        !memcmp(url, pat, url_next - url))
+                       /* the components are the same */
+                       ;
+               else
+                       return 0; /* found an unmatch */
+
+               if (url_next < url + url_len)
+                       url_next++;
+               url_len -= url_next - url;
+               url = url_next;
+               if (pat_next < pat + pat_len)
+                       pat_next++;
+               pat_len -= pat_next - pat;
+               pat = pat_next;
+       }
+
+       return (!url_len && !pat_len);
+}
+
+static char *url_normalize_1(const char *url, struct url_info *out_info, char allow_globs)
 {
        /*
         * Normalize NUL-terminated url using the following rules:
@@ -104,7 +147,7 @@ char *url_normalize(const char *url, struct url_info *out_info)
        struct strbuf norm;
        size_t spanned;
        size_t scheme_len, user_off=0, user_len=0, passwd_off=0, passwd_len=0;
-       size_t host_off=0, host_len=0, port_len=0, path_off, path_len, result_len;
+       size_t host_off=0, host_len=0, port_off=0, port_len=0, path_off, path_len, result_len;
        const char *slash_ptr, *at_ptr, *colon_ptr, *path_start;
        char *result;
 
@@ -191,7 +234,12 @@ char *url_normalize(const char *url, struct url_info *out_info)
                strbuf_release(&norm);
                return NULL;
        }
-       spanned = strspn(url, URL_HOST_CHARS);
+
+       if (allow_globs)
+               spanned = strspn(url, URL_HOST_CHARS "*");
+       else
+               spanned = strspn(url, URL_HOST_CHARS);
+
        if (spanned < colon_ptr - url) {
                /* Host name has invalid characters */
                if (out_info) {
@@ -258,6 +306,7 @@ char *url_normalize(const char *url, struct url_info *out_info)
                                return NULL;
                        }
                        strbuf_addch(&norm, ':');
+                       port_off = norm.len;
                        strbuf_add(&norm, url, slash_ptr - url);
                        port_len = slash_ptr - url;
                }
@@ -265,7 +314,7 @@ char *url_normalize(const char *url, struct url_info *out_info)
                url = slash_ptr;
        }
        if (host_off)
-               host_len = norm.len - host_off;
+               host_len = norm.len - host_off - (port_len ? port_len + 1 : 0);
 
 
        /*
@@ -373,6 +422,7 @@ char *url_normalize(const char *url, struct url_info *out_info)
                out_info->passwd_len = passwd_len;
                out_info->host_off = host_off;
                out_info->host_len = host_len;
+               out_info->port_off = port_off;
                out_info->port_len = port_len;
                out_info->path_off = path_off;
                out_info->path_len = path_len;
@@ -380,6 +430,11 @@ char *url_normalize(const char *url, struct url_info *out_info)
        return result;
 }
 
+char *url_normalize(const char *url, struct url_info *out_info)
+{
+       return url_normalize_1(url, out_info, 0);
+}
+
 static size_t url_match_prefix(const char *url,
                               const char *url_prefix,
                               size_t url_prefix_len)
@@ -414,7 +469,7 @@ static size_t url_match_prefix(const char *url,
 
 static int match_urls(const struct url_info *url,
                      const struct url_info *url_prefix,
-                     int *exactusermatch)
+                     struct urlmatch_item *match)
 {
        /*
         * url_prefix matches url if the scheme, host and port of url_prefix
@@ -433,8 +488,8 @@ static int match_urls(const struct url_info *url,
         * contained a user name or false if url_prefix did not have a
         * user name.  If there is no match *exactusermatch is left untouched.
         */
-       int usermatched = 0;
-       int pathmatchlen;
+       char usermatched = 0;
+       size_t pathmatchlen;
 
        if (!url || !url_prefix || !url->url || !url_prefix->url)
                return 0;
@@ -454,33 +509,53 @@ static int match_urls(const struct url_info *url,
                usermatched = 1;
        }
 
-       /* check the host and port */
-       if (url_prefix->host_len != url->host_len ||
-           strncmp(url->url + url->host_off,
-                   url_prefix->url + url_prefix->host_off, url->host_len))
-               return 0; /* host names and/or ports do not match */
+       /* check the host */
+       if (!match_host(url, url_prefix))
+               return 0; /* host names do not match */
+
+       /* check the port */
+       if (url_prefix->port_len != url->port_len ||
+           strncmp(url->url + url->port_off,
+                   url_prefix->url + url_prefix->port_off, url->port_len))
+               return 0; /* ports do not match */
 
        /* check the path */
        pathmatchlen = url_match_prefix(
                url->url + url->path_off,
                url_prefix->url + url_prefix->path_off,
                url_prefix->url_len - url_prefix->path_off);
+       if (!pathmatchlen)
+               return 0; /* paths do not match */
 
-       if (pathmatchlen && exactusermatch)
-               *exactusermatch = usermatched;
-       return pathmatchlen;
+       if (match) {
+               match->hostmatch_len = url_prefix->host_len;
+               match->pathmatch_len = pathmatchlen;
+               match->user_matched = usermatched;
+       }
+
+       return 1;
+}
+
+static int cmp_matches(const struct urlmatch_item *a,
+                      const struct urlmatch_item *b)
+{
+       if (a->hostmatch_len != b->hostmatch_len)
+               return a->hostmatch_len < b->hostmatch_len ? -1 : 1;
+       if (a->pathmatch_len != b->pathmatch_len)
+               return a->pathmatch_len < b->pathmatch_len ? -1 : 1;
+       if (a->user_matched != b->user_matched)
+               return b->user_matched ? -1 : 1;
+       return 0;
 }
 
 int urlmatch_config_entry(const char *var, const char *value, void *cb)
 {
        struct string_list_item *item;
        struct urlmatch_config *collect = cb;
-       struct urlmatch_item *matched;
+       struct urlmatch_item matched = {0};
        struct url_info *url = &collect->url;
        const char *key, *dot;
        struct strbuf synthkey = STRBUF_INIT;
-       size_t matched_len = 0;
-       int user_matched = 0;
        int retval;
 
        if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {
@@ -494,13 +569,13 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
                struct url_info norm_info;
 
                config_url = xmemdupz(key, dot - key);
-               norm_url = url_normalize(config_url, &norm_info);
+               norm_url = url_normalize_1(config_url, &norm_info, 1);
                free(config_url);
                if (!norm_url)
                        return 0;
-               matched_len = match_urls(url, &norm_info, &user_matched);
+               retval = match_urls(url, &norm_info, &matched);
                free(norm_url);
-               if (!matched_len)
+               if (!retval)
                        return 0;
                key = dot + 1;
        }
@@ -510,24 +585,18 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
 
        item = string_list_insert(&collect->vars, key);
        if (!item->util) {
-               matched = xcalloc(1, sizeof(*matched));
-               item->util = matched;
+               item->util = xcalloc(1, sizeof(matched));
        } else {
-               matched = item->util;
-               /*
-                * Is our match shorter?  Is our match the same
-                * length, and without user while the current
-                * candidate is with user?  Then we cannot use it.
-                */
-               if (matched_len < matched->matched_len ||
-                   ((matched_len == matched->matched_len) &&
-                    (!user_matched && matched->user_matched)))
+               if (cmp_matches(&matched, item->util) < 0)
+                        /*
+                         * Our match is worse than the old one,
+                         * we cannot use it.
+                         */
                        return 0;
                /* Otherwise, replace it with this one. */
        }
 
-       matched->matched_len = matched_len;
-       matched->user_matched = user_matched;
+       memcpy(item->util, &matched, sizeof(matched));
        strbuf_addstr(&synthkey, collect->section);
        strbuf_addch(&synthkey, '.');
        strbuf_addstr(&synthkey, key);
index 528862adc55c43ed26763b3c05e1d27d558a1b74..37ee5da85e2dd3f0ba3ee12d0c515fa6def2a043 100644 (file)
@@ -18,11 +18,12 @@ struct url_info {
        size_t passwd_len;      /* length of passwd; if passwd_off != 0 but
                                   passwd_len == 0, an empty passwd was given */
        size_t host_off;        /* offset into url to start of host name (0 => none) */
-       size_t host_len;        /* length of host name; this INCLUDES any ':portnum';
+       size_t host_len;        /* length of host name;
                                 * file urls may have host_len == 0 */
-       size_t port_len;        /* if a portnum is present (port_len != 0), it has
-                                * this length (excluding the leading ':') at the
-                                * end of the host name (always 0 for file urls) */
+       size_t port_off;        /* offset into url to start of port number (0 => none) */
+       size_t port_len;        /* if a portnum is present (port_off != 0), it has
+                                * this length (excluding the leading ':') starting
+                                * from port_off (always 0 for file urls) */
        size_t path_off;        /* offset into url to the start of the url path;
                                 * this will always point to a '/' character
                                 * after the url has been normalized */
@@ -33,7 +34,8 @@ struct url_info {
 extern char *url_normalize(const char *, struct url_info *);
 
 struct urlmatch_item {
-       size_t matched_len;
+       size_t hostmatch_len;
+       size_t pathmatch_len;
        char user_matched;
 };
 
index 2125d6da26dbdc01f7397415ef1f67616e6f2a71..8b732e40bce4968257c2918219f7dfdb0b9ed3a0 100644 (file)
@@ -262,25 +262,22 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {
 
 struct userdiff_driver *userdiff_find_by_path(const char *path)
 {
-       static struct git_attr *attr;
-       struct git_attr_check check;
-
-       if (!attr)
-               attr = git_attr("diff");
-       check.attr = attr;
+       static struct attr_check *check;
 
+       if (!check)
+               check = attr_check_initl("diff", NULL);
        if (!path)
                return NULL;
-       if (git_check_attr(path, 1, &check))
+       if (git_check_attr(path, check))
                return NULL;
 
-       if (ATTR_TRUE(check.value))
+       if (ATTR_TRUE(check->items[0].value))
                return &driver_true;
-       if (ATTR_FALSE(check.value))
+       if (ATTR_FALSE(check->items[0].value))
                return &driver_false;
-       if (ATTR_UNSET(check.value))
+       if (ATTR_UNSET(check->items[0].value))
                return NULL;
-       return userdiff_find_by_name(check.value);
+       return userdiff_find_by_name(check->items[0].value);
 }
 
 struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
index d633761575bff4811fa4f3f3822807b1f77f5ecb..fa7bc67a50a6d52116b0ca994b473668998f1c3f 100644 (file)
@@ -175,7 +175,7 @@ struct worktree **get_worktrees(unsigned flags)
        struct dirent *d;
        int counter = 0, alloc = 2;
 
-       list = xmalloc(alloc * sizeof(struct worktree *));
+       ALLOC_ARRAY(list, alloc);
 
        list[counter++] = get_main_worktree();
 
@@ -255,7 +255,7 @@ struct worktree *find_worktree(struct worktree **list,
                return wt;
 
        arg = prefix_filename(prefix, strlen(prefix), arg);
-       path = real_pathdup(arg);
+       path = real_pathdup(arg, 1);
        for (; *list; list++)
                if (!fspathcmp(path, real_path((*list)->path)))
                        break;
index e7f197996868a614c84537ad96fc672ea901148d..0542fc75821fdbdd7bfecebec5f7df01e49dcb0c 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -440,23 +440,6 @@ int xmkstemp(char *template)
        return fd;
 }
 
-/* git_mkstemp() - create tmp file honoring TMPDIR variable */
-int git_mkstemp(char *path, size_t len, const char *template)
-{
-       const char *tmp;
-       size_t n;
-
-       tmp = getenv("TMPDIR");
-       if (!tmp)
-               tmp = "/tmp";
-       n = snprintf(path, len, "%s/%s", tmp, template);
-       if (len <= n) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-       return mkstemp(path);
-}
-
 /* Adapted from libiberty's mkstemp.c. */
 
 #undef TMP_MAX
@@ -531,13 +514,6 @@ int git_mkstemp_mode(char *pattern, int mode)
        return git_mkstemps_mode(pattern, 0, mode);
 }
 
-#ifdef NO_MKSTEMPS
-int gitmkstemps(char *pattern, int suffix_len)
-{
-       return git_mkstemps_mode(pattern, suffix_len, 0600);
-}
-#endif
-
 int xmkstemp_mode(char *template, int mode)
 {
        int fd;
diff --git a/ws.c b/ws.c
index ea4b2b1dfd64ce41f4752a93f4d5478c4e53deda..a07caedd5a565bbf29e4f9b3036ac60e6e9f716e 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -71,24 +71,17 @@ unsigned parse_whitespace_rule(const char *string)
        return rule;
 }
 
-static void setup_whitespace_attr_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_whitespace;
-
-       if (!attr_whitespace)
-               attr_whitespace = git_attr("whitespace");
-       check[0].attr = attr_whitespace;
-}
-
 unsigned whitespace_rule(const char *pathname)
 {
-       struct git_attr_check attr_whitespace_rule;
+       static struct attr_check *attr_whitespace_rule;
+
+       if (!attr_whitespace_rule)
+               attr_whitespace_rule = attr_check_initl("whitespace", NULL);
 
-       setup_whitespace_attr_check(&attr_whitespace_rule);
-       if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {
+       if (!git_check_attr(pathname, attr_whitespace_rule)) {
                const char *value;
 
-               value = attr_whitespace_rule.value;
+               value = attr_whitespace_rule->items[0].value;
                if (ATTR_TRUE(value)) {
                        /* true (whitespace) */
                        unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
index 7389ce41022dbee5776d44a937e8bcf780656f42..8c88dbde3827ce9d1b5e4f95287513514b80fac1 100644 (file)
@@ -183,16 +183,14 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
                                /*
                                 * We don't need additional context if
-                                * a whole function was added, possibly
-                                * starting with empty lines.
+                                * a whole function was added.
                                 */
-                               while (i2 < xe->xdf2.nrec &&
-                                      is_empty_rec(&xe->xdf2, i2))
+                               while (i2 < xe->xdf2.nrec) {
+                                       if (match_func_rec(&xe->xdf2, xecfg, i2,
+                                               dummy, sizeof(dummy)) >= 0)
+                                               goto post_context_calculation;
                                        i2++;
-                               if (i2 < xe->xdf2.nrec &&
-                                   match_func_rec(&xe->xdf2, xecfg, i2,
-                                                  dummy, sizeof(dummy)) >= 0)
-                                       goto post_context_calculation;
+                               }
 
                                /*
                                 * Otherwise get more context from the