Merge branch 'bc/object-id'
authorJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:17:37 +0000 (12:17 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:17:37 +0000 (12:17 -0700)
for_each_ref() callback functions were taught to name the objects
not with "unsigned char sha1[20]" but with "struct object_id".

* bc/object-id: (56 commits)
struct ref_lock: convert old_sha1 member to object_id
warn_if_dangling_symref(): convert local variable "junk" to object_id
each_ref_fn_adapter(): remove adapter
rev_list_insert_ref(): remove unneeded arguments
rev_list_insert_ref_oid(): new function, taking an object_oid
mark_complete(): remove unneeded arguments
mark_complete_oid(): new function, taking an object_oid
clear_marks(): rewrite to take an object_id argument
mark_complete(): rewrite to take an object_id argument
send_ref(): convert local variable "peeled" to object_id
upload-pack: rewrite functions to take object_id arguments
find_symref(): convert local variable "unused" to object_id
find_symref(): rewrite to take an object_id argument
write_one_ref(): rewrite to take an object_id argument
write_refs_to_temp_dir(): convert local variable sha1 to object_id
submodule: rewrite to take an object_id argument
shallow: rewrite functions to take object_id arguments
handle_one_ref(): rewrite to take an object_id argument
add_info_ref(): rewrite to take an object_id argument
handle_one_reflog(): rewrite to take an object_id argument
...

79 files changed:
.gitignore
Documentation/RelNotes/2.4.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.5.0.txt
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-cat-file.txt
Documentation/git-commit.txt
Documentation/git-for-each-ref.txt
Documentation/git-http-backend.txt
Documentation/git-status.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/howto/new-command.txt
Documentation/pretty-formats.txt
Documentation/revisions.txt
Documentation/technical/api-remote.txt
Documentation/technical/http-protocol.txt
Documentation/technical/index-format.txt
Documentation/technical/protocol-capabilities.txt
Makefile
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/clean.c
builtin/commit.c
builtin/for-each-ref.c
builtin/log.c
builtin/merge.c
builtin/rm.c
builtin/update-index.c
cache.h
command-list.txt
compat/mingw.c
compat/mingw.h
dir.c
dir.h
ewah/ewah_io.c
ewah/ewok.h
fetch-pack.c
generate-cmdlist.perl [new file with mode: 0755]
generate-cmdlist.sh [deleted file]
git-compat-util.h
git-mergetool--lib.sh
git-mergetool.sh
git-pull.sh
git-rebase--interactive.sh
git-stash.sh
help.c
http-backend.c
merge-recursive.c
mergetools/winmerge [new file with mode: 0644]
pack-bitmap.c
read-cache.c
remote.c
remote.h
rerere.c
sha1_file.c
sha1_name.c
split-index.c
submodule.c
t/t0021-conversion.sh
t/t1006-cat-file.sh
t/t1507-rev-parse-upstream.sh
t/t1514-rev-parse-push.sh [new file with mode: 0755]
t/t3903-stash.sh
t/t5407-post-rewrite-hook.sh
t/t5516-fetch-push.sh
t/t5524-pull-msg.sh
t/t5551-http-fetch-smart.sh
t/t6300-for-each-ref.sh
t/t7063-status-untracked-cache.sh [new file with mode: 0755]
t/t7601-merge-pull-config.sh
test-dump-untracked-cache.c [new file with mode: 0644]
tree-walk.c
tree-walk.h
unpack-trees.c
upload-pack.c
wt-status.c
index a05241916c9c9a3760a6e98670a7f6427d553d77..422c5382c1acfde24223fda6236dd3586456a263 100644 (file)
 /test-delta
 /test-dump-cache-tree
 /test-dump-split-index
+/test-dump-untracked-cache
 /test-scrap-cache-tree
 /test-genrandom
 /test-hashmap
diff --git a/Documentation/RelNotes/2.4.2.txt b/Documentation/RelNotes/2.4.2.txt
new file mode 100644 (file)
index 0000000..250cdc4
--- /dev/null
@@ -0,0 +1,45 @@
+Git v2.4.2 Release Notes
+========================
+
+Fixes since v2.4.1
+------------------
+
+ * "git rev-list --objects $old --not --all" to see if everything that
+   is reachable from $old is already connected to the existing refs
+   was very inefficient.
+
+ * "hash-object --literally" introduced in v2.2 was not prepared to
+   take a really long object type name.
+
+ * "git rebase --quiet" was not quite quiet when there is nothing to
+   do.
+
+ * The completion for "log --decorate=" parameter value was incorrect.
+
+ * "filter-branch" corrupted commit log message that ends with an
+   incomplete line on platforms with some "sed" implementations that
+   munge such a line.  Work it around by avoiding to use "sed".
+
+ * "git daemon" fails to build from the source under NO_IPV6
+   configuration (regression in 2.4).
+
+ * "git stash pop/apply" forgot to make sure that not just the working
+   tree is clean but also the index is clean. The latter is important
+   as a stash application can conflict and the index will be used for
+   conflict resolution.
+
+ * We have prepended $GIT_EXEC_PATH and the path "git" is installed in
+   (typically "/usr/bin") to $PATH when invoking subprograms and hooks
+   for almost eternity, but the original use case the latter tried to
+   support was semi-bogus (i.e. install git to /opt/foo/git and run it
+   without having /opt/foo on $PATH), and more importantly it has
+   become less and less relevant as Git grew more mainstream (i.e. the
+   users would _want_ to have it on their $PATH).  Stop prepending the
+   path in which "git" is installed to users' $PATH, as that would
+   interfere the command search order people depend on (e.g. they may
+   not like versions of programs that are unrelated to Git in /usr/bin
+   and want to override them by having different ones in /usr/local/bin
+   and have the latter directory earlier in their $PATH).
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
index b102837bb07f490ea83a904f9bc3b867f332b038..0b7fe2af33df36c1d5341912ee5f6ba480b8bcd0 100644 (file)
@@ -9,6 +9,9 @@ Ports
 
 UI, Workflows & Features
 
+ * List of commands shown by "git help" are grouped along the workflow
+   elements to help early learners.
+
  * "git p4" now detects the filetype (e.g. binary) correctly even when
    the files are opened exclusively.
 
@@ -79,12 +82,30 @@ UI, Workflows & Features
 
  * The Git subcommand completion (in contrib/) listed credential
    helpers among candidates, which is not something the end user would
-   invoke interatively.
+   invoke interactively.
+
+ * The index file can be taught with "update-index --untracked-cache"
+   to optionally remember already seen untracked files, in order to
+   speed up "git status" in a working tree with tons of cruft.
+
+ * "git mergetool" learned to drive WinMerge as a backend.
+
+ * "git upload-pack" that serves "git fetch" can be told to serve
+   commits that are not at the tip of any ref, as long as they are
+   reachable from a ref, with uploadpack.allowReachableSHA1InWant
+   configuration variable.
+
+ * "git cat-file --batch(-check)" learned the "--follow-symlinks"
+   option that follows an in-tree symbolic link when asked about an
+   object via extended SHA-1 syntax, e.g. HEAD:RelNotes that points at
+   Documentation/RelNotes/2.5.0.txt.  With the new option, the command
+   behaves as if HEAD:Documentation/RelNotes/2.5.0.txt was given as
+   input instead.
 
 
 Performance, Internal Implementation, Development Support etc.
 
- * "unsigned char [20]" used thoughout the code to represent object
+ * "unsigned char [20]" used throughout the code to represent object
    names are being converted into a semi-opaque "struct object_id".
    This effort is expected to interfere with other topics in flight,
    but hopefully will give us one extra level of abstraction in the
@@ -180,9 +201,9 @@ notes for details).
  * Some time ago, "git blame" (incorrectly) lost the convert_to_git()
    call when synthesizing a fake "tip" commit that represents the
    state in the working tree, which broke folks who record the history
-   with LF line ending to make their project portabile across
-   platforms while terminating lines in their working tree files with
-   CRLF for their platform.
+   with LF line ending to make their project portable across platforms
+   while terminating lines in their working tree files with CRLF for
+   their platform.
    (merge 4bf256d tb/blame-resurrect-convert-to-git later to maint).
 
  * We avoid setting core.worktree when the repository location is the
@@ -208,7 +229,7 @@ notes for details).
    (merge 27547e5 cn/bom-in-gitignore later to maint).
 
  * a few helper scripts in the test suite did not report errors
-   correcty.
+   correctly.
    (merge de248e9 ep/fix-test-lib-functions-report later to maint).
 
  * The default $HOME/.gitconfig file created upon "git config --global"
@@ -308,10 +329,57 @@ notes for details).
    (merge ad3967a jk/stripspace-asciidoctor-fix later to maint).
    (merge 975e382 ja/tutorial-asciidoctor-fix later to maint).
 
+ * The code to read pack-bitmap wanted to allocate a few hundred
+   pointers to a structure, but by mistake allocated and leaked memory
+   enough to hold that many actual structures.  Correct the allocation
+   size and also have it on stack, as it is small enough.
+   (merge 599dc76 rs/plug-leak-in-pack-bitmaps later to maint).
+
+ * The pull.ff configuration was supposed to override the merge.ff
+   configuration, but it didn't.
+   (merge db9bb28 pt/pull-ff-vs-merge-ff later to maint).
+
+ * "git pull --log" and "git pull --no-log" worked as expected, but
+   "git pull --log=20" did not.
+   (merge 5061a44 pt/pull-log-n later to maint).
+
+ * "git rerere forget" in a repository without rerere enabled gave a
+   cryptic error message; it should be a silent no-op instead.
+   (merge 0544574 jk/rerere-forget-check-enabled later to maint).
+
+ * "git rebase -i" fired post-rewrite hook when it shouldn't (namely,
+   when it was told to stop sequencing with 'exec' insn).
+   (merge 141ff8f mm/rebase-i-post-rewrite-exec later to maint).
+
+ * Clarify that "log --raw" and "log --format=raw" are unrelated
+   concepts.
+   (merge 92de921 mm/log-format-raw-doc later to maint).
+
+ * Make "git stash something --help" error out, so that users can
+   safely say "git stash drop --help".
+   (merge 5ba2831 jk/stash-options later to maint).
+
+ * The clean/smudge interface did not work well when filtering an
+   empty contents (failed and then passed the empty input through).
+   It can be argued that a filter that produces anything but empty for
+   an empty input is nonsense, but if the user wants to do strange
+   things, then why not?
+   (merge f6a1e1e jh/filter-empty-contents later to maint).
+
+ * Communication between the HTTP server and http_backend process can
+   lead to a dead-lock when relaying a large ref negotiation request.
+   Diagnose the situation better, and mitigate it by reading such a
+   request first into core (to a reasonable limit).
+   (merge 636614f jk/http-backend-deadlock later to maint).
+
+ * "git clean pathspec..." tried to lstat(2) and complain even for
+   paths outside the given pathspec.
+   (merge 838d6a9 dt/clean-pathspec-filter-then-lstat later to maint).
+
  * Code cleanups and documentation updates.
    (merge 0269f96 mm/usage-log-l-can-take-regex later to maint).
    (merge 64f2589 nd/t1509-chroot-test later to maint).
-   (merge f86a374 sb/test-bitmap-free-at-end later to maint).
+   (merge d201a1e sb/test-bitmap-free-at-end later to maint).
    (merge 05bfc7d sb/line-log-plug-pairdiff-leak later to maint).
    (merge 846e5df pt/xdg-config-path later to maint).
    (merge 1154aa4 jc/plug-fmt-merge-msg-leak later to maint).
@@ -320,3 +388,6 @@ notes for details).
    (merge bbf431c ps/doc-packfile-vs-pack-file later to maint).
    (merge 309a9e3 jk/skip-http-tests-under-no-curl later to maint).
    (merge ccd593c dl/branch-error-message later to maint).
+   (merge 22570b6 rs/janitorial later to maint).
+   (merge 5c2a581 mc/commit-doc-grammofix later to maint).
+   (merge ce41720 ah/usage-strings later to maint).
index 04f99778d81def42e9e129b2f5b2b0551a0c760f..5aa73cfe458d9485510c6a9a67e38e522394b62a 100755 (executable)
@@ -38,6 +38,10 @@ sub format_one {
        }
 }
 
+while (<>) {
+       last if /^### command list/;
+}
+
 my %cmds = ();
 for (sort <>) {
        next if /^#/;
index 0f668bbe11a5b439fca53ca7aa52a43782e30b79..4d21ce1647f6125584872c8c67823ae8368b1132 100644 (file)
@@ -2061,7 +2061,7 @@ pull.ff::
        a case (equivalent to giving the `--no-ff` option from the command
        line). When set to `only`, only such fast-forward merges are
        allowed (equivalent to giving the `--ff-only` option from the
-       command line).
+       command line). This setting overrides `merge.ff` when pulling.
 
 pull.rebase::
        When true, rebase branches on top of the fetched branch, instead
@@ -2558,14 +2558,20 @@ uploadpack.hideRefs::
        are under the hierarchies listed on the value of this
        variable is excluded, and is hidden from `git ls-remote`,
        `git fetch`, etc.  An attempt to fetch a hidden ref by `git
-       fetch` will fail.  See also `uploadpack.allowtipsha1inwant`.
+       fetch` will fail.  See also `uploadpack.allowTipSHA1InWant`.
 
-uploadpack.allowtipsha1inwant::
+uploadpack.allowTipSHA1InWant::
        When `uploadpack.hideRefs` is in effect, allow `upload-pack`
        to accept a fetch request that asks for an object at the tip
        of a hidden ref (by default, such a request is rejected).
        see also `uploadpack.hideRefs`.
 
+uploadpack.allowReachableSHA1InWant::
+       Allow `upload-pack` to accept a fetch request that asks for an
+       object that is reachable from any ref tip. However, note that
+       calculating object reachability is computationally expensive.
+       Defaults to `false`.
+
 uploadpack.keepAlive::
        When `upload-pack` has started `pack-objects`, there may be a
        quiet period while `pack-objects` prepares the pack. Normally
index b7c3afeb3a0ce871d426de6912a6a875f37620fa..3ad6404dbcf2915721ac963085bbc0269c1d7312 100644 (file)
@@ -43,10 +43,19 @@ endif::git-format-patch[]
 
 ifndef::git-format-patch[]
 --raw::
-       Generate the raw format.
+ifndef::git-log[]
+       Generate the diff in raw format.
 ifdef::git-diff-core[]
        This is the default.
 endif::git-diff-core[]
+endif::git-log[]
+ifdef::git-log[]
+       For each commit, show a summary of changes using the raw diff
+       format. See the "RAW OUTPUT FORMAT" section of
+       linkgit:git-diff[1]. This is different from showing the log
+       itself in raw format, which you can achieve with
+       `--format=raw`.
+endif::git-log[]
 endif::git-format-patch[]
 
 ifndef::git-format-patch[]
index 499ae7b98a4d9bf48aa2c5a235608dfcffcc1c10..319ab4cb086874da5e2f153d69d8f9af1f18461e 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object>
-'git cat-file' (--batch | --batch-check) < <list-of-objects>
+'git cat-file' (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>
 
 DESCRIPTION
 -----------
@@ -72,6 +72,62 @@ OPTIONS
 --allow-unknown-type::
        Allow -s or -t to query broken/corrupt objects of unknown type.
 
+--follow-symlinks::
+       With --batch or --batch-check, follow symlinks inside the
+       repository when requesting objects with extended SHA-1
+       expressions of the form tree-ish:path-in-tree.  Instead of
+       providing output about the link itself, provide output about
+       the linked-to object.  If a symlink points outside the
+       tree-ish (e.g. a link to /foo or a root-level link to ../foo),
+       the portion of the link which is outside the tree will be
+       printed.
++
+This option does not (currently) work correctly when an object in the
+index is specified (e.g. `:link` instead of `HEAD:link`) rather than
+one in the tree.
++
+This option cannot (currently) be used unless `--batch` or
+`--batch-check` is used.
++
+For example, consider a git repository containing:
++
+--
+       f: a file containing "hello\n"
+       link: a symlink to f
+       dir/link: a symlink to ../f
+       plink: a symlink to ../f
+       alink: a symlink to /etc/passwd
+--
++
+For a regular file `f`, `echo HEAD:f | git cat-file --batch` would print
++
+--
+       ce013625030ba8dba906f756967f9e9ca394464a blob 6
+--
++
+And `echo HEAD:link | git cat-file --batch --follow-symlinks` would
+print the same thing, as would `HEAD:dir/link`, as they both point at
+`HEAD:f`.
++
+Without `--follow-symlinks`, these would print data about the symlink
+itself.  In the case of `HEAD:link`, you would see
++
+--
+       4d1ae35ba2c8ec712fa2a379db44ad639ca277bd blob 1
+--
++
+Both `plink` and `alink` point outside the tree, so they would
+respectively print:
++
+--
+       symlink 4
+       ../f
+
+       symlink 11
+       /etc/passwd
+--
+
+
 OUTPUT
 ------
 If '-t' is specified, one of the <type>.
@@ -151,6 +207,47 @@ the repository, then `cat-file` will ignore any custom format and print:
 <object> SP missing LF
 ------------
 
+If --follow-symlinks is used, and a symlink in the repository points
+outside the repository, then `cat-file` will ignore any custom format
+and print:
+
+------------
+symlink SP <size> LF
+<symlink> LF
+------------
+
+The symlink will either be absolute (beginning with a /), or relative
+to the tree root.  For instance, if dir/link points to ../../foo, then
+<symlink> will be ../foo.  <size> is the size of the symlink in bytes.
+
+If --follow-symlinks is used, the following error messages will be
+displayed:
+
+------------
+<object> SP missing LF
+------------
+is printed when the initial symlink requested does not exist.
+
+------------
+dangling SP <size> LF
+<object> LF
+------------
+is printed when the initial symlink exists, but something that
+it (transitive-of) points to does not.
+
+------------
+loop SP <size> LF
+<object> LF
+------------
+is printed for symlink loops (or any symlinks that
+require more than 40 link resolutions to resolve).
+
+------------
+notdir SP <size> LF
+<object> LF
+------------
+is printed when, during symlink resolution, a file is used as a
+directory name.
 
 CAVEATS
 -------
index 617dea083e273467f50493dacd3d859ebbe52aff..904dafa0f7070fc438f138393a3b356542ae04d9 100644 (file)
@@ -94,7 +94,7 @@ OPTIONS
 --reset-author::
        When used with -C/-c/--amend options, or when committing after a
        a conflicting cherry-pick, declare that the authorship of the
-       resulting commit now belongs of the committer. This also renews
+       resulting commit now belongs to the committer. This also renews
        the author timestamp.
 
 --short::
index 42408752d0848f0854308407ff7606b40f1e6eed..7f8d9a5b5f358baa50bdfe205f555ea904f9f629 100644 (file)
@@ -97,6 +97,12 @@ upstream::
        or "=" (in sync).  Has no effect if the ref does not have
        tracking information associated with it.
 
+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.
+
 HEAD::
        '*' if HEAD matches current ref (the checked out branch), ' '
        otherwise.
index 3ca18c4de519ebdf0a2af6509d0d6bed3e6e492c..9268fb6b1ea2de2b29fb077a1a675c727526abeb 100644 (file)
@@ -255,6 +255,15 @@ The GIT_HTTP_EXPORT_ALL environmental variable may be passed to
 'git-http-backend' to bypass the check for the "git-daemon-export-ok"
 file in each repository before allowing export of that repository.
 
+The `GIT_HTTP_MAX_REQUEST_BUFFER` environment variable (or the
+`http.maxRequestBuffer` config variable) may be set to change the
+largest ref negotiation request that git will handle during a fetch; any
+fetch requiring a larger buffer will not succeed.  This value should not
+normally need to be changed, but may be helpful if you are fetching from
+a repository with an extremely large number of refs.  The value can be
+specified with a unit (e.g., `100M` for 100 megabytes). The default is
+10 megabytes.
+
 The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
 GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
 ensuring that any reflogs created by 'git-receive-pack' contain some
index 5221f950ce06cda1a764424ec217760d044a5ac3..335f3123353482cfd708420dac3b766f181e89ff 100644 (file)
@@ -66,7 +66,10 @@ When `-u` option is not used, untracked files and directories are
 shown (i.e. the same as specifying `normal`), to help you avoid
 forgetting to add newly created files.  Because it takes extra work
 to find untracked files in the filesystem, this mode may take some
-time in a large working tree.  You can use `no` to have `git status`
+time in a large working tree.
+Consider enabling untracked cache and split index if supported (see
+`git update-index --untracked-cache` and `git update-index
+--split-index`), Otherwise you can use `no` to have `git status`
 return more quickly without showing untracked files.
 +
 The default can be changed using the status.showUntrackedFiles
index aff01798cdf6b114009eae8dfc4f2866a8a24d17..1a296bc29a16fcf4ee6b581310033861c1ec82bc 100644 (file)
@@ -170,6 +170,20 @@ may not support it yet.
        the shared index file. This mode is designed for very large
        indexes that take a significant amount of time to read or write.
 
+--untracked-cache::
+--no-untracked-cache::
+       Enable or disable untracked cache extension. This could speed
+       up for commands that involve determining untracked files such
+       as `git status`. The underlying operating system and file
+       system must change `st_mtime` field of a directory if files
+       are added or deleted in that directory.
+
+--force-untracked-cache::
+       For safety, `--untracked-cache` performs tests on the working
+       directory to make sure untracked cache can be used. These
+       tests can take a few seconds. `--force-untracked-cache` can be
+       used to skip the tests.
+
 \--::
        Do not interpret any more arguments as options.
 
index 38cc004065a6f8a00dc5014de5b732dc57e8d114..ccc12b280652609bfd5eb089e194a9219b7fc9f2 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v2.4.1/git.html[documentation for release 2.4.1]
+* link:v2.4.2/git.html[documentation for release 2.4.2]
 
 * release notes for
+  link:RelNotes/2.4.2.txt[2.4.2],
   link:RelNotes/2.4.1.txt[2.4.1],
   link:RelNotes/2.4.0.txt[2.4].
 
index d7de5a3e9ed236c87a62b577ee5978e02d855a2a..6d772bd9279b1564b66ad01c919b97569c9f2c0d 100644 (file)
@@ -95,7 +95,9 @@ your language, document it in the INSTALL file.
 that categorizes commands by type, so they can be listed in appropriate
 subsections in the documentation's summary command list.  Add an entry
 for yours.  To understand the categories, look at git-commands.txt
-in the main directory.
+in the main directory.  If the new command is part of the typical Git
+workflow and you believe it common enough to be mentioned in 'git help',
+map this command to a common group in the column [common].
 
 7. Give the maintainer one paragraph to include in the RelNotes file
 to describe the new feature; a good place to do so is in the cover
index dcf7429a47c4853e18ea06c8b5af4df5ece5c0fa..dc865cbb2766004089f6bf49bca2638ed6f693d9 100644 (file)
@@ -79,7 +79,10 @@ stored in the commit object.  Notably, the SHA-1s are
 displayed in full, regardless of whether --abbrev or
 --no-abbrev are used, and 'parents' information show the
 true parent commits, without taking grafts or history
-simplification into account.
+simplification into account. Note that this format affects the way
+commits are displayed, but not the way the diff is shown e.g. with
+`git log --raw`. To get full object names in a raw diff format,
+use `--no-abbrev`.
 
 * 'format:<string>'
 +
index 07961185fe0cddf78822aee75b43f206cbcd4579..d85e3033646faec23b23065cbd78436aca508acf 100644 (file)
@@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8.
   `branch.<name>.merge`).  A missing branchname defaults to the
   current one.
 
+'<branchname>@\{push\}', e.g. 'master@\{push\}', '@\{push\}'::
+  The suffix '@\{push}' reports the branch "where we would push to" if
+  `git push` were run while `branchname` was checked out (or the current
+  'HEAD' if no branchname is specified). Since our push destination is
+  in a remote repository, of course, we report the local tracking branch
+  that corresponds to that branch (i.e., something in 'refs/remotes/').
++
+Here's an example to make it more clear:
++
+------------------------------
+$ git config push.default current
+$ git config remote.pushdefault myfork
+$ git checkout -b mybranch origin/master
+
+$ git rev-parse --symbolic-full-name @{upstream}
+refs/remotes/origin/master
+
+$ git rev-parse --symbolic-full-name @{push}
+refs/remotes/myfork/mybranch
+------------------------------
++
+Note in the example that we set up a triangular workflow, where we pull
+from one location and push to another. In a non-triangular workflow,
+'@\{push}' is the same as '@\{upstream}', and there is no need for it.
+
 '<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
   A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
index 5d245aa9d1b1654749ee51495e9eebda261c3681..2cfdd224a83710f32e344b76c131181527caf98c 100644 (file)
@@ -97,10 +97,6 @@ It contains:
 
        The name of the remote listed in the configuration.
 
-`remote`::
-
-       The struct remote for that remote.
-
 `merge_name`::
 
        An array of the "merge" lines in the configuration.
index 229f845dfa67b9c2e74b2eb1de80a224ea757b53..1c561bdd9204fe221512064f765bea9eb7ec5945 100644 (file)
@@ -319,7 +319,8 @@ Servers SHOULD support all capabilities defined here.
 Clients MUST send at least one "want" command in the request body.
 Clients MUST NOT reference an id in a "want" command which did not
 appear in the response obtained through ref discovery unless the
-server advertises capability `allow-tip-sha1-in-want`.
+server advertises capability `allow-tip-sha1-in-want` or
+`allow-reachable-sha1-in-want`.
 
   compute_request   =  want_list
                       have_list
index 35112e4966f9b021b0889d734d5cb945b8943003..b7093af8b23e6a83741b81678e7aae0c5ff7c88a 100644 (file)
@@ -233,3 +233,65 @@ Git index format
   The remaining index entries after replaced ones will be added to the
   final index. These added entries are also sorted by entry name then
   stage.
+
+== Untracked cache
+
+  Untracked cache saves the untracked file list and necessary data to
+  verify the cache. The signature for this extension is { 'U', 'N',
+  'T', 'R' }.
+
+  The extension starts with
+
+  - A sequence of NUL-terminated strings, preceded by the size of the
+    sequence in variable width encoding. Each string describes the
+    environment where the cache can be used.
+
+  - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from
+    ctime field until "file size".
+
+  - Stat data of core.excludesfile
+
+  - 32-bit dir_flags (see struct dir_struct)
+
+  - 160-bit SHA-1 of $GIT_DIR/info/exclude. Null SHA-1 means the file
+    does not exist.
+
+  - 160-bit SHA-1 of core.excludesfile. Null SHA-1 means the file does
+    not exist.
+
+  - NUL-terminated string of per-dir exclude file name. This usually
+    is ".gitignore".
+
+  - The number of following directory blocks, variable width
+    encoding. If this number is zero, the extension ends here with a
+    following NUL.
+
+  - A number of directory blocks in depth-first-search order, each
+    consists of
+
+    - The number of untracked entries, variable width encoding.
+
+    - The number of sub-directory blocks, variable width encoding.
+
+    - The directory name terminated by NUL.
+
+    - A number of untrached file/dir names terminated by NUL.
+
+The remaining data of each directory block is grouped by type:
+
+  - An ewah bitmap, the n-th bit marks whether the n-th directory has
+    valid untracked cache entries.
+
+  - An ewah bitmap, the n-th bit records "check-only" bit of
+    read_directory_recursive() for the n-th directory.
+
+  - An ewah bitmap, the n-th bit indicates whether SHA-1 and stat data
+    is valid for the n-th directory and exists in the next data.
+
+  - An array of stat data. The n-th data corresponds with the n-th
+    "one" bit in the previous ewah bitmap.
+
+  - An array of SHA-1. The n-th SHA-1 corresponds with the n-th "one" bit
+    in the previous ewah bitmap.
+
+  - One NUL.
index 4f8a7bfb4c68e42e4969c8c9d0b3555b880a7e58..eaab6b4ac723c9f5d7dde9d70fe7e01d234e7734 100644 (file)
@@ -260,6 +260,13 @@ If the upload-pack server advertises this capability, fetch-pack may
 send "want" lines with SHA-1s that exist at the server but are not
 advertised by upload-pack.
 
+allow-reachable-sha1-in-want
+----------------------------
+
+If the upload-pack server advertises this capability, fetch-pack may
+send "want" lines with SHA-1s that exist at the server but are not
+advertised by upload-pack.
+
 push-cert=<nonce>
 -----------------
 
index 25a453bf2bfd1e17c3fee1ce6697f268804401fc..54ec511dffb0a7b6775f360fc10deaa2008fdee9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -574,6 +574,7 @@ TEST_PROGRAMS_NEED_X += test-date
 TEST_PROGRAMS_NEED_X += test-delta
 TEST_PROGRAMS_NEED_X += test-dump-cache-tree
 TEST_PROGRAMS_NEED_X += test-dump-split-index
+TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-hashmap
 TEST_PROGRAMS_NEED_X += test-index-version
@@ -1693,10 +1694,10 @@ $(BUILT_INS): git$X
        ln -s $< $@ 2>/dev/null || \
        cp $< $@
 
-common-cmds.h: ./generate-cmdlist.sh command-list.txt
+common-cmds.h: generate-cmdlist.perl command-list.txt
 
 common-cmds.h: $(wildcard Documentation/git-*.txt)
-       $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
+       $(QUIET_GEN)$(PERL_PATH) generate-cmdlist.perl command-list.txt > $@+ && mv $@+ $@
 
 SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
        $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
@@ -2454,7 +2455,7 @@ check-docs::
                esac ; \
                test -f "Documentation/$$v.txt" || \
                echo "no doc: $$v"; \
-               sed -e '/^#/d' command-list.txt | \
+               sed -e '1,/^### command list/d' -e '/^#/d' command-list.txt | \
                grep -q "^$$v[  ]" || \
                case "$$v" in \
                git) ;; \
@@ -2462,7 +2463,8 @@ check-docs::
                esac ; \
        done; \
        ( \
-               sed -e '/^#/d' \
+               sed -e '1,/^### command list/d' \
+                   -e '/^#/d' \
                    -e 's/[     ].*//' \
                    -e 's/^/listed /' command-list.txt; \
                $(MAKE) -C Documentation print-man1 | \
index 8d70623cb8714a87650f2425daf4ee7aaf6307a8..b3e948e757e97947211f598488339cd4f03c43eb 100644 (file)
@@ -26,8 +26,9 @@
 #include "userdiff.h"
 #include "line-range.h"
 #include "line-log.h"
+#include "dir.h"
 
-static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] file");
+static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
 
 static const char *blame_opt_usage[] = {
        blame_usage,
@@ -2151,16 +2152,6 @@ static void sanity_check_refcnt(struct scoreboard *sb)
        }
 }
 
-/*
- * Used for the command line parsing; check if the path exists
- * in the working tree.
- */
-static int has_string_in_work_tree(const char *path)
-{
-       struct stat st;
-       return !lstat(path, &st);
-}
-
 static unsigned parse_score(const char *arg)
 {
        char *end;
@@ -2656,14 +2647,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                if (argc < 2)
                        usage_with_options(blame_opt_usage, options);
                path = add_prefix(prefix, argv[argc - 1]);
-               if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+               if (argc == 3 && !file_exists(path)) { /* (2b) */
                        path = add_prefix(prefix, argv[1]);
                        argv[1] = argv[2];
                }
                argv[argc - 1] = "--";
 
                setup_work_tree();
-               if (!has_string_in_work_tree(path))
+               if (!file_exists(path))
                        die_errno("cannot stat path '%s'", path);
        }
 
index 0d3b9af6e9ebc57944def4ce0f7b0feadf4b66c2..b42e5b6dbc76016ca18e5ddd7ded2af613013eb7 100644 (file)
@@ -123,14 +123,12 @@ static int branch_merged(int kind, const char *name,
 
        if (kind == REF_LOCAL_BRANCH) {
                struct branch *branch = branch_get(name);
+               const char *upstream = branch_get_upstream(branch, NULL);
                unsigned char sha1[20];
 
-               if (branch &&
-                   branch->merge &&
-                   branch->merge[0] &&
-                   branch->merge[0]->dst &&
+               if (upstream &&
                    (reference_name = reference_name_to_free =
-                    resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
+                    resolve_refdup(upstream, RESOLVE_REF_READING,
                                    sha1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
        }
@@ -427,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
        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;
 
-       switch (stat_tracking_info(branch, &ours, &theirs)) {
-       case 0:
-               /* no base */
-               return;
-       case -1:
-               /* with "gone" base */
+       if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
+               if (!upstream)
+                       return;
                upstream_is_gone = 1;
-               break;
-       default:
-               /* with base */
-               break;
        }
 
        if (show_upstream_ref) {
-               ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+               ref = shorten_unambiguous_ref(upstream, 0);
                if (want_color(branch_use_color))
                        strbuf_addf(&fancy, "%s%s%s",
                                        branch_get_color(BRANCH_COLOR_UPSTREAM),
index ecb488822f903c56b5d14bdec3130c71817e1b65..049a95f1f113289a1a01f74a2485d1e9b282209f 100644 (file)
@@ -8,6 +8,7 @@
 #include "parse-options.h"
 #include "userdiff.h"
 #include "streaming.h"
+#include "tree-walk.h"
 
 static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                        int unknown_type)
@@ -233,6 +234,7 @@ static void print_object_or_die(int fd, struct expand_data *data)
 
 struct batch_options {
        int enabled;
+       int follow_symlinks;
        int print_contents;
        const char *format;
 };
@@ -241,12 +243,44 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,
                            struct expand_data *data)
 {
        struct strbuf buf = STRBUF_INIT;
+       struct object_context ctx;
+       int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
+       enum follow_symlinks_result result;
 
        if (!obj_name)
           return 1;
 
-       if (get_sha1(obj_name, data->sha1)) {
-               printf("%s missing\n", obj_name);
+       result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
+       if (result != FOUND) {
+               switch (result) {
+               case MISSING_OBJECT:
+                       printf("%s missing\n", obj_name);
+                       break;
+               case DANGLING_SYMLINK:
+                       printf("dangling %"PRIuMAX"\n%s\n",
+                              (uintmax_t)strlen(obj_name), obj_name);
+                       break;
+               case SYMLINK_LOOP:
+                       printf("loop %"PRIuMAX"\n%s\n",
+                              (uintmax_t)strlen(obj_name), obj_name);
+                       break;
+               case NOT_DIR:
+                       printf("notdir %"PRIuMAX"\n%s\n",
+                              (uintmax_t)strlen(obj_name), obj_name);
+                       break;
+               default:
+                       die("BUG: unknown get_sha1_with_context result %d\n",
+                              result);
+                       break;
+               }
+               fflush(stdout);
+               return 0;
+       }
+
+       if (ctx.mode == 0) {
+               printf("symlink %"PRIuMAX"\n%s\n",
+                      (uintmax_t)ctx.symlink_path.len,
+                      ctx.symlink_path.buf);
                fflush(stdout);
                return 0;
        }
@@ -333,7 +367,7 @@ static int batch_objects(struct batch_options *opt)
 
 static const char * const cat_file_usage[] = {
        N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"),
-       N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
+       N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"),
        NULL
 };
 
@@ -351,9 +385,8 @@ static int batch_option_callback(const struct option *opt,
 {
        struct batch_options *bo = opt->value;
 
-       if (unset) {
-               memset(bo, 0, sizeof(*bo));
-               return 0;
+       if (bo->enabled) {
+               return 1;
        }
 
        bo->enabled = 1;
@@ -387,6 +420,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
                        N_("show info about objects fed from the standard input"),
                        PARSE_OPT_OPTARG, batch_option_callback },
+               OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
+                        N_("follow in-tree symlinks (used with --batch or --batch-check)")),
                OPT_END()
        };
 
@@ -411,6 +446,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                usage_with_options(cat_file_usage, options);
        }
 
+       if (batch.follow_symlinks && !batch.enabled) {
+               usage_with_options(cat_file_usage, options);
+       }
+
        if (batch.enabled)
                return batch_objects(&batch);
 
index 98c103fa8b4faa83c33a62e5d7871c1e9d888c38..6dcb72e64484fa5082bd2b77b370107984373340 100644 (file)
@@ -314,7 +314,6 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
 {
        struct string_list menu_list = STRING_LIST_INIT_DUP;
        struct strbuf menu = STRBUF_INIT;
-       struct strbuf buf = STRBUF_INIT;
        struct menu_item *menu_item;
        struct string_list_item *string_list_item;
        int i;
@@ -363,7 +362,6 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
        pretty_print_menus(&menu_list);
 
        strbuf_release(&menu);
-       strbuf_release(&buf);
        string_list_clear(&menu_list, 0);
 }
 
@@ -941,15 +939,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                if (!cache_name_is_other(ent->name, ent->len))
                        continue;
 
-               if (lstat(ent->name, &st))
-                       die_errno("Cannot lstat '%s'", ent->name);
-
                if (pathspec.nr)
                        matches = dir_path_match(ent, &pathspec, 0, NULL);
 
                if (pathspec.nr && !matches)
                        continue;
 
+               if (lstat(ent->name, &st))
+                       die_errno("Cannot lstat '%s'", ent->name);
+
                if (S_ISDIR(st.st_mode) && !remove_directories &&
                    matches != MATCHED_EXACTLY)
                        continue;
index d6515a2a50e78e9376f1b55fa6fe79b1e867f25c..254477fd1d4e8b96f50eb42dc11ec1253c7467f8 100644 (file)
@@ -1366,13 +1366,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
 
        fd = hold_locked_index(&index_lock, 0);
-       if (0 <= fd)
-               update_index_if_able(&the_index, &index_lock);
 
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
        s.ignore_submodule_arg = ignore_submodule_arg;
        wt_status_collect(&s);
 
+       if (0 <= fd)
+               update_index_if_able(&the_index, &index_lock);
+
        if (s.relative_paths)
                s.prefix = prefix;
 
index 05ce28cebbe3f6e5c64bcd1087caef4449d366b1..f7e51a7fadc40b1e4484a657fc55af8a3043967c 100644 (file)
@@ -74,6 +74,7 @@ static struct {
        { "contents:body" },
        { "contents:signature" },
        { "upstream" },
+       { "push" },
        { "symref" },
        { "flag" },
        { "HEAD" },
@@ -659,15 +660,26 @@ static void populate_value(struct refinfo *ref)
                else if (starts_with(name, "symref"))
                        refname = ref->symref ? ref->symref : "";
                else if (starts_with(name, "upstream")) {
+                       const char *branch_name;
                        /* only local branches may have an upstream */
-                       if (!starts_with(ref->refname, "refs/heads/"))
+                       if (!skip_prefix(ref->refname, "refs/heads/",
+                                        &branch_name))
                                continue;
-                       branch = branch_get(ref->refname + 11);
+                       branch = branch_get(branch_name);
 
-                       if (!branch || !branch->merge || !branch->merge[0] ||
-                           !branch->merge[0]->dst)
+                       refname = branch_get_upstream(branch, NULL);
+                       if (!refname)
+                               continue;
+               } else if (starts_with(name, "push")) {
+                       const char *branch_name;
+                       if (!skip_prefix(ref->refname, "refs/heads/",
+                                        &branch_name))
+                               continue;
+                       branch = branch_get(branch_name);
+
+                       refname = branch_get_push(branch, NULL);
+                       if (!refname)
                                continue;
-                       refname = branch->merge[0]->dst;
                } else if (starts_with(name, "color:")) {
                        char color[COLOR_MAXLEN] = "";
 
@@ -713,11 +725,12 @@ static void populate_value(struct refinfo *ref)
                                refname = shorten_unambiguous_ref(refname,
                                                      warn_ambiguous_refs);
                        else if (!strcmp(formatp, "track") &&
-                                starts_with(name, "upstream")) {
+                                (starts_with(name, "upstream") ||
+                                 starts_with(name, "push"))) {
                                char buf[40];
 
                                if (stat_tracking_info(branch, &num_ours,
-                                                      &num_theirs) != 1)
+                                                      &num_theirs, NULL))
                                        continue;
 
                                if (!num_ours && !num_theirs)
@@ -735,11 +748,12 @@ static void populate_value(struct refinfo *ref)
                                }
                                continue;
                        } else if (!strcmp(formatp, "trackshort") &&
-                                  starts_with(name, "upstream")) {
+                                  (starts_with(name, "upstream") ||
+                                   starts_with(name, "push"))) {
                                assert(branch);
 
                                if (stat_tracking_info(branch, &num_ours,
-                                                       &num_theirs) != 1)
+                                                       &num_theirs, NULL))
                                        continue;
 
                                if (!num_ours && !num_theirs)
index dd8f3fcfc451780b244f6e6fd9e77298f484ad1a..e67671e37ab51d49424bb9a8945901dc9f9776ed 100644 (file)
@@ -38,7 +38,7 @@ static const char *fmt_patch_subject_prefix = "PATCH";
 static const char *fmt_pretty;
 
 static const char * const builtin_log_usage[] = {
-       N_("git log [<options>] [<revision range>] [[--] <path>...]"),
+       N_("git log [<options>] [<revision-range>] [[--] <path>...]"),
        N_("git show [<options>] <object>..."),
        NULL
 };
@@ -1632,16 +1632,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                break;
        default:
                current_branch = branch_get(NULL);
-               if (!current_branch || !current_branch->merge
-                                       || !current_branch->merge[0]
-                                       || !current_branch->merge[0]->dst) {
+               upstream = branch_get_upstream(current_branch, NULL);
+               if (!upstream) {
                        fprintf(stderr, _("Could not find a tracked"
                                        " remote branch, please"
                                        " specify <upstream> manually.\n"));
                        usage_with_options(cherry_usage, options);
                }
-
-               upstream = current_branch->merge[0]->dst;
        }
 
        init_revisions(&revs, prefix);
index f89f60e11a523d45a81c3bd3a92fa9b4c5021673..85c54dcd5a21f2b8413e5f73585f5f0a6c4c5715 100644 (file)
@@ -933,7 +933,7 @@ static int setup_with_upstream(const char ***argv)
 
        if (!branch)
                die(_("No current branch."));
-       if (!branch->remote)
+       if (!branch->remote_name)
                die(_("No remote for the current branch."));
        if (!branch->merge_nr)
                die(_("No default upstream defined for the current branch."));
index 3304bff42a2fb91f66b9bbca1117746eaa22352a..80b972f92fde3201bdf1cb8bd74c2b4ff53fa5ae 100644 (file)
@@ -84,7 +84,6 @@ static int check_submodules_use_gitfiles(void)
                const char *name = list.entry[i].name;
                int pos;
                const struct cache_entry *ce;
-               struct stat st;
 
                pos = cache_name_pos(name, strlen(name));
                if (pos < 0) {
@@ -95,7 +94,7 @@ static int check_submodules_use_gitfiles(void)
                ce = active_cache[pos];
 
                if (!S_ISGITLINK(ce->ce_mode) ||
-                   (lstat(ce->name, &st) < 0) ||
+                   !file_exists(ce->name) ||
                    is_empty_dir(name))
                        continue;
 
index 0665b31ea1e59734a2ace701b613fffebd074dde..7431938fa654ba9af524a7018ab2c7be8242caaa 100644 (file)
@@ -33,6 +33,7 @@ static int mark_valid_only;
 static int mark_skip_worktree_only;
 #define MARK_FLAG 1
 #define UNMARK_FLAG 2
+static struct strbuf mtime_dir = STRBUF_INIT;
 
 __attribute__((format (printf, 1, 2)))
 static void report(const char *fmt, ...)
@@ -48,6 +49,166 @@ static void report(const char *fmt, ...)
        va_end(vp);
 }
 
+static void remove_test_directory(void)
+{
+       if (mtime_dir.len)
+               remove_dir_recursively(&mtime_dir, 0);
+}
+
+static const char *get_mtime_path(const char *path)
+{
+       static struct strbuf sb = STRBUF_INIT;
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path);
+       return sb.buf;
+}
+
+static void xmkdir(const char *path)
+{
+       path = get_mtime_path(path);
+       if (mkdir(path, 0700))
+               die_errno(_("failed to create directory %s"), path);
+}
+
+static int xstat_mtime_dir(struct stat *st)
+{
+       if (stat(mtime_dir.buf, st))
+               die_errno(_("failed to stat %s"), mtime_dir.buf);
+       return 0;
+}
+
+static int create_file(const char *path)
+{
+       int fd;
+       path = get_mtime_path(path);
+       fd = open(path, O_CREAT | O_RDWR, 0644);
+       if (fd < 0)
+               die_errno(_("failed to create file %s"), path);
+       return fd;
+}
+
+static void xunlink(const char *path)
+{
+       path = get_mtime_path(path);
+       if (unlink(path))
+               die_errno(_("failed to delete file %s"), path);
+}
+
+static void xrmdir(const char *path)
+{
+       path = get_mtime_path(path);
+       if (rmdir(path))
+               die_errno(_("failed to delete directory %s"), path);
+}
+
+static void avoid_racy(void)
+{
+       /*
+        * not use if we could usleep(10) if USE_NSEC is defined. The
+        * field nsec could be there, but the OS could choose to
+        * ignore it?
+        */
+       sleep(1);
+}
+
+static int test_if_untracked_cache_is_supported(void)
+{
+       struct stat st;
+       struct stat_data base;
+       int fd, ret = 0;
+
+       strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX");
+       if (!mkdtemp(mtime_dir.buf))
+               die_errno("Could not make temporary directory");
+
+       fprintf(stderr, _("Testing "));
+       atexit(remove_test_directory);
+       xstat_mtime_dir(&st);
+       fill_stat_data(&base, &st);
+       fputc('.', stderr);
+
+       avoid_racy();
+       fd = create_file("newfile");
+       xstat_mtime_dir(&st);
+       if (!match_stat_data(&base, &st)) {
+               close(fd);
+               fputc('\n', stderr);
+               fprintf_ln(stderr,_("directory stat info does not "
+                                   "change after adding a new file"));
+               goto done;
+       }
+       fill_stat_data(&base, &st);
+       fputc('.', stderr);
+
+       avoid_racy();
+       xmkdir("new-dir");
+       xstat_mtime_dir(&st);
+       if (!match_stat_data(&base, &st)) {
+               close(fd);
+               fputc('\n', stderr);
+               fprintf_ln(stderr, _("directory stat info does not change "
+                                    "after adding a new directory"));
+               goto done;
+       }
+       fill_stat_data(&base, &st);
+       fputc('.', stderr);
+
+       avoid_racy();
+       write_or_die(fd, "data", 4);
+       close(fd);
+       xstat_mtime_dir(&st);
+       if (match_stat_data(&base, &st)) {
+               fputc('\n', stderr);
+               fprintf_ln(stderr, _("directory stat info changes "
+                                    "after updating a file"));
+               goto done;
+       }
+       fputc('.', stderr);
+
+       avoid_racy();
+       close(create_file("new-dir/new"));
+       xstat_mtime_dir(&st);
+       if (match_stat_data(&base, &st)) {
+               fputc('\n', stderr);
+               fprintf_ln(stderr, _("directory stat info changes after "
+                                    "adding a file inside subdirectory"));
+               goto done;
+       }
+       fputc('.', stderr);
+
+       avoid_racy();
+       xunlink("newfile");
+       xstat_mtime_dir(&st);
+       if (!match_stat_data(&base, &st)) {
+               fputc('\n', stderr);
+               fprintf_ln(stderr, _("directory stat info does not "
+                                    "change after deleting a file"));
+               goto done;
+       }
+       fill_stat_data(&base, &st);
+       fputc('.', stderr);
+
+       avoid_racy();
+       xunlink("new-dir/new");
+       xrmdir("new-dir");
+       xstat_mtime_dir(&st);
+       if (!match_stat_data(&base, &st)) {
+               fputc('\n', stderr);
+               fprintf_ln(stderr, _("directory stat info does not "
+                                    "change after deleting a directory"));
+               goto done;
+       }
+
+       if (rmdir(mtime_dir.buf))
+               die_errno(_("failed to delete directory %s"), mtime_dir.buf);
+       fprintf_ln(stderr, _(" OK"));
+       ret = 1;
+
+done:
+       strbuf_release(&mtime_dir);
+       return ret;
+}
+
 static int mark_ce_flags(const char *path, int flag, int mark)
 {
        int namelen = strlen(path);
@@ -741,6 +902,7 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx,
 int cmd_update_index(int argc, const char **argv, const char *prefix)
 {
        int newfd, entries, has_errors = 0, line_termination = '\n';
+       int untracked_cache = -1;
        int read_from_stdin = 0;
        int prefix_length = prefix ? strlen(prefix) : 0;
        int preferred_index_format = 0;
@@ -832,6 +994,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                        N_("write index in this format")),
                OPT_BOOL(0, "split-index", &split_index,
                        N_("enable or disable split index")),
+               OPT_BOOL(0, "untracked-cache", &untracked_cache,
+                       N_("enable/disable untracked cache")),
+               OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
+                           N_("enable untracked cache without testing the filesystem"), 2),
                OPT_END()
        };
 
@@ -938,6 +1104,28 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                the_index.split_index = NULL;
                the_index.cache_changed |= SOMETHING_CHANGED;
        }
+       if (untracked_cache > 0) {
+               struct untracked_cache *uc;
+
+               if (untracked_cache < 2) {
+                       setup_work_tree();
+                       if (!test_if_untracked_cache_is_supported())
+                               return 1;
+               }
+               if (!the_index.untracked) {
+                       uc = xcalloc(1, sizeof(*uc));
+                       strbuf_init(&uc->ident, 100);
+                       uc->exclude_per_dir = ".gitignore";
+                       /* should be the same flags used by git-status */
+                       uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+                       the_index.untracked = uc;
+               }
+               add_untracked_ident(the_index.untracked);
+               the_index.cache_changed |= UNTRACKED_CHANGED;
+       } else if (!untracked_cache && the_index.untracked) {
+               the_index.untracked = NULL;
+               the_index.cache_changed |= UNTRACKED_CHANGED;
+       }
 
        if (active_cache_changed) {
                if (newfd < 0) {
diff --git a/cache.h b/cache.h
index 1f4226be1580e368b22d62a6e27aa55d37a4dbd7..571c98f5e2406afad1352fcaddd1efff1af65cc1 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -297,8 +297,11 @@ static inline unsigned int canon_mode(unsigned int mode)
 #define RESOLVE_UNDO_CHANGED   (1 << 4)
 #define CACHE_TREE_CHANGED     (1 << 5)
 #define SPLIT_INDEX_ORDERED    (1 << 6)
+#define UNTRACKED_CHANGED      (1 << 7)
 
 struct split_index;
+struct untracked_cache;
+
 struct index_state {
        struct cache_entry **cache;
        unsigned int version;
@@ -312,6 +315,7 @@ struct index_state {
        struct hashmap name_hash;
        struct hashmap dir_hash;
        unsigned char sha1[20];
+       struct untracked_cache *untracked;
 };
 
 extern struct index_state the_index;
@@ -563,6 +567,8 @@ extern void fill_stat_data(struct stat_data *sd, struct stat *st);
  * INODE_CHANGED, and DATA_CHANGED.
  */
 extern int match_stat_data(const struct stat_data *sd, struct stat *st);
+extern int match_stat_data_racy(const struct index_state *istate,
+                               const struct stat_data *sd, struct stat *st);
 
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
@@ -965,15 +971,21 @@ struct object_context {
        unsigned char tree[20];
        char path[PATH_MAX];
        unsigned mode;
+       /*
+        * symlink_path is only used by get_tree_entry_follow_symlinks,
+        * and only for symlinks that point outside the repository.
+        */
+       struct strbuf symlink_path;
 };
 
-#define GET_SHA1_QUIETLY        01
-#define GET_SHA1_COMMIT         02
-#define GET_SHA1_COMMITTISH     04
-#define GET_SHA1_TREE          010
-#define GET_SHA1_TREEISH       020
-#define GET_SHA1_BLOB         040
-#define GET_SHA1_ONLY_TO_DIE 04000
+#define GET_SHA1_QUIETLY           01
+#define GET_SHA1_COMMIT            02
+#define GET_SHA1_COMMITTISH        04
+#define GET_SHA1_TREE             010
+#define GET_SHA1_TREEISH          020
+#define GET_SHA1_BLOB             040
+#define GET_SHA1_FOLLOW_SYMLINKS 0100
+#define GET_SHA1_ONLY_TO_DIE    04000
 
 extern int get_sha1(const char *str, unsigned char *sha1);
 extern int get_sha1_commit(const char *str, unsigned char *sha1);
index 54d8d21ad2b0dc43659e0dd7ee64c4cd3a3f8ed9..b17c011bfd3dc7693a00ee6faf20a7a0608c7b7e 100644 (file)
@@ -1,29 +1,39 @@
-# List of known git commands.
+# common commands are grouped by themes
+# these groups are output by 'git help' in the order declared here.
+# map each common command in the command list to one of these groups.
+### common groups (do not change this line)
+init         start a working area (see also: git help tutorial)
+worktree     work on the current change (see also: git help everyday)
+info         examine the history and state (see also: git help revisions)
+history      grow, mark and tweak your common history
+remote       collaborate (see also: git help workflows)
+
+### command list (do not change this line)
 # command name                          category [deprecated] [common]
-git-add                                 mainporcelain common
+git-add                                 mainporcelain           worktree
 git-am                                  mainporcelain
 git-annotate                            ancillaryinterrogators
 git-apply                               plumbingmanipulators
 git-archimport                          foreignscminterface
 git-archive                             mainporcelain
-git-bisect                              mainporcelain common
+git-bisect                              mainporcelain           info
 git-blame                               ancillaryinterrogators
-git-branch                              mainporcelain common
+git-branch                              mainporcelain           history
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
 git-check-ignore                        purehelpers
 git-check-mailmap                       purehelpers
-git-checkout                            mainporcelain common
+git-checkout                            mainporcelain           history
 git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
 git-cherry                              ancillaryinterrogators
 git-cherry-pick                         mainporcelain
 git-citool                              mainporcelain
 git-clean                               mainporcelain
-git-clone                               mainporcelain common
+git-clone                               mainporcelain           init
 git-column                              purehelpers
-git-commit                              mainporcelain common
+git-commit                              mainporcelain           history
 git-commit-tree                         plumbingmanipulators
 git-config                              ancillarymanipulators
 git-count-objects                       ancillaryinterrogators
@@ -35,14 +45,14 @@ git-cvsimport                           foreignscminterface
 git-cvsserver                           foreignscminterface
 git-daemon                              synchingrepositories
 git-describe                            mainporcelain
-git-diff                                mainporcelain common
+git-diff                                mainporcelain           history
 git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff-tree                           plumbinginterrogators
 git-difftool                            ancillaryinterrogators
 git-fast-export                         ancillarymanipulators
 git-fast-import                         ancillarymanipulators
-git-fetch                               mainporcelain common
+git-fetch                               mainporcelain           remote
 git-fetch-pack                          synchingrepositories
 git-filter-branch                       ancillarymanipulators
 git-fmt-merge-msg                       purehelpers
@@ -51,7 +61,7 @@ git-format-patch                        mainporcelain
 git-fsck                                ancillaryinterrogators
 git-gc                                  mainporcelain
 git-get-tar-commit-id                   ancillaryinterrogators
-git-grep                                mainporcelain common
+git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators
@@ -60,17 +70,17 @@ git-http-fetch                          synchelpers
 git-http-push                           synchelpers
 git-imap-send                           foreignscminterface
 git-index-pack                          plumbingmanipulators
-git-init                                mainporcelain common
+git-init                                mainporcelain           init
 git-instaweb                            ancillaryinterrogators
 git-interpret-trailers                  purehelpers
 gitk                                    mainporcelain
-git-log                                 mainporcelain common
+git-log                                 mainporcelain           info
 git-ls-files                            plumbinginterrogators
 git-ls-remote                           plumbinginterrogators
 git-ls-tree                             plumbinginterrogators
 git-mailinfo                            purehelpers
 git-mailsplit                           purehelpers
-git-merge                               mainporcelain common
+git-merge                               mainporcelain           history
 git-merge-base                          plumbinginterrogators
 git-merge-file                          plumbingmanipulators
 git-merge-index                         plumbingmanipulators
@@ -79,7 +89,7 @@ git-mergetool                           ancillarymanipulators
 git-merge-tree                          ancillaryinterrogators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
-git-mv                                  mainporcelain common
+git-mv                                  mainporcelain           worktree
 git-name-rev                            plumbinginterrogators
 git-notes                               mainporcelain
 git-p4                                  foreignscminterface
@@ -90,11 +100,11 @@ git-parse-remote                        synchelpers
 git-patch-id                            purehelpers
 git-prune                               ancillarymanipulators
 git-prune-packed                        plumbingmanipulators
-git-pull                                mainporcelain common
-git-push                                mainporcelain common
+git-pull                                mainporcelain           remote
+git-push                                mainporcelain           remote
 git-quiltimport                         foreignscminterface
 git-read-tree                           plumbingmanipulators
-git-rebase                              mainporcelain common
+git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
 git-reflog                              ancillarymanipulators
 git-relink                              ancillarymanipulators
@@ -103,28 +113,28 @@ git-repack                              ancillarymanipulators
 git-replace                             ancillarymanipulators
 git-request-pull                        foreignscminterface
 git-rerere                              ancillaryinterrogators
-git-reset                               mainporcelain common
+git-reset                               mainporcelain           worktree
 git-revert                              mainporcelain
 git-rev-list                            plumbinginterrogators
 git-rev-parse                           ancillaryinterrogators
-git-rm                                  mainporcelain common
+git-rm                                  mainporcelain           worktree
 git-send-email                          foreignscminterface
 git-send-pack                           synchingrepositories
 git-shell                               synchelpers
 git-shortlog                            mainporcelain
-git-show                                mainporcelain common
+git-show                                mainporcelain           info
 git-show-branch                         ancillaryinterrogators
 git-show-index                          plumbinginterrogators
 git-show-ref                            plumbinginterrogators
 git-sh-i18n                             purehelpers
 git-sh-setup                            purehelpers
 git-stash                               mainporcelain
-git-status                              mainporcelain common
+git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
 git-symbolic-ref                        plumbingmanipulators
-git-tag                                 mainporcelain common
+git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
 git-unpack-objects                      plumbingmanipulators
 git-update-index                        plumbingmanipulators
index 70f3191a4f19f10a156d1f2c054943d5147ab049..496e6f8bb0217c40450c61e88fbf1e08fdb3f704 100644 (file)
@@ -2128,3 +2128,14 @@ void mingw_startup()
        /* initialize Unicode console */
        winansi_init();
 }
+
+int uname(struct utsname *buf)
+{
+       DWORD v = GetVersion();
+       memset(buf, 0, sizeof(*buf));
+       strcpy(buf->sysname, "Windows");
+       sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff);
+       /* assuming NT variants only.. */
+       sprintf(buf->version, "%u", (v >> 16) & 0x7fff);
+       return 0;
+}
index 98c5e44294cd1f41d5b177b8d4be5219066c2dcd..738865c6c068ed7d8849aff5a9b533dfb1ef8bab 100644 (file)
@@ -76,6 +76,14 @@ struct itimerval {
 };
 #define ITIMER_REAL 0
 
+struct utsname {
+       char sysname[16];
+       char nodename[1];
+       char release[16];
+       char version[16];
+       char machine[1];
+};
+
 /*
  * sanitize preprocessor namespace polluted by Windows headers defining
  * macros which collide with git local versions
@@ -175,6 +183,7 @@ struct passwd *getpwuid(uid_t uid);
 int setitimer(int type, struct itimerval *in, struct itimerval *out);
 int sigaction(int sig, struct sigaction *in, struct sigaction *out);
 int link(const char *oldpath, const char *newpath);
+int uname(struct utsname *buf);
 
 /*
  * replacements of existing functions
diff --git a/dir.c b/dir.c
index 4183acc082671f135fe64cbcaa66ed3b17bc6364..8209f8b8af1e1e3dbc9c166ca351b602de5e6f7a 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -13,6 +13,8 @@
 #include "wildmatch.h"
 #include "pathspec.h"
 #include "utf8.h"
+#include "varint.h"
+#include "ewah/ewok.h"
 
 struct path_simplify {
        int len;
@@ -32,8 +34,22 @@ enum path_treatment {
        path_untracked
 };
 
+/*
+ * Support data structure for our opendir/readdir/closedir wrappers
+ */
+struct cached_dir {
+       DIR *fdir;
+       struct untracked_cache_dir *untracked;
+       int nr_files;
+       int nr_dirs;
+
+       struct dirent *de;
+       const char *file;
+       struct untracked_cache_dir *ucd;
+};
+
 static enum path_treatment read_directory_recursive(struct dir_struct *dir,
-       const char *path, int len,
+       const char *path, int len, struct untracked_cache_dir *untracked,
        int check_only, const struct path_simplify *simplify);
 static int get_dtype(struct dirent *de, const char *path, int len);
 
@@ -385,7 +401,6 @@ int report_path_error(const char *ps_matched,
        /*
         * Make sure all pathspec matched; otherwise it is an error.
         */
-       struct strbuf sb = STRBUF_INIT;
        int num, errors = 0;
        for (num = 0; num < pathspec->nr; num++) {
                int other, found_dup;
@@ -417,7 +432,6 @@ int report_path_error(const char *ps_matched,
                      pathspec->items[num].original);
                errors++;
        }
-       strbuf_release(&sb);
        return errors;
 }
 
@@ -510,7 +524,8 @@ void add_exclude(const char *string, const char *base,
        x->el = el;
 }
 
-static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size,
+                                               struct sha1_stat *sha1_stat)
 {
        int pos, len;
        unsigned long sz;
@@ -529,6 +544,10 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
                return NULL;
        }
        *size = xsize_t(sz);
+       if (sha1_stat) {
+               memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
+               hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
+       }
        return data;
 }
 
@@ -573,11 +592,93 @@ static void trim_trailing_spaces(char *buf)
                *last_space = '\0';
 }
 
-int add_excludes_from_file_to_list(const char *fname,
-                                  const char *base,
-                                  int baselen,
-                                  struct exclude_list *el,
-                                  int check_index)
+/*
+ * Given a subdirectory name and "dir" of the current directory,
+ * search the subdir in "dir" and return it, or create a new one if it
+ * does not exist in "dir".
+ *
+ * If "name" has the trailing slash, it'll be excluded in the search.
+ */
+static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
+                                                   struct untracked_cache_dir *dir,
+                                                   const char *name, int len)
+{
+       int first, last;
+       struct untracked_cache_dir *d;
+       if (!dir)
+               return NULL;
+       if (len && name[len - 1] == '/')
+               len--;
+       first = 0;
+       last = dir->dirs_nr;
+       while (last > first) {
+               int cmp, next = (last + first) >> 1;
+               d = dir->dirs[next];
+               cmp = strncmp(name, d->name, len);
+               if (!cmp && strlen(d->name) > len)
+                       cmp = -1;
+               if (!cmp)
+                       return d;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+
+       uc->dir_created++;
+       d = xmalloc(sizeof(*d) + len + 1);
+       memset(d, 0, sizeof(*d));
+       memcpy(d->name, name, len);
+       d->name[len] = '\0';
+
+       ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
+       memmove(dir->dirs + first + 1, dir->dirs + first,
+               (dir->dirs_nr - first) * sizeof(*dir->dirs));
+       dir->dirs_nr++;
+       dir->dirs[first] = d;
+       return d;
+}
+
+static void do_invalidate_gitignore(struct untracked_cache_dir *dir)
+{
+       int i;
+       dir->valid = 0;
+       dir->untracked_nr = 0;
+       for (i = 0; i < dir->dirs_nr; i++)
+               do_invalidate_gitignore(dir->dirs[i]);
+}
+
+static void invalidate_gitignore(struct untracked_cache *uc,
+                                struct untracked_cache_dir *dir)
+{
+       uc->gitignore_invalidated++;
+       do_invalidate_gitignore(dir);
+}
+
+static void invalidate_directory(struct untracked_cache *uc,
+                                struct untracked_cache_dir *dir)
+{
+       int i;
+       uc->dir_invalidated++;
+       dir->valid = 0;
+       dir->untracked_nr = 0;
+       for (i = 0; i < dir->dirs_nr; i++)
+               dir->dirs[i]->recurse = 0;
+}
+
+/*
+ * Given a file with name "fname", read it (either from disk, or from
+ * the index if "check_index" is non-zero), parse it and store the
+ * exclude rules in "el".
+ *
+ * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
+ * stat data from disk (only valid if add_excludes returns zero). If
+ * ss_valid is non-zero, "ss" must contain good value as input.
+ */
+static int add_excludes(const char *fname, const char *base, int baselen,
+                       struct exclude_list *el, int check_index,
+                       struct sha1_stat *sha1_stat)
 {
        struct stat st;
        int fd, i, lineno = 1;
@@ -591,7 +692,7 @@ int add_excludes_from_file_to_list(const char *fname,
                if (0 <= fd)
                        close(fd);
                if (!check_index ||
-                   (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+                   (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)
                        return -1;
                if (size == 0) {
                        free(buf);
@@ -604,6 +705,11 @@ int add_excludes_from_file_to_list(const char *fname,
        } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
+                       if (sha1_stat) {
+                               fill_stat_data(&sha1_stat->stat, &st);
+                               hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);
+                               sha1_stat->valid = 1;
+                       }
                        close(fd);
                        return 0;
                }
@@ -615,6 +721,22 @@ int add_excludes_from_file_to_list(const char *fname,
                }
                buf[size++] = '\n';
                close(fd);
+               if (sha1_stat) {
+                       int pos;
+                       if (sha1_stat->valid &&
+                           !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))
+                               ; /* no content change, ss->sha1 still good */
+                       else if (check_index &&
+                                (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&
+                                !ce_stage(active_cache[pos]) &&
+                                ce_uptodate(active_cache[pos]) &&
+                                !would_convert_to_git(fname))
+                               hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
+                       else
+                               hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
+                       fill_stat_data(&sha1_stat->stat, &st);
+                       sha1_stat->valid = 1;
+               }
        }
 
        el->filebuf = buf;
@@ -638,6 +760,13 @@ int add_excludes_from_file_to_list(const char *fname,
        return 0;
 }
 
+int add_excludes_from_file_to_list(const char *fname, const char *base,
+                                  int baselen, struct exclude_list *el,
+                                  int check_index)
+{
+       return add_excludes(fname, base, baselen, el, check_index, NULL);
+}
+
 struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                      int group_type, const char *src)
 {
@@ -655,14 +784,28 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir,
 /*
  * Used to set up core.excludesfile and .git/info/exclude lists.
  */
-void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
+                                    struct sha1_stat *sha1_stat)
 {
        struct exclude_list *el;
+       /*
+        * catch setup_standard_excludes() that's called before
+        * dir->untracked is assigned. That function behaves
+        * differently when dir->untracked is non-NULL.
+        */
+       if (!dir->untracked)
+               dir->unmanaged_exclude_files++;
        el = add_exclude_list(dir, EXC_FILE, fname);
-       if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
+       if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
                die("cannot use %s as an exclude file", fname);
 }
 
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+       dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
+       add_excludes_from_file_1(dir, fname, NULL);
+}
+
 int match_basename(const char *basename, int basenamelen,
                   const char *pattern, int prefix, int patternlen,
                   int flags)
@@ -837,6 +980,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
        struct exclude_list_group *group;
        struct exclude_list *el;
        struct exclude_stack *stk = NULL;
+       struct untracked_cache_dir *untracked;
        int current;
 
        group = &dir->exclude_list_group[EXC_DIRS];
@@ -874,8 +1018,14 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
        /* Read from the parent directories and push them down. */
        current = stk ? stk->baselen : -1;
        strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
+       if (dir->untracked)
+               untracked = stk ? stk->ucd : dir->untracked->root;
+       else
+               untracked = NULL;
+
        while (current < baselen) {
                const char *cp;
+               struct sha1_stat sha1_stat;
 
                stk = xcalloc(1, sizeof(*stk));
                if (current < 0) {
@@ -886,10 +1036,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                        if (!cp)
                                die("oops in prep_exclude");
                        cp++;
+                       untracked =
+                               lookup_untracked(dir->untracked, untracked,
+                                                base + current,
+                                                cp - base - current);
                }
                stk->prev = dir->exclude_stack;
                stk->baselen = cp - base;
                stk->exclude_ix = group->nr;
+               stk->ucd = untracked;
                el = add_exclude_list(dir, EXC_DIRS, NULL);
                strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
                assert(stk->baselen == dir->basebuf.len);
@@ -912,7 +1067,23 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                }
 
                /* Try to read per-directory file */
-               if (dir->exclude_per_dir) {
+               hashclr(sha1_stat.sha1);
+               sha1_stat.valid = 0;
+               if (dir->exclude_per_dir &&
+                   /*
+                    * If we know that no files have been added in
+                    * this directory (i.e. valid_cached_dir() has
+                    * been executed and set untracked->valid) ..
+                    */
+                   (!untracked || !untracked->valid ||
+                    /*
+                     * .. and .gitignore does not exist before
+                     * (i.e. null exclude_sha1 and skip_worktree is
+                     * not set). Then we can skip loading .gitignore,
+                     * which would result in ENOENT anyway.
+                     * skip_worktree is taken care in read_directory()
+                     */
+                    !is_null_sha1(untracked->exclude_sha1))) {
                        /*
                         * dir->basebuf gets reused by the traversal, but we
                         * need fname to remain unchanged to ensure the src
@@ -925,8 +1096,27 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                        strbuf_addbuf(&sb, &dir->basebuf);
                        strbuf_addstr(&sb, dir->exclude_per_dir);
                        el->src = strbuf_detach(&sb, NULL);
-                       add_excludes_from_file_to_list(el->src, el->src,
-                                                      stk->baselen, el, 1);
+                       add_excludes(el->src, el->src, stk->baselen, el, 1,
+                                    untracked ? &sha1_stat : NULL);
+               }
+               /*
+                * NEEDSWORK: when untracked cache is enabled, prep_exclude()
+                * will first be called in valid_cached_dir() then maybe many
+                * times more in last_exclude_matching(). When the cache is
+                * used, last_exclude_matching() will not be called and
+                * reading .gitignore content will be a waste.
+                *
+                * So when it's called by valid_cached_dir() and we can get
+                * .gitignore SHA-1 from the index (i.e. .gitignore is not
+                * modified on work tree), we could delay reading the
+                * .gitignore content until we absolutely need it in
+                * last_exclude_matching(). Be careful about ignore rule
+                * order, though, if you do that.
+                */
+               if (untracked &&
+                   hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {
+                       invalidate_gitignore(dir->untracked, untracked);
+                       hashcpy(untracked->exclude_sha1, sha1_stat.sha1);
                }
                dir->exclude_stack = stk;
                current = stk->baselen;
@@ -1107,6 +1297,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  *  (c) otherwise, we recurse into it.
  */
 static enum path_treatment treat_directory(struct dir_struct *dir,
+       struct untracked_cache_dir *untracked,
        const char *dirname, int len, int exclude,
        const struct path_simplify *simplify)
 {
@@ -1134,7 +1325,9 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
        if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return exclude ? path_excluded : path_untracked;
 
-       return read_directory_recursive(dir, dirname, len, 1, simplify);
+       untracked = lookup_untracked(dir->untracked, untracked, dirname, len);
+       return read_directory_recursive(dir, dirname, len,
+                                       untracked, 1, simplify);
 }
 
 /*
@@ -1250,6 +1443,7 @@ static int get_dtype(struct dirent *de, const char *path, int len)
 }
 
 static enum path_treatment treat_one_path(struct dir_struct *dir,
+                                         struct untracked_cache_dir *untracked,
                                          struct strbuf *path,
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
@@ -1302,7 +1496,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
-               return treat_directory(dir, path->buf, path->len, exclude,
+               return treat_directory(dir, untracked, path->buf, path->len, exclude,
                        simplify);
        case DT_REG:
        case DT_LNK:
@@ -1310,14 +1504,52 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
        }
 }
 
+static enum path_treatment treat_path_fast(struct dir_struct *dir,
+                                          struct untracked_cache_dir *untracked,
+                                          struct cached_dir *cdir,
+                                          struct strbuf *path,
+                                          int baselen,
+                                          const struct path_simplify *simplify)
+{
+       strbuf_setlen(path, baselen);
+       if (!cdir->ucd) {
+               strbuf_addstr(path, cdir->file);
+               return path_untracked;
+       }
+       strbuf_addstr(path, cdir->ucd->name);
+       /* treat_one_path() does this before it calls treat_directory() */
+       if (path->buf[path->len - 1] != '/')
+               strbuf_addch(path, '/');
+       if (cdir->ucd->check_only)
+               /*
+                * check_only is set as a result of treat_directory() getting
+                * to its bottom. Verify again the same set of directories
+                * with check_only set.
+                */
+               return read_directory_recursive(dir, path->buf, path->len,
+                                               cdir->ucd, 1, simplify);
+       /*
+        * We get path_recurse in the first run when
+        * directory_exists_in_index() returns index_nonexistent. We
+        * are sure that new changes in the index does not impact the
+        * outcome. Return now.
+        */
+       return path_recurse;
+}
+
 static enum path_treatment treat_path(struct dir_struct *dir,
-                                     struct dirent *de,
+                                     struct untracked_cache_dir *untracked,
+                                     struct cached_dir *cdir,
                                      struct strbuf *path,
                                      int baselen,
                                      const struct path_simplify *simplify)
 {
        int dtype;
+       struct dirent *de = cdir->de;
 
+       if (!de)
+               return treat_path_fast(dir, untracked, cdir, path,
+                                      baselen, simplify);
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
@@ -1326,7 +1558,121 @@ static enum path_treatment treat_path(struct dir_struct *dir,
                return path_none;
 
        dtype = DTYPE(de);
-       return treat_one_path(dir, path, simplify, dtype, de);
+       return treat_one_path(dir, untracked, path, simplify, dtype, de);
+}
+
+static void add_untracked(struct untracked_cache_dir *dir, const char *name)
+{
+       if (!dir)
+               return;
+       ALLOC_GROW(dir->untracked, dir->untracked_nr + 1,
+                  dir->untracked_alloc);
+       dir->untracked[dir->untracked_nr++] = xstrdup(name);
+}
+
+static int valid_cached_dir(struct dir_struct *dir,
+                           struct untracked_cache_dir *untracked,
+                           struct strbuf *path,
+                           int check_only)
+{
+       struct stat st;
+
+       if (!untracked)
+               return 0;
+
+       if (stat(path->len ? path->buf : ".", &st)) {
+               invalidate_directory(dir->untracked, untracked);
+               memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
+               return 0;
+       }
+       if (!untracked->valid ||
+           match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {
+               if (untracked->valid)
+                       invalidate_directory(dir->untracked, untracked);
+               fill_stat_data(&untracked->stat_data, &st);
+               return 0;
+       }
+
+       if (untracked->check_only != !!check_only) {
+               invalidate_directory(dir->untracked, untracked);
+               return 0;
+       }
+
+       /*
+        * prep_exclude will be called eventually on this directory,
+        * but it's called much later in last_exclude_matching(). We
+        * need it now to determine the validity of the cache for this
+        * path. The next calls will be nearly no-op, the way
+        * prep_exclude() is designed.
+        */
+       if (path->len && path->buf[path->len - 1] != '/') {
+               strbuf_addch(path, '/');
+               prep_exclude(dir, path->buf, path->len);
+               strbuf_setlen(path, path->len - 1);
+       } else
+               prep_exclude(dir, path->buf, path->len);
+
+       /* hopefully prep_exclude() haven't invalidated this entry... */
+       return untracked->valid;
+}
+
+static int open_cached_dir(struct cached_dir *cdir,
+                          struct dir_struct *dir,
+                          struct untracked_cache_dir *untracked,
+                          struct strbuf *path,
+                          int check_only)
+{
+       memset(cdir, 0, sizeof(*cdir));
+       cdir->untracked = untracked;
+       if (valid_cached_dir(dir, untracked, path, check_only))
+               return 0;
+       cdir->fdir = opendir(path->len ? path->buf : ".");
+       if (dir->untracked)
+               dir->untracked->dir_opened++;
+       if (!cdir->fdir)
+               return -1;
+       return 0;
+}
+
+static int read_cached_dir(struct cached_dir *cdir)
+{
+       if (cdir->fdir) {
+               cdir->de = readdir(cdir->fdir);
+               if (!cdir->de)
+                       return -1;
+               return 0;
+       }
+       while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
+               struct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];
+               if (!d->recurse) {
+                       cdir->nr_dirs++;
+                       continue;
+               }
+               cdir->ucd = d;
+               cdir->nr_dirs++;
+               return 0;
+       }
+       cdir->ucd = NULL;
+       if (cdir->nr_files < cdir->untracked->untracked_nr) {
+               struct untracked_cache_dir *d = cdir->untracked;
+               cdir->file = d->untracked[cdir->nr_files++];
+               return 0;
+       }
+       return -1;
+}
+
+static void close_cached_dir(struct cached_dir *cdir)
+{
+       if (cdir->fdir)
+               closedir(cdir->fdir);
+       /*
+        * We have gone through this directory and found no untracked
+        * entries. Mark it valid.
+        */
+       if (cdir->untracked) {
+               cdir->untracked->valid = 1;
+               cdir->untracked->recurse = 1;
+       }
 }
 
 /*
@@ -1342,38 +1688,48 @@ static enum path_treatment treat_path(struct dir_struct *dir,
  */
 static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                                    const char *base, int baselen,
-                                   int check_only,
+                                   struct untracked_cache_dir *untracked, int check_only,
                                    const struct path_simplify *simplify)
 {
-       DIR *fdir;
+       struct cached_dir cdir;
        enum path_treatment state, subdir_state, dir_state = path_none;
-       struct dirent *de;
        struct strbuf path = STRBUF_INIT;
 
        strbuf_add(&path, base, baselen);
 
-       fdir = opendir(path.len ? path.buf : ".");
-       if (!fdir)
+       if (open_cached_dir(&cdir, dir, untracked, &path, check_only))
                goto out;
 
-       while ((de = readdir(fdir)) != NULL) {
+       if (untracked)
+               untracked->check_only = !!check_only;
+
+       while (!read_cached_dir(&cdir)) {
                /* check how the file or directory should be treated */
-               state = treat_path(dir, de, &path, baselen, simplify);
+               state = treat_path(dir, untracked, &cdir, &path, baselen, simplify);
+
                if (state > dir_state)
                        dir_state = state;
 
                /* recurse into subdir if instructed by treat_path */
                if (state == path_recurse) {
-                       subdir_state = read_directory_recursive(dir, path.buf,
-                               path.len, check_only, simplify);
+                       struct untracked_cache_dir *ud;
+                       ud = lookup_untracked(dir->untracked, untracked,
+                                             path.buf + baselen,
+                                             path.len - baselen);
+                       subdir_state =
+                               read_directory_recursive(dir, path.buf, path.len,
+                                                        ud, check_only, simplify);
                        if (subdir_state > dir_state)
                                dir_state = subdir_state;
                }
 
                if (check_only) {
                        /* abort early if maximum state has been reached */
-                       if (dir_state == path_untracked)
+                       if (dir_state == path_untracked) {
+                               if (cdir.fdir)
+                                       add_untracked(untracked, path.buf + baselen);
                                break;
+                       }
                        /* skip the dir_add_* part */
                        continue;
                }
@@ -1391,15 +1747,18 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                        break;
 
                case path_untracked:
-                       if (!(dir->flags & DIR_SHOW_IGNORED))
-                               dir_add_name(dir, path.buf, path.len);
+                       if (dir->flags & DIR_SHOW_IGNORED)
+                               break;
+                       dir_add_name(dir, path.buf, path.len);
+                       if (cdir.fdir)
+                               add_untracked(untracked, path.buf + baselen);
                        break;
 
                default:
                        break;
                }
        }
-       closedir(fdir);
+       close_cached_dir(&cdir);
  out:
        strbuf_release(&path);
 
@@ -1469,7 +1828,7 @@ static int treat_leading_path(struct dir_struct *dir,
                        break;
                if (simplify_away(sb.buf, sb.len, simplify))
                        break;
-               if (treat_one_path(dir, &sb, simplify,
+               if (treat_one_path(dir, NULL, &sb, simplify,
                                   DT_DIR, NULL) == path_none)
                        break; /* do not recurse into it */
                if (len <= baselen) {
@@ -1482,9 +1841,139 @@ static int treat_leading_path(struct dir_struct *dir,
        return rc;
 }
 
+static const char *get_ident_string(void)
+{
+       static struct strbuf sb = STRBUF_INIT;
+       struct utsname uts;
+
+       if (sb.len)
+               return sb.buf;
+       if (uname(&uts))
+               die_errno(_("failed to get kernel name and information"));
+       strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
+                   uts.sysname, uts.release, uts.version);
+       return sb.buf;
+}
+
+static int ident_in_untracked(const struct untracked_cache *uc)
+{
+       const char *end = uc->ident.buf + uc->ident.len;
+       const char *p   = uc->ident.buf;
+
+       for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
+               if (!strcmp(p, get_ident_string()))
+                       return 1;
+       return 0;
+}
+
+void add_untracked_ident(struct untracked_cache *uc)
+{
+       if (ident_in_untracked(uc))
+               return;
+       strbuf_addstr(&uc->ident, get_ident_string());
+       /* this strbuf contains a list of strings, save NUL too */
+       strbuf_addch(&uc->ident, 0);
+}
+
+static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
+                                                     int base_len,
+                                                     const struct pathspec *pathspec)
+{
+       struct untracked_cache_dir *root;
+       int i;
+
+       if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
+               return NULL;
+
+       /*
+        * We only support $GIT_DIR/info/exclude and core.excludesfile
+        * as the global ignore rule files. Any other additions
+        * (e.g. from command line) invalidate the cache. This
+        * condition also catches running setup_standard_excludes()
+        * before setting dir->untracked!
+        */
+       if (dir->unmanaged_exclude_files)
+               return NULL;
+
+       /*
+        * Optimize for the main use case only: whole-tree git
+        * status. More work involved in treat_leading_path() if we
+        * use cache on just a subset of the worktree. pathspec
+        * support could make the matter even worse.
+        */
+       if (base_len || (pathspec && pathspec->nr))
+               return NULL;
+
+       /* Different set of flags may produce different results */
+       if (dir->flags != dir->untracked->dir_flags ||
+           /*
+            * See treat_directory(), case index_nonexistent. Without
+            * this flag, we may need to also cache .git file content
+            * for the resolve_gitlink_ref() call, which we don't.
+            */
+           !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
+           /* We don't support collecting ignore files */
+           (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
+                          DIR_COLLECT_IGNORED)))
+               return NULL;
+
+       /*
+        * If we use .gitignore in the cache and now you change it to
+        * .gitexclude, everything will go wrong.
+        */
+       if (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&
+           strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))
+               return NULL;
+
+       /*
+        * EXC_CMDL is not considered in the cache. If people set it,
+        * skip the cache.
+        */
+       if (dir->exclude_list_group[EXC_CMDL].nr)
+               return NULL;
+
+       /*
+        * An optimization in prep_exclude() does not play well with
+        * CE_SKIP_WORKTREE. It's a rare case anyway, if a single
+        * entry has that bit set, disable the whole untracked cache.
+        */
+       for (i = 0; i < active_nr; i++)
+               if (ce_skip_worktree(active_cache[i]))
+                       return NULL;
+
+       if (!ident_in_untracked(dir->untracked)) {
+               warning(_("Untracked cache is disabled on this system."));
+               return NULL;
+       }
+
+       if (!dir->untracked->root) {
+               const int len = sizeof(*dir->untracked->root);
+               dir->untracked->root = xmalloc(len);
+               memset(dir->untracked->root, 0, len);
+       }
+
+       /* Validate $GIT_DIR/info/exclude and core.excludesfile */
+       root = dir->untracked->root;
+       if (hashcmp(dir->ss_info_exclude.sha1,
+                   dir->untracked->ss_info_exclude.sha1)) {
+               invalidate_gitignore(dir->untracked, root);
+               dir->untracked->ss_info_exclude = dir->ss_info_exclude;
+       }
+       if (hashcmp(dir->ss_excludes_file.sha1,
+                   dir->untracked->ss_excludes_file.sha1)) {
+               invalidate_gitignore(dir->untracked, root);
+               dir->untracked->ss_excludes_file = dir->ss_excludes_file;
+       }
+
+       /* Make sure this directory is not dropped out at saving phase */
+       root->recurse = 1;
+       return root;
+}
+
 int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
 {
        struct path_simplify *simplify;
+       struct untracked_cache_dir *untracked;
 
        /*
         * Check out create_simplify()
@@ -1508,11 +1997,39 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
         * create_simplify().
         */
        simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
+       untracked = validate_untracked_cache(dir, len, pathspec);
+       if (!untracked)
+               /*
+                * make sure untracked cache code path is disabled,
+                * e.g. prep_exclude()
+                */
+               dir->untracked = NULL;
        if (!len || treat_leading_path(dir, path, len, simplify))
-               read_directory_recursive(dir, path, len, 0, simplify);
+               read_directory_recursive(dir, path, len, untracked, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
+       if (dir->untracked) {
+               static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
+               trace_printf_key(&trace_untracked_stats,
+                                "node creation: %u\n"
+                                "gitignore invalidation: %u\n"
+                                "directory invalidation: %u\n"
+                                "opendir: %u\n",
+                                dir->untracked->dir_created,
+                                dir->untracked->gitignore_invalidated,
+                                dir->untracked->dir_invalidated,
+                                dir->untracked->dir_opened);
+               if (dir->untracked == the_index.untracked &&
+                   (dir->untracked->dir_opened ||
+                    dir->untracked->gitignore_invalidated ||
+                    dir->untracked->dir_invalidated))
+                       the_index.cache_changed |= UNTRACKED_CHANGED;
+               if (dir->untracked != the_index.untracked) {
+                       free(dir->untracked);
+                       dir->untracked = NULL;
+               }
+       }
        return dir->nr;
 }
 
@@ -1678,12 +2195,14 @@ void setup_standard_excludes(struct dir_struct *dir)
        if (!excludes_file)
                excludes_file = xdg_config_home("ignore");
        if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
-               add_excludes_from_file(dir, excludes_file);
+               add_excludes_from_file_1(dir, excludes_file,
+                                        dir->untracked ? &dir->ss_excludes_file : NULL);
 
        /* per repository user preference */
        path = git_path("info/exclude");
        if (!access_or_warn(path, R_OK, 0))
-               add_excludes_from_file(dir, path);
+               add_excludes_from_file_1(dir, path,
+                                        dir->untracked ? &dir->ss_info_exclude : NULL);
 }
 
 int remove_path(const char *name)
@@ -1735,3 +2254,404 @@ void clear_directory(struct dir_struct *dir)
        }
        strbuf_release(&dir->basebuf);
 }
+
+struct ondisk_untracked_cache {
+       struct stat_data info_exclude_stat;
+       struct stat_data excludes_file_stat;
+       uint32_t dir_flags;
+       unsigned char info_exclude_sha1[20];
+       unsigned char excludes_file_sha1[20];
+       char exclude_per_dir[FLEX_ARRAY];
+};
+
+#define ouc_size(len) (offsetof(struct ondisk_untracked_cache, exclude_per_dir) + len + 1)
+
+struct write_data {
+       int index;         /* number of written untracked_cache_dir */
+       struct ewah_bitmap *check_only; /* from untracked_cache_dir */
+       struct ewah_bitmap *valid;      /* from untracked_cache_dir */
+       struct ewah_bitmap *sha1_valid; /* set if exclude_sha1 is not null */
+       struct strbuf out;
+       struct strbuf sb_stat;
+       struct strbuf sb_sha1;
+};
+
+static void stat_data_to_disk(struct stat_data *to, const struct stat_data *from)
+{
+       to->sd_ctime.sec  = htonl(from->sd_ctime.sec);
+       to->sd_ctime.nsec = htonl(from->sd_ctime.nsec);
+       to->sd_mtime.sec  = htonl(from->sd_mtime.sec);
+       to->sd_mtime.nsec = htonl(from->sd_mtime.nsec);
+       to->sd_dev        = htonl(from->sd_dev);
+       to->sd_ino        = htonl(from->sd_ino);
+       to->sd_uid        = htonl(from->sd_uid);
+       to->sd_gid        = htonl(from->sd_gid);
+       to->sd_size       = htonl(from->sd_size);
+}
+
+static void write_one_dir(struct untracked_cache_dir *untracked,
+                         struct write_data *wd)
+{
+       struct stat_data stat_data;
+       struct strbuf *out = &wd->out;
+       unsigned char intbuf[16];
+       unsigned int intlen, value;
+       int i = wd->index++;
+
+       /*
+        * untracked_nr should be reset whenever valid is clear, but
+        * for safety..
+        */
+       if (!untracked->valid) {
+               untracked->untracked_nr = 0;
+               untracked->check_only = 0;
+       }
+
+       if (untracked->check_only)
+               ewah_set(wd->check_only, i);
+       if (untracked->valid) {
+               ewah_set(wd->valid, i);
+               stat_data_to_disk(&stat_data, &untracked->stat_data);
+               strbuf_add(&wd->sb_stat, &stat_data, sizeof(stat_data));
+       }
+       if (!is_null_sha1(untracked->exclude_sha1)) {
+               ewah_set(wd->sha1_valid, i);
+               strbuf_add(&wd->sb_sha1, untracked->exclude_sha1, 20);
+       }
+
+       intlen = encode_varint(untracked->untracked_nr, intbuf);
+       strbuf_add(out, intbuf, intlen);
+
+       /* skip non-recurse directories */
+       for (i = 0, value = 0; i < untracked->dirs_nr; i++)
+               if (untracked->dirs[i]->recurse)
+                       value++;
+       intlen = encode_varint(value, intbuf);
+       strbuf_add(out, intbuf, intlen);
+
+       strbuf_add(out, untracked->name, strlen(untracked->name) + 1);
+
+       for (i = 0; i < untracked->untracked_nr; i++)
+               strbuf_add(out, untracked->untracked[i],
+                          strlen(untracked->untracked[i]) + 1);
+
+       for (i = 0; i < untracked->dirs_nr; i++)
+               if (untracked->dirs[i]->recurse)
+                       write_one_dir(untracked->dirs[i], wd);
+}
+
+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)
+{
+       struct ondisk_untracked_cache *ouc;
+       struct write_data wd;
+       unsigned char varbuf[16];
+       int len = 0, varint_len;
+       if (untracked->exclude_per_dir)
+               len = strlen(untracked->exclude_per_dir);
+       ouc = xmalloc(sizeof(*ouc) + len + 1);
+       stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
+       stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
+       hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
+       hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
+       ouc->dir_flags = htonl(untracked->dir_flags);
+       memcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);
+
+       varint_len = encode_varint(untracked->ident.len, varbuf);
+       strbuf_add(out, varbuf, varint_len);
+       strbuf_add(out, untracked->ident.buf, untracked->ident.len);
+
+       strbuf_add(out, ouc, ouc_size(len));
+       free(ouc);
+       ouc = NULL;
+
+       if (!untracked->root) {
+               varint_len = encode_varint(0, varbuf);
+               strbuf_add(out, varbuf, varint_len);
+               return;
+       }
+
+       wd.index      = 0;
+       wd.check_only = ewah_new();
+       wd.valid      = ewah_new();
+       wd.sha1_valid = ewah_new();
+       strbuf_init(&wd.out, 1024);
+       strbuf_init(&wd.sb_stat, 1024);
+       strbuf_init(&wd.sb_sha1, 1024);
+       write_one_dir(untracked->root, &wd);
+
+       varint_len = encode_varint(wd.index, varbuf);
+       strbuf_add(out, varbuf, varint_len);
+       strbuf_addbuf(out, &wd.out);
+       ewah_serialize_strbuf(wd.valid, out);
+       ewah_serialize_strbuf(wd.check_only, out);
+       ewah_serialize_strbuf(wd.sha1_valid, out);
+       strbuf_addbuf(out, &wd.sb_stat);
+       strbuf_addbuf(out, &wd.sb_sha1);
+       strbuf_addch(out, '\0'); /* safe guard for string lists */
+
+       ewah_free(wd.valid);
+       ewah_free(wd.check_only);
+       ewah_free(wd.sha1_valid);
+       strbuf_release(&wd.out);
+       strbuf_release(&wd.sb_stat);
+       strbuf_release(&wd.sb_sha1);
+}
+
+static void free_untracked(struct untracked_cache_dir *ucd)
+{
+       int i;
+       if (!ucd)
+               return;
+       for (i = 0; i < ucd->dirs_nr; i++)
+               free_untracked(ucd->dirs[i]);
+       for (i = 0; i < ucd->untracked_nr; i++)
+               free(ucd->untracked[i]);
+       free(ucd->untracked);
+       free(ucd->dirs);
+       free(ucd);
+}
+
+void free_untracked_cache(struct untracked_cache *uc)
+{
+       if (uc)
+               free_untracked(uc->root);
+       free(uc);
+}
+
+struct read_data {
+       int index;
+       struct untracked_cache_dir **ucd;
+       struct ewah_bitmap *check_only;
+       struct ewah_bitmap *valid;
+       struct ewah_bitmap *sha1_valid;
+       const unsigned char *data;
+       const unsigned char *end;
+};
+
+static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from)
+{
+       to->sd_ctime.sec  = get_be32(&from->sd_ctime.sec);
+       to->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec);
+       to->sd_mtime.sec  = get_be32(&from->sd_mtime.sec);
+       to->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec);
+       to->sd_dev        = get_be32(&from->sd_dev);
+       to->sd_ino        = get_be32(&from->sd_ino);
+       to->sd_uid        = get_be32(&from->sd_uid);
+       to->sd_gid        = get_be32(&from->sd_gid);
+       to->sd_size       = get_be32(&from->sd_size);
+}
+
+static int read_one_dir(struct untracked_cache_dir **untracked_,
+                       struct read_data *rd)
+{
+       struct untracked_cache_dir ud, *untracked;
+       const unsigned char *next, *data = rd->data, *end = rd->end;
+       unsigned int value;
+       int i, len;
+
+       memset(&ud, 0, sizeof(ud));
+
+       next = data;
+       value = decode_varint(&next);
+       if (next > end)
+               return -1;
+       ud.recurse         = 1;
+       ud.untracked_alloc = value;
+       ud.untracked_nr    = value;
+       if (ud.untracked_nr)
+               ud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);
+       data = next;
+
+       next = data;
+       ud.dirs_alloc = ud.dirs_nr = decode_varint(&next);
+       if (next > end)
+               return -1;
+       ud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);
+       data = next;
+
+       len = strlen((const char *)data);
+       next = data + len + 1;
+       if (next > rd->end)
+               return -1;
+       *untracked_ = untracked = xmalloc(sizeof(*untracked) + len);
+       memcpy(untracked, &ud, sizeof(ud));
+       memcpy(untracked->name, data, len + 1);
+       data = next;
+
+       for (i = 0; i < untracked->untracked_nr; i++) {
+               len = strlen((const char *)data);
+               next = data + len + 1;
+               if (next > rd->end)
+                       return -1;
+               untracked->untracked[i] = xstrdup((const char*)data);
+               data = next;
+       }
+
+       rd->ucd[rd->index++] = untracked;
+       rd->data = data;
+
+       for (i = 0; i < untracked->dirs_nr; i++) {
+               len = read_one_dir(untracked->dirs + i, rd);
+               if (len < 0)
+                       return -1;
+       }
+       return 0;
+}
+
+static void set_check_only(size_t pos, void *cb)
+{
+       struct read_data *rd = cb;
+       struct untracked_cache_dir *ud = rd->ucd[pos];
+       ud->check_only = 1;
+}
+
+static void read_stat(size_t pos, void *cb)
+{
+       struct read_data *rd = cb;
+       struct untracked_cache_dir *ud = rd->ucd[pos];
+       if (rd->data + sizeof(struct stat_data) > rd->end) {
+               rd->data = rd->end + 1;
+               return;
+       }
+       stat_data_from_disk(&ud->stat_data, (struct stat_data *)rd->data);
+       rd->data += sizeof(struct stat_data);
+       ud->valid = 1;
+}
+
+static void read_sha1(size_t pos, void *cb)
+{
+       struct read_data *rd = cb;
+       struct untracked_cache_dir *ud = rd->ucd[pos];
+       if (rd->data + 20 > rd->end) {
+               rd->data = rd->end + 1;
+               return;
+       }
+       hashcpy(ud->exclude_sha1, rd->data);
+       rd->data += 20;
+}
+
+static void load_sha1_stat(struct sha1_stat *sha1_stat,
+                          const struct stat_data *stat,
+                          const unsigned char *sha1)
+{
+       stat_data_from_disk(&sha1_stat->stat, stat);
+       hashcpy(sha1_stat->sha1, sha1);
+       sha1_stat->valid = 1;
+}
+
+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)
+{
+       const struct ondisk_untracked_cache *ouc;
+       struct untracked_cache *uc;
+       struct read_data rd;
+       const unsigned char *next = data, *end = (const unsigned char *)data + sz;
+       const char *ident;
+       int ident_len, len;
+
+       if (sz <= 1 || end[-1] != '\0')
+               return NULL;
+       end--;
+
+       ident_len = decode_varint(&next);
+       if (next + ident_len > end)
+               return NULL;
+       ident = (const char *)next;
+       next += ident_len;
+
+       ouc = (const struct ondisk_untracked_cache *)next;
+       if (next + ouc_size(0) > end)
+               return NULL;
+
+       uc = xcalloc(1, sizeof(*uc));
+       strbuf_init(&uc->ident, ident_len);
+       strbuf_add(&uc->ident, ident, ident_len);
+       load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,
+                      ouc->info_exclude_sha1);
+       load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,
+                      ouc->excludes_file_sha1);
+       uc->dir_flags = get_be32(&ouc->dir_flags);
+       uc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);
+       /* NUL after exclude_per_dir is covered by sizeof(*ouc) */
+       next += ouc_size(strlen(ouc->exclude_per_dir));
+       if (next >= end)
+               goto done2;
+
+       len = decode_varint(&next);
+       if (next > end || len == 0)
+               goto done2;
+
+       rd.valid      = ewah_new();
+       rd.check_only = ewah_new();
+       rd.sha1_valid = ewah_new();
+       rd.data       = next;
+       rd.end        = end;
+       rd.index      = 0;
+       rd.ucd        = xmalloc(sizeof(*rd.ucd) * len);
+
+       if (read_one_dir(&uc->root, &rd) || rd.index != len)
+               goto done;
+
+       next = rd.data;
+       len = ewah_read_mmap(rd.valid, next, end - next);
+       if (len < 0)
+               goto done;
+
+       next += len;
+       len = ewah_read_mmap(rd.check_only, next, end - next);
+       if (len < 0)
+               goto done;
+
+       next += len;
+       len = ewah_read_mmap(rd.sha1_valid, next, end - next);
+       if (len < 0)
+               goto done;
+
+       ewah_each_bit(rd.check_only, set_check_only, &rd);
+       rd.data = next + len;
+       ewah_each_bit(rd.valid, read_stat, &rd);
+       ewah_each_bit(rd.sha1_valid, read_sha1, &rd);
+       next = rd.data;
+
+done:
+       free(rd.ucd);
+       ewah_free(rd.valid);
+       ewah_free(rd.check_only);
+       ewah_free(rd.sha1_valid);
+done2:
+       if (next != end) {
+               free_untracked_cache(uc);
+               uc = NULL;
+       }
+       return uc;
+}
+
+void untracked_cache_invalidate_path(struct index_state *istate,
+                                    const char *path)
+{
+       const char *sep;
+       struct untracked_cache_dir *d;
+       if (!istate->untracked || !istate->untracked->root)
+               return;
+       sep = strrchr(path, '/');
+       if (sep)
+               d = lookup_untracked(istate->untracked,
+                                    istate->untracked->root,
+                                    path, sep - path);
+       else
+               d = istate->untracked->root;
+       istate->untracked->dir_invalidated++;
+       d->valid = 0;
+       d->untracked_nr = 0;
+}
+
+void untracked_cache_remove_from_index(struct index_state *istate,
+                                      const char *path)
+{
+       untracked_cache_invalidate_path(istate, path);
+}
+
+void untracked_cache_add_to_index(struct index_state *istate,
+                                 const char *path)
+{
+       untracked_cache_invalidate_path(istate, path);
+}
diff --git a/dir.h b/dir.h
index 72b73c65dcfc6f31004a032d0706626424ac2241..7b5855dd80eda02973a55c827d79826a25937880 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -66,6 +66,7 @@ struct exclude_stack {
        struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
        int baselen;
        int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
+       struct untracked_cache_dir *ucd;
 };
 
 struct exclude_list_group {
@@ -73,6 +74,73 @@ struct exclude_list_group {
        struct exclude_list *el;
 };
 
+struct sha1_stat {
+       struct stat_data stat;
+       unsigned char sha1[20];
+       int valid;
+};
+
+/*
+ *  Untracked cache
+ *
+ *  The following inputs are sufficient to determine what files in a
+ *  directory are excluded:
+ *
+ *   - The list of files and directories of the directory in question
+ *   - The $GIT_DIR/index
+ *   - dir_struct flags
+ *   - The content of $GIT_DIR/info/exclude
+ *   - The content of core.excludesfile
+ *   - The content (or the lack) of .gitignore of all parent directories
+ *     from $GIT_WORK_TREE
+ *   - The check_only flag in read_directory_recursive (for
+ *     DIR_HIDE_EMPTY_DIRECTORIES)
+ *
+ *  The first input can be checked using directory mtime. In many
+ *  filesystems, directory mtime (stat_data field) is updated when its
+ *  files or direct subdirs are added or removed.
+ *
+ *  The second one can be hooked from cache_tree_invalidate_path().
+ *  Whenever a file (or a submodule) is added or removed from a
+ *  directory, we invalidate that directory.
+ *
+ *  The remaining inputs are easy, their SHA-1 could be used to verify
+ *  their contents (exclude_sha1[], info_exclude_sha1[] and
+ *  excludes_file_sha1[])
+ */
+struct untracked_cache_dir {
+       struct untracked_cache_dir **dirs;
+       char **untracked;
+       struct stat_data stat_data;
+       unsigned int untracked_alloc, dirs_nr, dirs_alloc;
+       unsigned int untracked_nr;
+       unsigned int check_only : 1;
+       /* all data except 'dirs' in this struct are good */
+       unsigned int valid : 1;
+       unsigned int recurse : 1;
+       /* null SHA-1 means this directory does not have .gitignore */
+       unsigned char exclude_sha1[20];
+       char name[FLEX_ARRAY];
+};
+
+struct untracked_cache {
+       struct sha1_stat ss_info_exclude;
+       struct sha1_stat ss_excludes_file;
+       const char *exclude_per_dir;
+       struct strbuf ident;
+       /*
+        * dir_struct#flags must match dir_flags or the untracked
+        * cache is ignored.
+        */
+       unsigned dir_flags;
+       struct untracked_cache_dir *root;
+       /* Statistics */
+       int dir_created;
+       int gitignore_invalidated;
+       int dir_invalidated;
+       int dir_opened;
+};
+
 struct dir_struct {
        int nr, alloc;
        int ignored_nr, ignored_alloc;
@@ -120,6 +188,12 @@ struct dir_struct {
        struct exclude_stack *exclude_stack;
        struct exclude *exclude;
        struct strbuf basebuf;
+
+       /* Enable untracked file cache if set */
+       struct untracked_cache *untracked;
+       struct sha1_stat ss_info_exclude;
+       struct sha1_stat ss_excludes_file;
+       unsigned unmanaged_exclude_files;
 };
 
 /*
@@ -226,4 +300,12 @@ static inline int dir_path_match(const struct dir_entry *ent,
                              has_trailing_dir);
 }
 
+void untracked_cache_invalidate_path(struct index_state *, const char *);
+void untracked_cache_remove_from_index(struct index_state *, const char *);
+void untracked_cache_add_to_index(struct index_state *, const char *);
+
+void free_untracked_cache(struct untracked_cache *);
+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);
+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
+void add_untracked_ident(struct untracked_cache *);
 #endif
index 1c2d7afd4cb9b70a324d355c3d69b732b181012b..43481b9c60c8afe30d6916650dd0e765065715c9 100644 (file)
@@ -19,6 +19,7 @@
  */
 #include "git-compat-util.h"
 #include "ewok.h"
+#include "strbuf.h"
 
 int ewah_serialize_native(struct ewah_bitmap *self, int fd)
 {
@@ -110,6 +111,18 @@ int ewah_serialize(struct ewah_bitmap *self, int fd)
        return ewah_serialize_to(self, write_helper, (void *)(intptr_t)fd);
 }
 
+static int write_strbuf(void *user_data, const void *data, size_t len)
+{
+       struct strbuf *sb = user_data;
+       strbuf_add(sb, data, len);
+       return len;
+}
+
+int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *sb)
+{
+       return ewah_serialize_to(self, write_strbuf, sb);
+}
+
 int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len)
 {
        const uint8_t *ptr = map;
index 13c6e20412591ed3bc56b38b17419a540264f6c5..e73252536702aaf9fed17757937fbaf4b4593f91 100644 (file)
@@ -30,6 +30,7 @@
 #      define ewah_calloc xcalloc
 #endif
 
+struct strbuf;
 typedef uint64_t eword_t;
 #define BITS_IN_WORD (sizeof(eword_t) * 8)
 
@@ -98,6 +99,7 @@ int ewah_serialize_to(struct ewah_bitmap *self,
                      void *out);
 int ewah_serialize(struct ewah_bitmap *self, int fd);
 int ewah_serialize_native(struct ewah_bitmap *self, int fd);
+int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *);
 
 int ewah_deserialize(struct ewah_bitmap *self, int fd);
 int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len);
index a1dcb27f72ddb7d0bed7951176f5d7b3e8f180d7..a912935a63c225b49eb67174d4142de41758dbe8 100644 (file)
@@ -43,7 +43,12 @@ static int marked;
 #define MAX_IN_VAIN 256
 
 static struct prio_queue rev_list = { compare_commits_by_commit_date };
-static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want;
+static int non_common_revs, multi_ack, use_sideband;
+/* Allow specifying sha1 if it is a ref tip. */
+#define ALLOW_TIP_SHA1 01
+/* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */
+#define ALLOW_REACHABLE_SHA1   02
+static unsigned int allow_unadvertised_object_request;
 
 static void rev_list_push(struct commit *commit, int mark)
 {
@@ -555,7 +560,8 @@ static void filter_refs(struct fetch_pack_args *args,
        }
 
        /* Append unmatched requests to the list */
-       if (allow_tip_sha1_in_want) {
+       if ((allow_unadvertised_object_request &
+           (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) {
                for (i = 0; i < nr_sought; i++) {
                        unsigned char sha1[20];
 
@@ -834,7 +840,12 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
        if (server_supports("allow-tip-sha1-in-want")) {
                if (args->verbose)
                        fprintf(stderr, "Server supports allow-tip-sha1-in-want\n");
-               allow_tip_sha1_in_want = 1;
+               allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
+       }
+       if (server_supports("allow-reachable-sha1-in-want")) {
+               if (args->verbose)
+                       fprintf(stderr, "Server supports allow-reachable-sha1-in-want\n");
+               allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
        }
        if (!server_supports("thin-pack"))
                args->use_thin_pack = 0;
diff --git a/generate-cmdlist.perl b/generate-cmdlist.perl
new file mode 100755 (executable)
index 0000000..31516e3
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+print <<"EOT";
+/* Automatically generated by $0 */
+
+struct cmdname_help {
+       char name[16];
+       char help[80];
+       unsigned char group;
+};
+
+static char *common_cmd_groups[] = {
+EOT
+
+my $n = 0;
+my %grp;
+while (<>) {
+       last if /^### command list/;
+       next if (1../^### common groups/) || /^#/ || /^\s*$/;
+       chop;
+       my ($k, $v) = split ' ', $_, 2;
+       $grp{$k} = $n++;
+       print "\tN_(\"$v\"),\n";
+}
+
+print "};\n\nstatic struct cmdname_help common_cmds[] = {\n";
+
+while (<>) {
+       next if /^#/ || /^\s*$/;
+       my @tags = split;
+       my $cmd = shift @tags;
+       for my $t (@tags) {
+               if (exists $grp{$t}) {
+                       my $s;
+                       open my $f, '<', "Documentation/$cmd.txt" or die;
+                       while (<$f>) {
+                               ($s) = /^$cmd - (.+)$/;
+                               last if $s;
+                       }
+                       close $f;
+                       $cmd =~ s/^git-//;
+                       print "\t{\"$cmd\", N_(\"$s\"), $grp{$t}},\n";
+                       last;
+               }
+       }
+}
+
+print "};\n";
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
deleted file mode 100755 (executable)
index 9a4c9b9..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/sh
-
-echo "/* Automatically generated by $0 */
-struct cmdname_help {
-    char name[16];
-    char help[80];
-};
-
-static struct cmdname_help common_cmds[] = {"
-
-sed -n -e 's/^git-\([^         ]*\)[   ].* common.*/\1/p' command-list.txt |
-sort |
-while read cmd
-do
-     sed -n '
-     /^NAME/,/git-'"$cmd"'/H
-     ${
-           x
-           s/.*git-'"$cmd"' - \(.*\)/  {"'"$cmd"'", N_("\1")},/
-           p
-     }' "Documentation/git-$cmd.txt"
-done
-echo "};"
index b7a97fbe8300532505b9d63dace2a3f76d8e04e2..17584adbd093a0848debcc23b2a73dcf520a8fb4 100644 (file)
 #elif defined(_MSC_VER)
 #include "compat/msvc.h"
 #else
+#include <sys/utsname.h>
 #include <sys/wait.h>
 #include <sys/resource.h>
 #include <sys/socket.h>
index fe61e89f31c44ca90dbfba468cf8b23d3993ba9f..14b039de6513459a7f39ec434638ed15eaf1d9b2 100644 (file)
@@ -2,6 +2,9 @@
 
 : ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
 
+IFS='
+'
+
 mode_ok () {
        if diff_mode
        then
index d20581c15c5086b02411cab618cde0a9ff14e7ed..9f77e3a8bb0b1197f860912b6d0e56b40b607cb9 100755 (executable)
@@ -451,8 +451,6 @@ fi
 printf "Merging:\n"
 printf "%s\n" "$files"
 
-IFS='
-'
 rc=0
 for i in $files
 do
index 9005171b74e0a865ecab869a00ab24b08b3b39e7..0917d0d056573912df60afd7b556efe04eeebae8 100755 (executable)
@@ -54,8 +54,11 @@ then
 fi
 
 # Setup default fast-forward options via `pull.ff`
-pull_ff=$(git config pull.ff)
+pull_ff=$(bool_or_string_config pull.ff)
 case "$pull_ff" in
+true)
+       no_ff=--ff
+       ;;
 false)
        no_ff=--no-ff
        ;;
@@ -81,8 +84,8 @@ do
                diffstat=--no-stat ;;
        --stat|--summary)
                diffstat=--stat ;;
-       --log|--no-log)
-               log_arg=$1 ;;
+       --log|--log=*|--no-log)
+               log_arg="$1" ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
                no_commit=--no-commit ;;
        --c|--co|--com|--comm|--commi|--commit)
index bab0dccc04d85215223c30597659f23cf8edff32..dc3133f681227e66dd54f7bed4be68277e4240b3 100644 (file)
@@ -502,7 +502,7 @@ do_pick () {
 }
 
 do_next () {
-       rm -f "$msg" "$author_script" "$amend" || exit
+       rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
        read -r command sha1 rest < "$todo"
        case "$command" in
        "$comment_char"*|''|noop)
@@ -592,9 +592,6 @@ do_next () {
                read -r command rest < "$todo"
                mark_action_done
                printf 'Executing: %s\n' "$rest"
-               # "exec" command doesn't take a sha1 in the todo-list.
-               # => can't just use $sha1 here.
-               git rev-parse --verify HEAD > "$state_dir"/stopped-sha
                ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
                status=$?
                # Run in subshell because require_clean_work_tree can die.
@@ -890,7 +887,10 @@ first and then run 'git rebase --continue' again."
                fi
        fi
 
-       record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
+       if test -r "$state_dir"/stopped-sha
+       then
+               record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
+       fi
 
        require_clean_work_tree "rebase"
        do_rest
index 7911f30c631fe58d6e5655f5213cae4e72ef99e1..1f5ea877d719715760d42a3ee1d0950a1adaf9fc 100755 (executable)
@@ -219,6 +219,9 @@ save_stash () {
                -a|--all)
                        untracked=all
                        ;;
+               --help)
+                       show_help
+                       ;;
                --)
                        shift
                        break
@@ -301,11 +304,17 @@ list_stash () {
 }
 
 show_stash () {
+       ALLOW_UNKNOWN_FLAGS=t
        assert_stash_like "$@"
 
        git diff ${FLAGS:---stat} $b_commit $w_commit
 }
 
+show_help () {
+       exec git help stash
+       exit 1
+}
+
 #
 # Parses the remaining options looking for flags and
 # at most one revision defaulting to ${ref_stash}@{0}
@@ -332,13 +341,14 @@ show_stash () {
 #
 #   GIT_QUIET is set to t if -q is specified
 #   INDEX_OPTION is set to --index if --index is specified.
-#   FLAGS is set to the remaining flags
+#   FLAGS is set to the remaining flags (if allowed)
 #
 # dies if:
 #   * too many revisions specified
 #   * no revision is specified and there is no stash stack
 #   * a revision is specified which cannot be resolve to a SHA1
 #   * a non-existent stash reference is specified
+#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
 #
 
 parse_flags_and_rev()
@@ -371,7 +381,12 @@ parse_flags_and_rev()
                        --index)
                                INDEX_OPTION=--index
                        ;;
+                       --help)
+                               show_help
+                       ;;
                        -*)
+                               test "$ALLOW_UNKNOWN_FLAGS" = t ||
+                                       die "$(eval_gettext "unknown option: \$opt")"
                                FLAGS="${FLAGS}${FLAGS:+ }$opt"
                        ;;
                esac
diff --git a/help.c b/help.c
index 6f3415b0fcf43b1270783fd1bc7d40d27f8df8a2..80ca8ee68d5af25b43314d351bc7ad09d9d9d03b 100644 (file)
--- a/help.c
+++ b/help.c
@@ -218,17 +218,39 @@ void list_commands(unsigned int colopts,
        }
 }
 
+static int cmd_group_cmp(const void *elem1, const void *elem2)
+{
+       const struct cmdname_help *e1 = elem1;
+       const struct cmdname_help *e2 = elem2;
+
+       if (e1->group < e2->group)
+               return -1;
+       if (e1->group > e2->group)
+               return 1;
+       return strcmp(e1->name, e2->name);
+}
+
 void list_common_cmds_help(void)
 {
        int i, longest = 0;
+       int current_grp = -1;
 
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
                if (longest < strlen(common_cmds[i].name))
                        longest = strlen(common_cmds[i].name);
        }
 
-       puts(_("The most commonly used git commands are:"));
+       qsort(common_cmds, ARRAY_SIZE(common_cmds),
+               sizeof(common_cmds[0]), cmd_group_cmp);
+
+       puts(_("These are common Git commands used in various situations:"));
+
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+               if (common_cmds[i].group != current_grp) {
+                       printf("\n%s\n", _(common_cmd_groups[common_cmds[i].group]));
+                       current_grp = common_cmds[i].group;
+               }
+
                printf("   %s   ", common_cmds[i].name);
                mput_char(' ', longest - strlen(common_cmds[i].name));
                puts(_(common_cmds[i].help));
index 55353ad66a27532844c6cc4d63aaa8b0d4c89c7d..501bf797c0658e33aa8215562c44f0dec9eea75e 100644 (file)
@@ -13,18 +13,20 @@ static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
 static const char last_modified[] = "Last-Modified";
 static int getanyfile = 1;
+static unsigned long max_request_buffer = 10 * 1024 * 1024;
 
 static struct string_list *query_params;
 
 struct rpc_service {
        const char *name;
        const char *config_name;
+       unsigned buffer_input : 1;
        signed enabled : 2;
 };
 
 static struct rpc_service rpc_service[] = {
-       { "upload-pack", "uploadpack", 1 },
-       { "receive-pack", "receivepack", -1 },
+       { "upload-pack", "uploadpack", 1, 1 },
+       { "receive-pack", "receivepack", 0, -1 },
 };
 
 static struct string_list *get_parameters(void)
@@ -225,6 +227,7 @@ static void http_config(void)
        struct strbuf var = STRBUF_INIT;
 
        git_config_get_bool("http.getanyfile", &getanyfile);
+       git_config_get_ulong("http.maxrequestbuffer", &max_request_buffer);
 
        for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
                struct rpc_service *svc = &rpc_service[i];
@@ -266,9 +269,52 @@ static struct rpc_service *select_service(const char *name)
        return svc;
 }
 
-static void inflate_request(const char *prog_name, int out)
+/*
+ * This is basically strbuf_read(), except that if we
+ * hit max_request_buffer we die (we'd rather reject a
+ * maliciously large request than chew up infinite memory).
+ */
+static ssize_t read_request(int fd, unsigned char **out)
+{
+       size_t len = 0, alloc = 8192;
+       unsigned char *buf = xmalloc(alloc);
+
+       if (max_request_buffer < alloc)
+               max_request_buffer = alloc;
+
+       while (1) {
+               ssize_t cnt;
+
+               cnt = read_in_full(fd, buf + len, alloc - len);
+               if (cnt < 0) {
+                       free(buf);
+                       return -1;
+               }
+
+               /* partial read from read_in_full means we hit EOF */
+               len += cnt;
+               if (len < alloc) {
+                       *out = buf;
+                       return len;
+               }
+
+               /* otherwise, grow and try again (if we can) */
+               if (alloc == max_request_buffer)
+                       die("request was larger than our maximum size (%lu);"
+                           " try setting GIT_HTTP_MAX_REQUEST_BUFFER",
+                           max_request_buffer);
+
+               alloc = alloc_nr(alloc);
+               if (alloc > max_request_buffer)
+                       alloc = max_request_buffer;
+               REALLOC_ARRAY(buf, alloc);
+       }
+}
+
+static void inflate_request(const char *prog_name, int out, int buffer_input)
 {
        git_zstream stream;
+       unsigned char *full_request = NULL;
        unsigned char in_buf[8192];
        unsigned char out_buf[8192];
        unsigned long cnt = 0;
@@ -277,11 +323,21 @@ static void inflate_request(const char *prog_name, int out)
        git_inflate_init_gzip_only(&stream);
 
        while (1) {
-               ssize_t n = xread(0, in_buf, sizeof(in_buf));
+               ssize_t n;
+
+               if (buffer_input) {
+                       if (full_request)
+                               n = 0; /* nothing left to read */
+                       else
+                               n = read_request(0, &full_request);
+                       stream.next_in = full_request;
+               } else {
+                       n = xread(0, in_buf, sizeof(in_buf));
+                       stream.next_in = in_buf;
+               }
+
                if (n <= 0)
                        die("request ended in the middle of the gzip stream");
-
-               stream.next_in = in_buf;
                stream.avail_in = n;
 
                while (0 < stream.avail_in) {
@@ -307,9 +363,22 @@ static void inflate_request(const char *prog_name, int out)
 done:
        git_inflate_end(&stream);
        close(out);
+       free(full_request);
+}
+
+static void copy_request(const char *prog_name, int out)
+{
+       unsigned char *buf;
+       ssize_t n = read_request(0, &buf);
+       if (n < 0)
+               die_errno("error reading request body");
+       if (write_in_full(out, buf, n) != n)
+               die("%s aborted reading request", prog_name);
+       close(out);
+       free(buf);
 }
 
-static void run_service(const char **argv)
+static void run_service(const char **argv, int buffer_input)
 {
        const char *encoding = getenv("HTTP_CONTENT_ENCODING");
        const char *user = getenv("REMOTE_USER");
@@ -334,7 +403,7 @@ static void run_service(const char **argv)
                                 "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
 
        cld.argv = argv;
-       if (gzipped_request)
+       if (buffer_input || gzipped_request)
                cld.in = -1;
        cld.git_cmd = 1;
        if (start_command(&cld))
@@ -342,7 +411,9 @@ static void run_service(const char **argv)
 
        close(1);
        if (gzipped_request)
-               inflate_request(argv[0], cld.in);
+               inflate_request(argv[0], cld.in, buffer_input);
+       else if (buffer_input)
+               copy_request(argv[0], cld.in);
        else
                close(0);
 
@@ -392,7 +463,7 @@ static void get_info_refs(char *arg)
                packet_flush(1);
 
                argv[0] = svc->name;
-               run_service(argv);
+               run_service(argv, 0);
 
        } else {
                select_getanyfile();
@@ -496,25 +567,28 @@ static void service_rpc(char *service_name)
        end_headers();
 
        argv[0] = svc->name;
-       run_service(argv);
+       run_service(argv, svc->buffer_input);
        strbuf_release(&buf);
 }
 
+static int dead;
 static NORETURN void die_webcgi(const char *err, va_list params)
 {
-       static int dead;
+       if (dead <= 1) {
+               vreportf("fatal: ", err, params);
 
-       if (!dead) {
-               dead = 1;
                http_status(500, "Internal Server Error");
                hdr_nocache();
                end_headers();
-
-               vreportf("fatal: ", err, params);
        }
        exit(0); /* we successfully reported a failure ;-) */
 }
 
+static int die_webcgi_recursing(void)
+{
+       return dead++ > 1;
+}
+
 static char* getdir(void)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -569,6 +643,7 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
        set_die_routine(die_webcgi);
+       set_die_is_recursing_routine(die_webcgi_recursing);
 
        if (!method)
                die("No REQUEST_METHOD from server");
@@ -619,6 +694,9 @@ int main(int argc, char **argv)
                not_found("Repository not exported: '%s'", dir);
 
        http_config();
+       max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER",
+                                          max_request_buffer);
+
        cmd->imp(cmd_arg);
        return 0;
 }
index 1c9c30db6cbf229a2f836113c04bb980340ce7eb..44d85bea4b678b761d737f0983ed8e7757478cc8 100644 (file)
@@ -611,7 +611,6 @@ static char *unique_path(struct merge_options *o, const char *path, const char *
 {
        struct strbuf newpath = STRBUF_INIT;
        int suffix = 0;
-       struct stat st;
        size_t base_len;
 
        strbuf_addf(&newpath, "%s~", path);
@@ -620,7 +619,7 @@ static char *unique_path(struct merge_options *o, const char *path, const char *
        base_len = newpath.len;
        while (string_list_has_string(&o->current_file_set, newpath.buf) ||
               string_list_has_string(&o->current_directory_set, newpath.buf) ||
-              lstat(newpath.buf, &st) == 0) {
+              file_exists(newpath.buf)) {
                strbuf_setlen(&newpath, base_len);
                strbuf_addf(&newpath, "_%d", suffix++);
        }
diff --git a/mergetools/winmerge b/mergetools/winmerge
new file mode 100644 (file)
index 0000000..74a66d4
--- /dev/null
@@ -0,0 +1,36 @@
+diff_cmd () {
+       "$merge_tool_path" -u -e "$LOCAL" "$REMOTE"
+       return 0
+}
+
+merge_cmd () {
+       # mergetool.winmerge.trustExitCode is implicitly false.
+       # touch $BACKUP so that we can check_unchanged.
+       touch "$BACKUP"
+       "$merge_tool_path" -u -e -dl Local -dr Remote \
+               "$LOCAL" "$REMOTE" "$MERGED"
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       # Use WinMergeU.exe if it exists in $PATH
+       if type -p WinMergeU.exe >/dev/null 2>&1
+       then
+               printf WinMergeU.exe
+               return
+       fi
+
+       # Look for WinMergeU.exe in the typical locations
+       winmerge_exe="WinMerge/WinMergeU.exe"
+       for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' |
+               cut -d '=' -f 2- | sort -u)
+       do
+               if test -n "$directory" && test -x "$directory/$winmerge_exe"
+               then
+                       printf '%s' "$directory/$winmerge_exe"
+                       return
+               fi
+       done
+
+       printf WinMergeU.exe
+}
index 62a98cc119e3bf5a7b71e6b47143e06653ee9d6e..2b3ff2379747f7931167d3099c624113bf032536 100644 (file)
@@ -209,14 +209,12 @@ static inline uint8_t read_u8(const unsigned char *buffer, size_t *pos)
        return buffer[(*pos)++];
 }
 
+#define MAX_XOR_OFFSET 160
+
 static int load_bitmap_entries_v1(struct bitmap_index *index)
 {
-       static const size_t MAX_XOR_OFFSET = 160;
-
        uint32_t i;
-       struct stored_bitmap **recent_bitmaps;
-
-       recent_bitmaps = xcalloc(MAX_XOR_OFFSET, sizeof(struct stored_bitmap));
+       struct stored_bitmap *recent_bitmaps[MAX_XOR_OFFSET] = { NULL };
 
        for (i = 0; i < index->entry_count; ++i) {
                int xor_offset, flags;
@@ -987,7 +985,7 @@ void test_bitmap_walk(struct rev_info *revs)
        else
                fprintf(stderr, "Mismatch!\n");
 
-       free(result);
+       bitmap_free(result);
 }
 
 static int rebuild_bitmap(uint32_t *reposition,
index 36ff89f29e5f56a5b3dcfd803f12cd139295b8b8..723d48dddfe58f50b30105689dfeb9bcb388c8f2 100644 (file)
@@ -39,11 +39,12 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
 #define CACHE_EXT_TREE 0x54524545      /* "TREE" */
 #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
 #define CACHE_EXT_LINK 0x6c696e6b        /* "link" */
+#define CACHE_EXT_UNTRACKED 0x554E5452   /* "UNTR" */
 
 /* changes that can be kept in $GIT_DIR/index (basically all extensions) */
 #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
                 CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
-                SPLIT_INDEX_ORDERED)
+                SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)
 
 struct index_state the_index;
 static const char *alternate_index_output;
@@ -79,6 +80,7 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
        memcpy(new->name, new_name, namelen + 1);
 
        cache_tree_invalidate_path(istate, old->name);
+       untracked_cache_remove_from_index(istate, old->name);
        remove_index_entry_at(istate, nr);
        add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 }
@@ -270,20 +272,34 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
        return changed;
 }
 
-static int is_racy_timestamp(const struct index_state *istate,
-                            const struct cache_entry *ce)
+static int is_racy_stat(const struct index_state *istate,
+                       const struct stat_data *sd)
 {
-       return (!S_ISGITLINK(ce->ce_mode) &&
-               istate->timestamp.sec &&
+       return (istate->timestamp.sec &&
 #ifdef USE_NSEC
                 /* nanosecond timestamped files can also be racy! */
-               (istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
-                (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
-                 istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
+               (istate->timestamp.sec < sd->sd_mtime.sec ||
+                (istate->timestamp.sec == sd->sd_mtime.sec &&
+                 istate->timestamp.nsec <= sd->sd_mtime.nsec))
 #else
-               istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
+               istate->timestamp.sec <= sd->sd_mtime.sec
 #endif
-                );
+               );
+}
+
+static int is_racy_timestamp(const struct index_state *istate,
+                            const struct cache_entry *ce)
+{
+       return (!S_ISGITLINK(ce->ce_mode) &&
+               is_racy_stat(istate, &ce->ce_stat_data));
+}
+
+int match_stat_data_racy(const struct index_state *istate,
+                        const struct stat_data *sd, struct stat *st)
+{
+       if (is_racy_stat(istate, sd))
+               return MTIME_CHANGED;
+       return match_stat_data(sd, st);
 }
 
 int ie_match_stat(const struct index_state *istate,
@@ -538,6 +554,7 @@ int remove_file_from_index(struct index_state *istate, const char *path)
        if (pos < 0)
                pos = -pos-1;
        cache_tree_invalidate_path(istate, path);
+       untracked_cache_remove_from_index(istate, path);
        while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
                remove_index_entry_at(istate, pos);
        return 0;
@@ -982,6 +999,8 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
        }
        pos = -pos-1;
 
+       untracked_cache_add_to_index(istate, ce->name);
+
        /*
         * Inserting a merged entry ("stage 0") into the index
         * will always replace all non-merged entries..
@@ -1372,6 +1391,9 @@ static int read_index_extension(struct index_state *istate,
                if (read_link_extension(istate, data, sz))
                        return -1;
                break;
+       case CACHE_EXT_UNTRACKED:
+               istate->untracked = read_untracked_extension(data, sz);
+               break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
                        return error("index uses %.4s extension, which we do not understand",
@@ -1667,6 +1689,8 @@ int discard_index(struct index_state *istate)
        istate->cache = NULL;
        istate->cache_alloc = 0;
        discard_split_index(istate);
+       free_untracked_cache(istate->untracked);
+       istate->untracked = NULL;
        return 0;
 }
 
@@ -2053,6 +2077,17 @@ static int do_write_index(struct index_state *istate, int newfd,
                if (err)
                        return -1;
        }
+       if (!strip_extensions && istate->untracked) {
+               struct strbuf sb = STRBUF_INIT;
+
+               write_untracked_extension(&sb, istate->untracked);
+               err = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,
+                                            sb.len) < 0 ||
+                       ce_write(&c, newfd, sb.buf, sb.len) < 0;
+               strbuf_release(&sb);
+               if (err)
+                       return -1;
+       }
 
        if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
                return -1;
index 1623eae2ff4537e3566c067ce745945744648a93..26504b744786c65ea4d6e1e0abbf5c6409af5358 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -49,10 +49,7 @@ static int branches_alloc;
 static int branches_nr;
 
 static struct branch *current_branch;
-static const char *default_remote_name;
-static const char *branch_pushremote_name;
 static const char *pushremote_name;
-static int explicit_default_remote_name;
 
 static struct rewrites rewrites;
 static struct rewrites rewrites_push;
@@ -367,16 +364,9 @@ static int handle_config(const char *key, const char *value, void *cb)
                        return 0;
                branch = make_branch(name, subkey - name);
                if (!strcmp(subkey, ".remote")) {
-                       if (git_config_string(&branch->remote_name, key, value))
-                               return -1;
-                       if (branch == current_branch) {
-                               default_remote_name = branch->remote_name;
-                               explicit_default_remote_name = 1;
-                       }
+                       return git_config_string(&branch->remote_name, key, value);
                } else if (!strcmp(subkey, ".pushremote")) {
-                       if (branch == current_branch)
-                               if (git_config_string(&branch_pushremote_name, key, value))
-                                       return -1;
+                       return git_config_string(&branch->pushremote_name, key, value);
                } else if (!strcmp(subkey, ".merge")) {
                        if (!value)
                                return config_error_nonbool(key);
@@ -501,12 +491,15 @@ static void alias_all_urls(void)
 
 static void read_config(void)
 {
+       static int loaded;
        unsigned char sha1[20];
        const char *head_ref;
        int flag;
-       if (default_remote_name) /* did this already */
+
+       if (loaded)
                return;
-       default_remote_name = "origin";
+       loaded = 1;
+
        current_branch = NULL;
        head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
@@ -514,10 +507,6 @@ static void read_config(void)
                current_branch = make_branch(head_ref, 0);
        }
        git_config(handle_config, NULL);
-       if (branch_pushremote_name) {
-               free((char *)pushremote_name);
-               pushremote_name = branch_pushremote_name;
-       }
        alias_all_urls();
 }
 
@@ -696,22 +685,45 @@ static int valid_remote_nick(const char *name)
        return !strchr(name, '/'); /* no slash */
 }
 
-static struct remote *remote_get_1(const char *name, const char *pushremote_name)
+const char *remote_for_branch(struct branch *branch, int *explicit)
+{
+       if (branch && branch->remote_name) {
+               if (explicit)
+                       *explicit = 1;
+               return branch->remote_name;
+       }
+       if (explicit)
+               *explicit = 0;
+       return "origin";
+}
+
+const char *pushremote_for_branch(struct branch *branch, int *explicit)
+{
+       if (branch && branch->pushremote_name) {
+               if (explicit)
+                       *explicit = 1;
+               return branch->pushremote_name;
+       }
+       if (pushremote_name) {
+               if (explicit)
+                       *explicit = 1;
+               return pushremote_name;
+       }
+       return remote_for_branch(branch, explicit);
+}
+
+static struct remote *remote_get_1(const char *name,
+                                  const char *(*get_default)(struct branch *, int *))
 {
        struct remote *ret;
        int name_given = 0;
 
+       read_config();
+
        if (name)
                name_given = 1;
-       else {
-               if (pushremote_name) {
-                       name = pushremote_name;
-                       name_given = 1;
-               } else {
-                       name = default_remote_name;
-                       name_given = explicit_default_remote_name;
-               }
-       }
+       else
+               name = get_default(current_branch, &name_given);
 
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
@@ -731,14 +743,12 @@ static struct remote *remote_get_1(const char *name, const char *pushremote_name
 
 struct remote *remote_get(const char *name)
 {
-       read_config();
-       return remote_get_1(name, NULL);
+       return remote_get_1(name, remote_for_branch);
 }
 
 struct remote *pushremote_get(const char *name)
 {
-       read_config();
-       return remote_get_1(name, pushremote_name);
+       return remote_get_1(name, pushremote_for_branch);
 }
 
 int remote_is_configured(const char *name)
@@ -1633,15 +1643,31 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
 
 static void set_merge(struct branch *ret)
 {
+       struct remote *remote;
        char *ref;
        unsigned char sha1[20];
        int i;
 
+       if (!ret)
+               return; /* no branch */
+       if (ret->merge)
+               return; /* already run */
+       if (!ret->remote_name || !ret->merge_nr) {
+               /*
+                * no merge config; let's make sure we don't confuse callers
+                * with a non-zero merge_nr but a NULL merge
+                */
+               ret->merge_nr = 0;
+               return;
+       }
+
+       remote = remote_get(ret->remote_name);
+
        ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
        for (i = 0; i < ret->merge_nr; i++) {
                ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
                ret->merge[i]->src = xstrdup(ret->merge_name[i]);
-               if (!remote_find_tracking(ret->remote, ret->merge[i]) ||
+               if (!remote_find_tracking(remote, ret->merge[i]) ||
                    strcmp(ret->remote_name, "."))
                        continue;
                if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
@@ -1661,11 +1687,7 @@ struct branch *branch_get(const char *name)
                ret = current_branch;
        else
                ret = make_branch(name, 0);
-       if (ret && ret->remote_name) {
-               ret->remote = remote_get(ret->remote_name);
-               if (ret->merge_nr)
-                       set_merge(ret);
-       }
+       set_merge(ret);
        return ret;
 }
 
@@ -1683,6 +1705,130 @@ int branch_merge_matches(struct branch *branch,
        return refname_match(branch->merge[i]->src, refname);
 }
 
+__attribute((format (printf,2,3)))
+static const char *error_buf(struct strbuf *err, const char *fmt, ...)
+{
+       if (err) {
+               va_list ap;
+               va_start(ap, fmt);
+               strbuf_vaddf(err, fmt, ap);
+               va_end(ap);
+       }
+       return NULL;
+}
+
+const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
+{
+       if (!branch)
+               return error_buf(err, _("HEAD does not point to a branch"));
+
+       if (!branch->merge || !branch->merge[0]) {
+               /*
+                * no merge config; is it because the user didn't define any,
+                * or because it is not a real branch, and get_branch
+                * auto-vivified it?
+                */
+               if (!ref_exists(branch->refname))
+                       return error_buf(err, _("no such branch: '%s'"),
+                                        branch->name);
+               return error_buf(err,
+                                _("no upstream configured for branch '%s'"),
+                                branch->name);
+       }
+
+       if (!branch->merge[0]->dst)
+               return error_buf(err,
+                                _("upstream branch '%s' not stored as a remote-tracking branch"),
+                                branch->merge[0]->src);
+
+       return branch->merge[0]->dst;
+}
+
+static const char *tracking_for_push_dest(struct remote *remote,
+                                         const char *refname,
+                                         struct strbuf *err)
+{
+       char *ret;
+
+       ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+       if (!ret)
+               return error_buf(err,
+                                _("push destination '%s' on remote '%s' has no local tracking branch"),
+                                refname, remote->name);
+       return ret;
+}
+
+static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
+{
+       struct remote *remote;
+
+       if (!branch)
+               return error_buf(err, _("HEAD does not point to a branch"));
+
+       remote = remote_get(pushremote_for_branch(branch, NULL));
+       if (!remote)
+               return error_buf(err,
+                                _("branch '%s' has no remote for pushing"),
+                                branch->name);
+
+       if (remote->push_refspec_nr) {
+               char *dst;
+               const char *ret;
+
+               dst = apply_refspecs(remote->push, remote->push_refspec_nr,
+                                    branch->refname);
+               if (!dst)
+                       return error_buf(err,
+                                        _("push refspecs for '%s' do not include '%s'"),
+                                        remote->name, branch->name);
+
+               ret = tracking_for_push_dest(remote, dst, err);
+               free(dst);
+               return ret;
+       }
+
+       if (remote->mirror)
+               return tracking_for_push_dest(remote, branch->refname, err);
+
+       switch (push_default) {
+       case PUSH_DEFAULT_NOTHING:
+               return error_buf(err, _("push has no destination (push.default is 'nothing')"));
+
+       case PUSH_DEFAULT_MATCHING:
+       case PUSH_DEFAULT_CURRENT:
+               return tracking_for_push_dest(remote, branch->refname, err);
+
+       case PUSH_DEFAULT_UPSTREAM:
+               return branch_get_upstream(branch, err);
+
+       case PUSH_DEFAULT_UNSPECIFIED:
+       case PUSH_DEFAULT_SIMPLE:
+               {
+                       const char *up, *cur;
+
+                       up = branch_get_upstream(branch, err);
+                       if (!up)
+                               return NULL;
+                       cur = tracking_for_push_dest(remote, branch->refname, err);
+                       if (!cur)
+                               return NULL;
+                       if (strcmp(cur, up))
+                               return error_buf(err,
+                                                _("cannot resolve 'simple' push to a single destination"));
+                       return cur;
+               }
+       }
+
+       die("BUG: unhandled push situation");
+}
+
+const char *branch_get_push(struct branch *branch, struct strbuf *err)
+{
+       if (!branch->push_tracking_ref)
+               branch->push_tracking_ref = branch_get_push_1(branch, err);
+       return branch->push_tracking_ref;
+}
+
 static int ignore_symref_update(const char *refname)
 {
        unsigned char sha1[20];
@@ -1877,12 +2023,15 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
 
 /*
  * Compare a branch with its upstream, and save their differences (number
- * of commits) in *num_ours and *num_theirs.
+ * of commits) in *num_ours and *num_theirs. The name of the upstream branch
+ * (or NULL if no upstream is defined) is returned via *upstream_name, if it
+ * is not itself NULL.
  *
- * Return 0 if branch has no upstream (no base), -1 if upstream is missing
- * (with "gone" base), otherwise 1 (with base).
+ * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
+ * upstream defined, or ref does not exist), 0 otherwise.
  */
-int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
+int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
+                      const char **upstream_name)
 {
        unsigned char sha1[20];
        struct commit *ours, *theirs;
@@ -1892,12 +2041,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
        int rev_argc;
 
        /* Cannot stat unless we are marked to build on top of somebody else. */
-       if (!branch ||
-           !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
-               return 0;
+       base = branch_get_upstream(branch, NULL);
+       if (upstream_name)
+               *upstream_name = base;
+       if (!base)
+               return -1;
 
        /* Cannot stat if what we used to build on no longer exists */
-       base = branch->merge[0]->dst;
        if (read_ref(base, sha1))
                return -1;
        theirs = lookup_commit_reference(sha1);
@@ -1913,7 +2063,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
        /* are we the same? */
        if (theirs == ours) {
                *num_theirs = *num_ours = 0;
-               return 1;
+               return 0;
        }
 
        /* Run "rev-list --left-right ours...theirs" internally... */
@@ -1949,7 +2099,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
        /* clear object flags smudged by the above traversal */
        clear_commit_marks(ours, ALL_REV_FLAGS);
        clear_commit_marks(theirs, ALL_REV_FLAGS);
-       return 1;
+       return 0;
 }
 
 /*
@@ -1958,23 +2108,17 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
 int format_tracking_info(struct branch *branch, struct strbuf *sb)
 {
        int ours, theirs;
+       const char *full_base;
        char *base;
        int upstream_is_gone = 0;
 
-       switch (stat_tracking_info(branch, &ours, &theirs)) {
-       case 0:
-               /* no base */
-               return 0;
-       case -1:
-               /* with "gone" base */
+       if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
+               if (!full_base)
+                       return 0;
                upstream_is_gone = 1;
-               break;
-       default:
-               /* with base */
-               break;
        }
 
-       base = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+       base = shorten_unambiguous_ref(full_base, 0);
        if (upstream_is_gone) {
                strbuf_addf(sb,
                        _("Your branch is based on '%s', but the upstream is gone.\n"),
index 02d66ceff5c962995de37f351a09fe91055d6364..312b7ca131c459faeae32ff5cf3021c6516eb2af 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -203,19 +203,42 @@ struct branch {
        const char *refname;
 
        const char *remote_name;
-       struct remote *remote;
+       const char *pushremote_name;
 
        const char **merge_name;
        struct refspec **merge;
        int merge_nr;
        int merge_alloc;
+
+       const char *push_tracking_ref;
 };
 
 struct branch *branch_get(const char *name);
+const char *remote_for_branch(struct branch *branch, int *explicit);
+const char *pushremote_for_branch(struct branch *branch, int *explicit);
 
 int branch_has_merge_config(struct branch *branch);
 int branch_merge_matches(struct branch *, int n, const char *);
 
+/**
+ * Return the fully-qualified refname of the tracking branch for `branch`.
+ * I.e., what "branch@{upstream}" would give you. Returns NULL if no
+ * upstream is defined.
+ *
+ * If `err` is not NULL and no upstream is defined, a more specific error
+ * message is recorded there (if the function does not return NULL, then
+ * `err` is not touched).
+ */
+const char *branch_get_upstream(struct branch *branch, struct strbuf *err);
+
+/**
+ * Return the tracking branch that corresponds to the ref we would push to
+ * given a bare `git push` while `branch` is checked out.
+ *
+ * The return value and `err` conventions match those of `branch_get_upstream`.
+ */
+const char *branch_get_push(struct branch *branch, struct strbuf *err);
+
 /* Flags to match_refs. */
 enum match_refs_flags {
        MATCH_REFS_NONE         = 0,
@@ -226,7 +249,8 @@ enum match_refs_flags {
 };
 
 /* Reporting of tracking info */
-int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);
+int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
+                      const char **upstream_name);
 int format_tracking_info(struct branch *branch, struct strbuf *sb);
 
 struct ref *get_local_heads(void);
index 31644dec04fe4a77d43624720ff516de2d746dbc..94aea9a36f16d9d72c1769aa9e258c5370c7dc84 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -659,6 +659,8 @@ int rerere_forget(struct pathspec *pathspec)
                return error("Could not read index");
 
        fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+       if (fd < 0)
+               return 0;
 
        unmerge_cache(pathspec);
        find_conflict(&conflict);
index ccc6dac54b570d7174175242ba2d535d4006ab1c..7e38148fe52959e1cb8435132c65491dba6850b0 100644 (file)
@@ -3286,7 +3286,7 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
        int ret;
 
        if (!size) {
-               ret = index_mem(sha1, NULL, size, type, path, flags);
+               ret = index_mem(sha1, "", size, type, path, flags);
        } else if (size <= SMALL_FILE_SIZE) {
                char *buf = xmalloc(size);
                if (size == read_in_full(fd, buf, size))
index 1cb810877af3eee2690f094897e5895da7af41fc..e57513e61032fb851a669255c30d8417f66c81f9 100644 (file)
@@ -6,6 +6,7 @@
 #include "tree-walk.h"
 #include "refs.h"
 #include "remote.h"
+#include "dir.h"
 
 static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
 
@@ -415,12 +416,12 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
-static inline int upstream_mark(const char *string, int len)
+static inline int at_mark(const char *string, int len,
+                         const char **suffix, int nr)
 {
-       const char *suffix[] = { "@{upstream}", "@{u}" };
        int i;
 
-       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+       for (i = 0; i < nr; i++) {
                int suffix_len = strlen(suffix[i]);
                if (suffix_len <= len
                    && !memcmp(string, suffix[i], suffix_len))
@@ -429,6 +430,18 @@ static inline int upstream_mark(const char *string, int len)
        return 0;
 }
 
+static inline int upstream_mark(const char *string, int len)
+{
+       const char *suffix[] = { "@{upstream}", "@{u}" };
+       return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
+}
+
+static inline int push_mark(const char *string, int len)
+{
+       const char *suffix[] = { "@{push}" };
+       return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
+}
+
 static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
 static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
 
@@ -476,7 +489,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
                                        nth_prior = 1;
                                        continue;
                                }
-                               if (!upstream_mark(str + at, len - at)) {
+                               if (!upstream_mark(str + at, len - at) &&
+                                   !push_mark(str + at, len - at)) {
                                        reflog_len = (len-1) - (at+2);
                                        len = at;
                                }
@@ -1055,46 +1069,36 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref)
        free(s);
 }
 
-static const char *get_upstream_branch(const char *branch_buf, int len)
-{
-       char *branch = xstrndup(branch_buf, len);
-       struct branch *upstream = branch_get(*branch ? branch : NULL);
-
-       /*
-        * Upstream can be NULL only if branch refers to HEAD and HEAD
-        * points to something different than a branch.
-        */
-       if (!upstream)
-               die(_("HEAD does not point to a branch"));
-       if (!upstream->merge || !upstream->merge[0]->dst) {
-               if (!ref_exists(upstream->refname))
-                       die(_("No such branch: '%s'"), branch);
-               if (!upstream->merge) {
-                       die(_("No upstream configured for branch '%s'"),
-                               upstream->name);
-               }
-               die(
-                       _("Upstream branch '%s' not stored as a remote-tracking branch"),
-                       upstream->merge[0]->src);
-       }
-       free(branch);
-
-       return upstream->merge[0]->dst;
-}
-
-static int interpret_upstream_mark(const char *name, int namelen,
-                                  int at, struct strbuf *buf)
+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 *))
 {
        int len;
+       struct branch *branch;
+       struct strbuf err = STRBUF_INIT;
+       const char *value;
 
-       len = upstream_mark(name + at, namelen - at);
+       len = get_mark(name + at, namelen - at);
        if (!len)
                return -1;
 
        if (memchr(name, ':', at))
                return -1;
 
-       set_shortened_ref(buf, get_upstream_branch(name, at));
+       if (at) {
+               char *name_str = xmemdupz(name, at);
+               branch = branch_get(name_str);
+               free(name_str);
+       } else
+               branch = branch_get(NULL);
+
+       value = get_data(branch, &err);
+       if (!value)
+               die("%s", err.buf);
+
+       set_shortened_ref(buf, value);
        return len + at;
 }
 
@@ -1145,7 +1149,13 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
                if (len > 0)
                        return reinterpret(name, namelen, len, buf);
 
-               len = interpret_upstream_mark(name, namelen, at - name, buf);
+               len = interpret_branch_mark(name, namelen, at - name, buf,
+                                           upstream_mark, branch_get_upstream);
+               if (len > 0)
+                       return len;
+
+               len = interpret_branch_mark(name, namelen, at - name, buf,
+                                           push_mark, branch_get_push);
                if (len > 0)
                        return len;
        }
@@ -1237,14 +1247,13 @@ static void diagnose_invalid_sha1_path(const char *prefix,
                                       const char *object_name,
                                       int object_name_len)
 {
-       struct stat st;
        unsigned char sha1[20];
        unsigned mode;
 
        if (!prefix)
                prefix = "";
 
-       if (!lstat(filename, &st))
+       if (file_exists(filename))
                die("Path '%s' exists on disk, but not in '%.*s'.",
                    filename, object_name_len, object_name);
        if (errno == ENOENT || errno == ENOTDIR) {
@@ -1271,7 +1280,6 @@ static void diagnose_invalid_index_path(int stage,
                                        const char *prefix,
                                        const char *filename)
 {
-       struct stat st;
        const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
@@ -1314,7 +1322,7 @@ static void diagnose_invalid_index_path(int stage,
                            ce_stage(ce), filename);
        }
 
-       if (!lstat(filename, &st))
+       if (file_exists(filename))
                die("Path '%s' exists on disk, but not in the index.", filename);
        if (errno == ENOENT || errno == ENOTDIR)
                die("Path '%s' does not exist (neither on disk nor in the index).",
@@ -1435,11 +1443,19 @@ static int get_sha1_with_context_1(const char *name,
                        new_filename = resolve_relative_path(filename);
                        if (new_filename)
                                filename = new_filename;
-                       ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
-                       if (ret && only_to_die) {
-                               diagnose_invalid_sha1_path(prefix, filename,
-                                                          tree_sha1,
-                                                          name, len);
+                       if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
+                               ret = get_tree_entry_follow_symlinks(tree_sha1,
+                                       filename, sha1, &oc->symlink_path,
+                                       &oc->mode);
+                       } else {
+                               ret = get_tree_entry(tree_sha1, filename,
+                                                    sha1, &oc->mode);
+                               if (ret && only_to_die) {
+                                       diagnose_invalid_sha1_path(prefix,
+                                                                  filename,
+                                                                  tree_sha1,
+                                                                  name, len);
+                               }
                        }
                        hashcpy(oc->tree, tree_sha1);
                        strlcpy(oc->path, filename, sizeof(oc->path));
@@ -1470,5 +1486,7 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)
 
 int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
 {
+       if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
+               die("BUG: incompatible flags for get_sha1_with_context");
        return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
 }
index 21485e20665979458fb794ee4c3cc77c27032fa8..968b780a06d1f17b190395f06f78fc3124fcf445 100644 (file)
@@ -41,13 +41,6 @@ int read_link_extension(struct index_state *istate,
        return 0;
 }
 
-static int write_strbuf(void *user_data, const void *data, size_t len)
-{
-       struct strbuf *sb = user_data;
-       strbuf_add(sb, data, len);
-       return len;
-}
-
 int write_link_extension(struct strbuf *sb,
                         struct index_state *istate)
 {
@@ -55,8 +48,8 @@ int write_link_extension(struct strbuf *sb,
        strbuf_add(sb, si->base_sha1, 20);
        if (!si->delete_bitmap && !si->replace_bitmap)
                return 0;
-       ewah_serialize_to(si->delete_bitmap, write_strbuf, sb);
-       ewah_serialize_to(si->replace_bitmap, write_strbuf, sb);
+       ewah_serialize_strbuf(si->delete_bitmap, sb);
+       ewah_serialize_strbuf(si->replace_bitmap, sb);
        return 0;
 }
 
index e4c59df5ac60996d144cab43b5bb833bcf64b67c..15e90d1c10ebc9ecb0825f731b33d81cf3d9b4d5 100644 (file)
@@ -892,7 +892,6 @@ int submodule_uses_gitfile(const char *path)
 
 int ok_to_remove_submodule(const char *path)
 {
-       struct stat st;
        ssize_t len;
        struct child_process cp = CHILD_PROCESS_INIT;
        const char *argv[] = {
@@ -905,7 +904,7 @@ int ok_to_remove_submodule(const char *path)
        struct strbuf buf = STRBUF_INIT;
        int ok_to_remove = 1;
 
-       if ((lstat(path, &st) < 0) || is_empty_dir(path))
+       if (!file_exists(path) || is_empty_dir(path))
                return 1;
 
        if (!submodule_uses_gitfile(path))
index e0200b9f338f0478fd55029eab9c32c6b7956476..718efa04d34df1f867a37d7b3073f1463457455b 100755 (executable)
@@ -226,4 +226,30 @@ test_expect_success EXPENSIVE 'filter large file' '
        ! test -s err
 '
 
+test_expect_success "filter: clean empty file" '
+       git config filter.in-repo-header.clean  "echo cleaned && cat" &&
+       git config filter.in-repo-header.smudge "sed 1d" &&
+
+       echo "empty-in-worktree    filter=in-repo-header" >>.gitattributes &&
+       >empty-in-worktree &&
+
+       echo cleaned >expected &&
+       git add empty-in-worktree &&
+       git show :empty-in-worktree >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success "filter: smudge empty file" '
+       git config filter.empty-in-repo.clean "cat >/dev/null" &&
+       git config filter.empty-in-repo.smudge "echo smudged && cat" &&
+
+       echo "empty-in-repo filter=empty-in-repo" >>.gitattributes &&
+       echo dead data walking >empty-in-repo &&
+       git add empty-in-repo &&
+
+       echo smudged >expected &&
+       git checkout-index --prefix=filtered- empty-in-repo &&
+       test_cmp expected filtered-empty-in-repo
+'
+
 test_done
index 4f225db9a72729e99f5eaab0b81ec579691b34c7..93a4794930bfebc99504a245392f32a3c92b3dde 100755 (executable)
@@ -201,6 +201,13 @@ do
     '
 done
 
+for opt in t s e p
+do
+    test_expect_success "Passing -$opt with --follow-symlinks fails" '
+           test_must_fail git cat-file --follow-symlinks -$opt $hello_sha1
+       '
+done
+
 test_expect_success "--batch-check for a non-existent named object" '
     test "foobar42 missing
 foobar84 missing" = \
@@ -341,4 +348,203 @@ test_expect_success "Size of large broken object is correct when type is large"
        test_cmp expect actual
 '
 
+# Tests for git cat-file --follow-symlinks
+test_expect_success 'prep for symlink tests' '
+       echo_without_newline "$hello_content" >morx &&
+       test_ln_s_add morx same-dir-link &&
+       test_ln_s_add dir link-to-dir &&
+       test_ln_s_add ../fleem out-of-repo-link &&
+       test_ln_s_add .. out-of-repo-link-dir &&
+       test_ln_s_add same-dir-link link-to-link &&
+       test_ln_s_add nope broken-same-dir-link &&
+       mkdir dir &&
+       test_ln_s_add ../morx dir/parent-dir-link &&
+       test_ln_s_add .. dir/link-dir &&
+       test_ln_s_add ../../escape dir/out-of-repo-link &&
+       test_ln_s_add ../.. dir/out-of-repo-link-dir &&
+       test_ln_s_add nope dir/broken-link-in-dir &&
+       mkdir dir/subdir &&
+       test_ln_s_add ../../morx dir/subdir/grandparent-dir-link &&
+       test_ln_s_add ../../../great-escape dir/subdir/out-of-repo-link &&
+       test_ln_s_add ../../.. dir/subdir/out-of-repo-link-dir &&
+       test_ln_s_add ../../../ dir/subdir/out-of-repo-link-dir-trailing &&
+       test_ln_s_add ../parent-dir-link dir/subdir/parent-dir-link-to-link &&
+       echo_without_newline "$hello_content" >dir/subdir/ind2 &&
+       echo_without_newline "$hello_content" >dir/ind1 &&
+       test_ln_s_add dir dirlink &&
+       test_ln_s_add dir/subdir subdirlink &&
+       test_ln_s_add subdir/ind2 dir/link-to-child &&
+       test_ln_s_add dir/link-to-child link-to-down-link &&
+       test_ln_s_add dir/.. up-down &&
+       test_ln_s_add dir/../ up-down-trailing &&
+       test_ln_s_add dir/../morx up-down-file &&
+       test_ln_s_add dir/../../morx up-up-down-file &&
+       test_ln_s_add subdirlink/../../morx up-two-down-file &&
+       test_ln_s_add loop1 loop2 &&
+       test_ln_s_add loop2 loop1 &&
+       git add morx dir/subdir/ind2 dir/ind1 &&
+       git commit -am "test" &&
+       echo $hello_sha1 blob $hello_size >found
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for non-links' '
+       echo HEAD:morx | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual &&
+       echo HEAD:nope missing >expect &&
+       echo HEAD:nope | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, same-dir links' '
+       echo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, links to dirs' '
+       echo HEAD:link-to-dir/ind1 | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for broken in-repo, same-dir links' '
+       echo dangling 25 >expect &&
+       echo HEAD:broken-same-dir-link >>expect &&
+       echo HEAD:broken-same-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for same-dir links-to-links' '
+       echo HEAD:link-to-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for parent-dir links' '
+       echo HEAD:dir/parent-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual &&
+       echo notdir 29 >expect &&
+       echo HEAD:dir/parent-dir-link/nope >>expect &&
+       echo HEAD:dir/parent-dir-link/nope | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for .. links' '
+       echo dangling 22 >expect &&
+       echo HEAD:dir/link-dir/nope >>expect &&
+       echo HEAD:dir/link-dir/nope | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:dir/link-dir/morx | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual &&
+       echo dangling 27 >expect &&
+       echo HEAD:dir/broken-link-in-dir >>expect &&
+       echo HEAD:dir/broken-link-in-dir | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for ../.. links' '
+       echo notdir 41 >expect &&
+       echo HEAD:dir/subdir/grandparent-dir-link/nope >>expect &&
+       echo HEAD:dir/subdir/grandparent-dir-link/nope | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:dir/subdir/grandparent-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual &&
+       echo HEAD:dir/subdir/parent-dir-link-to-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/ links' '
+       echo dangling 17 >expect &&
+       echo HEAD:dirlink/morx >>expect &&
+       echo HEAD:dirlink/morx | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo $hello_sha1 blob $hello_size >expect &&
+       echo HEAD:dirlink/ind1 | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/subdir links' '
+       echo dangling 20 >expect &&
+       echo HEAD:subdirlink/morx >>expect &&
+       echo HEAD:subdirlink/morx | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:subdirlink/ind2 | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir ->subdir links' '
+       echo notdir 27 >expect &&
+       echo HEAD:dir/link-to-child/morx >>expect &&
+       echo HEAD:dir/link-to-child/morx | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:dir/link-to-child | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual &&
+       echo HEAD:link-to-down-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks' '
+       echo symlink 8 >expect &&
+       echo ../fleem >>expect &&
+       echo HEAD:out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo symlink 2 >expect &&
+       echo .. >>expect &&
+       echo HEAD:out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in dirs' '
+       echo symlink 9 >expect &&
+       echo ../escape >>expect &&
+       echo HEAD:dir/out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo symlink 2 >expect &&
+       echo .. >>expect &&
+       echo HEAD:dir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in subdirs' '
+       echo symlink 15 >expect &&
+       echo ../great-escape >>expect &&
+       echo HEAD:dir/subdir/out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo symlink 2 >expect &&
+       echo .. >>expect &&
+       echo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo symlink 3 >expect &&
+       echo ../ >>expect &&
+       echo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '
+       echo HEAD: | git cat-file --batch-check >expect &&
+       echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual &&
+       echo symlink 7 >expect &&
+       echo ../morx >>expect &&
+       echo HEAD:up-up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual &&
+       echo HEAD:up-two-down-file | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlink breaks loops' '
+       echo loop 10 >expect &&
+       echo HEAD:loop1 >>expect &&
+       echo HEAD:loop1 | git cat-file --batch-check --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch --follow-symlink returns correct sha and mode' '
+       echo HEAD:morx | git cat-file --batch >expect &&
+       echo HEAD:morx | git cat-file --batch --follow-symlinks >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 1978947c4196fb666d55395bedc53ae149b6d13d..46ef1f22dca14423ecff8da45d608a8885f23dba 100755 (executable)
@@ -150,7 +150,7 @@ test_expect_success 'branch@{u} works when tracking a local branch' '
 
 test_expect_success 'branch@{u} error message when no upstream' '
        cat >expect <<-EOF &&
-       fatal: No upstream configured for branch ${sq}non-tracking${sq}
+       fatal: no upstream configured for branch ${sq}non-tracking${sq}
        EOF
        error_message non-tracking@{u} 2>actual &&
        test_i18ncmp expect actual
@@ -158,7 +158,7 @@ test_expect_success 'branch@{u} error message when no upstream' '
 
 test_expect_success '@{u} error message when no upstream' '
        cat >expect <<-EOF &&
-       fatal: No upstream configured for branch ${sq}master${sq}
+       fatal: no upstream configured for branch ${sq}master${sq}
        EOF
        test_must_fail git rev-parse --verify @{u} 2>actual &&
        test_i18ncmp expect actual
@@ -166,7 +166,7 @@ test_expect_success '@{u} error message when no upstream' '
 
 test_expect_success 'branch@{u} error message with misspelt branch' '
        cat >expect <<-EOF &&
-       fatal: No such branch: ${sq}no-such-branch${sq}
+       fatal: no such branch: ${sq}no-such-branch${sq}
        EOF
        error_message no-such-branch@{u} 2>actual &&
        test_i18ncmp expect actual
@@ -183,7 +183,7 @@ test_expect_success '@{u} error message when not on a branch' '
 
 test_expect_success 'branch@{u} error message if upstream branch not fetched' '
        cat >expect <<-EOF &&
-       fatal: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
+       fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
        EOF
        error_message bad-upstream@{u} 2>actual &&
        test_i18ncmp expect actual
diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh
new file mode 100755 (executable)
index 0000000..7214f5b
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='test <branch>@{push} syntax'
+. ./test-lib.sh
+
+resolve () {
+       echo "$2" >expect &&
+       git rev-parse --symbolic-full-name "$1" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+       git init --bare parent.git &&
+       git init --bare other.git &&
+       git remote add origin parent.git &&
+       git remote add other other.git &&
+       test_commit base &&
+       git push origin HEAD &&
+       git branch --set-upstream-to=origin/master master &&
+       git branch --track topic origin/master &&
+       git push origin topic &&
+       git push other topic
+'
+
+test_expect_success '@{push} with default=nothing' '
+       test_config push.default nothing &&
+       test_must_fail git rev-parse master@{push}
+'
+
+test_expect_success '@{push} with default=simple' '
+       test_config push.default simple &&
+       resolve master@{push} refs/remotes/origin/master
+'
+
+test_expect_success 'triangular @{push} fails with default=simple' '
+       test_config push.default simple &&
+       test_must_fail git rev-parse topic@{push}
+'
+
+test_expect_success '@{push} with default=current' '
+       test_config push.default current &&
+       resolve topic@{push} refs/remotes/origin/topic
+'
+
+test_expect_success '@{push} with default=matching' '
+       test_config push.default matching &&
+       resolve topic@{push} refs/remotes/origin/topic
+'
+
+test_expect_success '@{push} with pushremote defined' '
+       test_config push.default current &&
+       test_config branch.topic.pushremote other &&
+       resolve topic@{push} refs/remotes/other/topic
+'
+
+test_expect_success '@{push} with push refspecs' '
+       test_config push.default nothing &&
+       test_config remote.origin.push refs/heads/*:refs/heads/magic/* &&
+       git push &&
+       resolve topic@{push} refs/remotes/origin/magic/topic
+'
+
+test_done
index 0746eeeff70c29c91accacdfa1efa4bc2e612715..7396ca991132a88012955a281a71c330eab2092b 100755 (executable)
@@ -100,6 +100,10 @@ test_expect_success 'unstashing in a subdirectory' '
        )
 '
 
+test_expect_success 'stash drop complains of extra options' '
+       test_must_fail git stash drop --foo
+'
+
 test_expect_success 'drop top stash' '
        git reset --hard &&
        git stash list > stashlist1 &&
index ea2e0d4b489a9e079c93b36b013d4bc919755c02..7a48236e87695d947c7c3f166e1d291cd3b7a05b 100755 (executable)
@@ -61,10 +61,10 @@ test_expect_success 'git rebase' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD^)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -77,9 +77,9 @@ test_expect_success 'git rebase --skip' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -89,9 +89,9 @@ test_expect_success 'git rebase --skip the last one' '
        test_must_fail git rebase --onto D A &&
        git rebase --skip &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse E) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse E) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -103,10 +103,10 @@ test_expect_success 'git rebase -m' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD^)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -119,9 +119,9 @@ test_expect_success 'git rebase -m --skip' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -148,10 +148,10 @@ test_expect_success 'git rebase -i (unchanged)' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD^)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -163,9 +163,9 @@ test_expect_success 'git rebase -i (skip)' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -177,10 +177,10 @@ test_expect_success 'git rebase -i (squash)' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -189,10 +189,10 @@ test_expect_success 'git rebase -i (fixup without conflict)' '
        clear_hook_input &&
        FAKE_LINES="1 fixup 2" git rebase -i B &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
@@ -205,10 +205,27 @@ test_expect_success 'git rebase -i (double edit)' '
        git add foo &&
        git rebase --continue &&
        echo rebase >expected.args &&
-       cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD^)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -i (exec)' '
+       git reset --hard D &&
+       clear_hook_input &&
+       FAKE_LINES="edit 1 exec_false 2" git rebase -i B &&
+       echo something >bar &&
+       git add bar &&
+       # Fails because of exec false
+       test_must_fail git rebase --continue &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<-EOF &&
+       $(git rev-parse C) $(git rev-parse HEAD^)
+       $(git rev-parse D) $(git rev-parse HEAD)
+       EOF
        verify_hook_input
 '
 
index 8a5f2363a93b324964caaab81903fae6b802c29b..ec22c984450bda4110fe165917aece2ecbe4ceb2 100755 (executable)
@@ -1120,6 +1120,61 @@ test_expect_success 'fetch exact SHA1' '
        )
 '
 
+for configallowtipsha1inwant in true false
+do
+       test_expect_success "shallow fetch reachable SHA1 (but not a ref), allowtipsha1inwant=$configallowtipsha1inwant" '
+               mk_empty testrepo &&
+               (
+                       cd testrepo &&
+                       git config uploadpack.allowtipsha1inwant $configallowtipsha1inwant &&
+                       git commit --allow-empty -m foo &&
+                       git commit --allow-empty -m bar
+               ) &&
+               SHA1=$(git --git-dir=testrepo/.git rev-parse HEAD^) &&
+               mk_empty shallow &&
+               (
+                       cd shallow &&
+                       test_must_fail git fetch --depth=1 ../testrepo/.git $SHA1 &&
+                       git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
+                       git fetch --depth=1 ../testrepo/.git $SHA1 &&
+                       git cat-file commit $SHA1
+               )
+       '
+
+       test_expect_success "deny fetch unreachable SHA1, allowtipsha1inwant=$configallowtipsha1inwant" '
+               mk_empty testrepo &&
+               (
+                       cd testrepo &&
+                       git config uploadpack.allowtipsha1inwant $configallowtipsha1inwant &&
+                       git commit --allow-empty -m foo &&
+                       git commit --allow-empty -m bar &&
+                       git commit --allow-empty -m xyz
+               ) &&
+               SHA1_1=$(git --git-dir=testrepo/.git rev-parse HEAD^^) &&
+               SHA1_2=$(git --git-dir=testrepo/.git rev-parse HEAD^) &&
+               SHA1_3=$(git --git-dir=testrepo/.git rev-parse HEAD) &&
+               (
+                       cd testrepo &&
+                       git reset --hard $SHA1_2 &&
+                       git cat-file commit $SHA1_1 &&
+                       git cat-file commit $SHA1_3
+               ) &&
+               mk_empty shallow &&
+               (
+                       cd shallow &&
+                       test_must_fail git fetch ../testrepo/.git $SHA1_3 &&
+                       test_must_fail git fetch ../testrepo/.git $SHA1_1 &&
+                       git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
+                       git fetch ../testrepo/.git $SHA1_1 &&
+                       git cat-file commit $SHA1_1 &&
+                       test_must_fail git cat-file commit $SHA1_2 &&
+                       git fetch ../testrepo/.git $SHA1_2 &&
+                       git cat-file commit $SHA1_2 &&
+                       test_must_fail git fetch ../testrepo/.git $SHA1_3
+               )
+       '
+done
+
 test_expect_success 'fetch follows tags by default' '
        mk_test testrepo heads/master &&
        rm -fr src dst &&
index 8cccecc2fcce0c1a9b1f90a30608ab9e9b662eae..c278adaa5a2556327a820cdeb98943b58d1e429f 100755 (executable)
@@ -17,6 +17,9 @@ test_expect_success setup '
                git commit -m "add bfile"
        ) &&
        test_tick && test_tick &&
+       echo "second" >afile &&
+       git add afile &&
+       git commit -m "second commit" &&
        echo "original $dollar" >afile &&
        git add afile &&
        git commit -m "do not clobber $dollar signs"
@@ -32,4 +35,18 @@ test_expect_success pull '
 )
 '
 
+test_expect_success '--log=1 limits shortlog length' '
+(
+       cd cloned &&
+       git reset --hard HEAD^ &&
+       test "$(cat afile)" = original &&
+       test "$(cat bfile)" = added &&
+       git pull --log=1 &&
+       git log -3 &&
+       git cat-file commit HEAD >result &&
+       grep Dollar result &&
+       ! grep "second commit" result
+)
+'
+
 test_done
index 2b293119015af7bfb8f151d2a3ec8c51c4c0b209..58207d88250f57d448f4e83baf2add3192cb9455 100755 (executable)
@@ -218,27 +218,35 @@ test_expect_success 'transfer.hiderefs works over smart-http' '
        git -C hidden.git rev-parse --verify b
 '
 
-test_expect_success 'create 2,000 tags in the repo' '
-       (
-       cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
-       for i in $(test_seq 2000)
+# create an arbitrary number of tags, numbered from tag-$1 to tag-$2
+create_tags () {
+       rm -f marks &&
+       for i in $(test_seq "$1" "$2")
        do
-               echo "commit refs/heads/too-many-refs"
-               echo "mark :$i"
-               echo "committer git <git@example.com> $i +0000"
-               echo "data 0"
-               echo "M 644 inline bla.txt"
-               echo "data 4"
-               echo "bla"
+               # don't use here-doc, because it requires a process
+               # per loop iteration
+               echo "commit refs/heads/too-many-refs-$1" &&
+               echo "mark :$i" &&
+               echo "committer git <git@example.com> $i +0000" &&
+               echo "data 0" &&
+               echo "M 644 inline bla.txt" &&
+               echo "data 4" &&
+               echo "bla" &&
                # make every commit dangling by always
                # rewinding the branch after each commit
-               echo "reset refs/heads/too-many-refs"
-               echo "from :1"
+               echo "reset refs/heads/too-many-refs-$1" &&
+               echo "from :$1"
        done | git fast-import --export-marks=marks &&
 
        # now assign tags to all the dangling commits we created above
        tag=$(perl -e "print \"bla\" x 30") &&
        sed -e "s|^:\([^ ]*\) \(.*\)$|\2 refs/tags/$tag-\1|" <marks >>packed-refs
+}
+
+test_expect_success 'create 2,000 tags in the repo' '
+       (
+               cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+               create_tags 1 2000
        )
 '
 
@@ -259,5 +267,20 @@ test_expect_success 'large fetch-pack requests can be split across POSTs' '
        test_line_count = 2 posts
 '
 
+test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' '
+       (
+               cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+               create_tags 2001 50000
+       ) &&
+       git -C too-many-refs fetch -q --tags &&
+       (
+               cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+               create_tags 50001 100000
+       ) &&
+       git -C too-many-refs fetch -q --tags &&
+       git -C too-many-refs for-each-ref refs/tags >tags &&
+       test_line_count = 100000 tags
+'
+
 stop_httpd
 test_done
index c66bf7981c5328356005963e15262c8d870ac61c..24fc2ba55da38232f07c58346c628b61822af44a 100755 (executable)
@@ -28,7 +28,10 @@ test_expect_success setup '
        git update-ref refs/remotes/origin/master master &&
        git remote add origin nowhere &&
        git config branch.master.remote origin &&
-       git config branch.master.merge refs/heads/master
+       git config branch.master.merge refs/heads/master &&
+       git remote add myfork elsewhere &&
+       git config remote.pushdefault myfork &&
+       git config push.default current
 '
 
 test_atom() {
@@ -47,6 +50,7 @@ test_atom() {
 
 test_atom head refname refs/heads/master
 test_atom head upstream refs/remotes/origin/master
+test_atom head push refs/remotes/myfork/master
 test_atom head objecttype commit
 test_atom head objectsize 171
 test_atom head objectname $(git rev-parse refs/heads/master)
@@ -83,6 +87,7 @@ test_atom head HEAD '*'
 
 test_atom tag refname refs/tags/testtag
 test_atom tag upstream ''
+test_atom tag push ''
 test_atom tag objecttype tag
 test_atom tag objectsize 154
 test_atom tag objectname $(git rev-parse refs/tags/testtag)
@@ -347,6 +352,12 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' '
        test_cmp expected actual
 '
 
+test_expect_success '%(push) supports tracking specifiers, too' '
+       echo "[ahead 1]" >expected &&
+       git for-each-ref --format="%(push:track)" refs/heads >actual &&
+       test_cmp expected actual
+'
+
 cat >expected <<EOF
 $(git rev-parse --short HEAD)
 EOF
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
new file mode 100755 (executable)
index 0000000..bd4806c
--- /dev/null
@@ -0,0 +1,357 @@
+#!/bin/sh
+
+test_description='test untracked cache'
+
+. ./test-lib.sh
+
+avoid_racy() {
+       sleep 1
+}
+
+# It's fine if git update-index returns an error code other than one,
+# it'll be caught in the first test.
+test_lazy_prereq UNTRACKED_CACHE '
+       { git update-index --untracked-cache; ret=$?; } &&
+       test $ret -ne 1
+'
+
+if ! test_have_prereq UNTRACKED_CACHE; then
+       skip_all='This system does not support untracked cache'
+       test_done
+fi
+
+test_expect_success 'setup' '
+       git init worktree &&
+       cd worktree &&
+       mkdir done dtwo dthree &&
+       touch one two three done/one dtwo/two dthree/three &&
+       git add one two done/one &&
+       : >.git/info/exclude &&
+       git update-index --untracked-cache
+'
+
+test_expect_success 'untracked cache is empty' '
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude 0000000000000000000000000000000000000000
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+EOF
+       test_cmp ../expect ../actual
+'
+
+cat >../status.expect <<EOF &&
+A  done/one
+A  one
+A  two
+?? dthree/
+?? dtwo/
+?? three
+EOF
+
+cat >../dump.expect <<EOF &&
+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ 0000000000000000000000000000000000000000 recurse valid
+dthree/
+dtwo/
+three
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+three
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+
+test_expect_success 'status first time (empty cache)' '
+       avoid_racy &&
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 3
+gitignore invalidation: 1
+directory invalidation: 0
+opendir: 4
+EOF
+       test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'untracked cache after first status' '
+       test-dump-untracked-cache >../actual &&
+       test_cmp ../dump.expect ../actual
+'
+
+test_expect_success 'status second time (fully populated cache)' '
+       avoid_racy &&
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 0
+opendir: 0
+EOF
+       test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'untracked cache after second status' '
+       test-dump-untracked-cache >../actual &&
+       test_cmp ../dump.expect ../actual
+'
+
+test_expect_success 'modify in root directory, one dir invalidation' '
+       avoid_racy &&
+       : >four &&
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       cat >../status.expect <<EOF &&
+A  done/one
+A  one
+A  two
+?? dthree/
+?? dtwo/
+?? four
+?? three
+EOF
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 1
+opendir: 1
+EOF
+       test_cmp ../trace.expect ../trace
+
+'
+
+test_expect_success 'verify untracked cache dump' '
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ 0000000000000000000000000000000000000000 recurse valid
+dthree/
+dtwo/
+four
+three
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+three
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_expect_success 'new .gitignore invalidates recursively' '
+       avoid_racy &&
+       echo four >.gitignore &&
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       cat >../status.expect <<EOF &&
+A  done/one
+A  one
+A  two
+?? .gitignore
+?? dthree/
+?? dtwo/
+?? three
+EOF
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 1
+directory invalidation: 1
+opendir: 4
+EOF
+       test_cmp ../trace.expect ../trace
+
+'
+
+test_expect_success 'verify untracked cache dump' '
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dthree/
+dtwo/
+three
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+three
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_expect_success 'new info/exclude invalidates everything' '
+       avoid_racy &&
+       echo three >>.git/info/exclude &&
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       cat >../status.expect <<EOF &&
+A  done/one
+A  one
+A  two
+?? .gitignore
+?? dtwo/
+EOF
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 1
+directory invalidation: 0
+opendir: 4
+EOF
+       test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'verify untracked cache dump' '
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dtwo/
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_expect_success 'move two from tracked to untracked' '
+       git rm --cached two &&
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_expect_success 'status after the move' '
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       cat >../status.expect <<EOF &&
+A  done/one
+A  one
+?? .gitignore
+?? dtwo/
+?? two
+EOF
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 0
+opendir: 1
+EOF
+       test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'verify untracked cache dump' '
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dtwo/
+two
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_expect_success 'move two from untracked to tracked' '
+       git add two &&
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_expect_success 'status after the move' '
+       : >../trace &&
+       GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+       git status --porcelain >../actual &&
+       cat >../status.expect <<EOF &&
+A  done/one
+A  one
+A  two
+?? .gitignore
+?? dtwo/
+EOF
+       test_cmp ../status.expect ../actual &&
+       cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 0
+opendir: 1
+EOF
+       test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'verify untracked cache dump' '
+       test-dump-untracked-cache >../actual &&
+       cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dtwo/
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+       test_cmp ../expect ../actual
+'
+
+test_done
index f768c900abd1cddc6d69a1bdeae897374e7d0dad..c6c44ec570dac66ca9a800ac687280cd3597e886 100755 (executable)
@@ -45,6 +45,14 @@ test_expect_success 'fast-forward pull succeeds with "true" in pull.ff' '
        test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
 '
 
+test_expect_success 'pull.ff=true overrides merge.ff=false' '
+       git reset --hard c0 &&
+       test_config merge.ff false &&
+       test_config pull.ff true &&
+       git pull . c1 &&
+       test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
+'
+
 test_expect_success 'fast-forward pull creates merge with "false" in pull.ff' '
        git reset --hard c0 &&
        test_config pull.ff false &&
diff --git a/test-dump-untracked-cache.c b/test-dump-untracked-cache.c
new file mode 100644 (file)
index 0000000..25d855d
--- /dev/null
@@ -0,0 +1,62 @@
+#include "cache.h"
+#include "dir.h"
+
+static int compare_untracked(const void *a_, const void *b_)
+{
+       const char *const *a = a_;
+       const char *const *b = b_;
+       return strcmp(*a, *b);
+}
+
+static int compare_dir(const void *a_, const void *b_)
+{
+       const struct untracked_cache_dir *const *a = a_;
+       const struct untracked_cache_dir *const *b = b_;
+       return strcmp((*a)->name, (*b)->name);
+}
+
+static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)
+{
+       int i, len;
+       qsort(ucd->untracked, ucd->untracked_nr, sizeof(*ucd->untracked),
+             compare_untracked);
+       qsort(ucd->dirs, ucd->dirs_nr, sizeof(*ucd->dirs),
+             compare_dir);
+       len = base->len;
+       strbuf_addf(base, "%s/", ucd->name);
+       printf("%s %s", base->buf,
+              sha1_to_hex(ucd->exclude_sha1));
+       if (ucd->recurse)
+               fputs(" recurse", stdout);
+       if (ucd->check_only)
+               fputs(" check_only", stdout);
+       if (ucd->valid)
+               fputs(" valid", stdout);
+       printf("\n");
+       for (i = 0; i < ucd->untracked_nr; i++)
+               printf("%s\n", ucd->untracked[i]);
+       for (i = 0; i < ucd->dirs_nr; i++)
+               dump(ucd->dirs[i], base);
+       strbuf_setlen(base, len);
+}
+
+int main(int ac, char **av)
+{
+       struct untracked_cache *uc;
+       struct strbuf base = STRBUF_INIT;
+       setup_git_directory();
+       if (read_cache() < 0)
+               die("unable to read index file");
+       uc = the_index.untracked;
+       if (!uc) {
+               printf("no untracked cache\n");
+               return 0;
+       }
+       printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1));
+       printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1));
+       printf("exclude_per_dir %s\n", uc->exclude_per_dir);
+       printf("flags %08x\n", uc->dir_flags);
+       if (uc->root)
+               dump(uc->root, &base);
+       return 0;
+}
index 5dd9a718047bc14e9ae840a46d1f71cb435fd6d7..6dccd2d5dd78e0ee71c1ebc771a99010a56db157 100644 (file)
@@ -415,6 +415,12 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
        return error;
 }
 
+struct dir_state {
+       void *tree;
+       unsigned long size;
+       unsigned char sha1[20];
+};
+
 static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
 {
        int namelen = strlen(name);
@@ -478,6 +484,206 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        return retval;
 }
 
+/*
+ * This is Linux's built-in max for the number of symlinks to follow.
+ * That limit, of course, does not affect git, but it's a reasonable
+ * choice.
+ */
+#define GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS 40
+
+/**
+ * Find a tree entry by following symlinks in tree_sha (which is
+ * assumed to be the root of the repository).  In the event that a
+ * symlink points outside the repository (e.g. a link to /foo or a
+ * root-level link to ../foo), the portion of the link which is
+ * outside the repository will be returned in result_path, and *mode
+ * will be set to 0.  It is assumed that result_path is uninitialized.
+ * If there are no symlinks, or the end result of the symlink chain
+ * points to an object inside the repository, result will be filled in
+ * with the sha1 of the found object, and *mode will hold the mode of
+ * the object.
+ *
+ * See the code for enum follow_symlink_result for a description of
+ * the return values.
+ */
+enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode)
+{
+       int retval = MISSING_OBJECT;
+       struct dir_state *parents = NULL;
+       size_t parents_alloc = 0;
+       ssize_t parents_nr = 0;
+       unsigned char current_tree_sha1[20];
+       struct strbuf namebuf = STRBUF_INIT;
+       struct tree_desc t;
+       int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS;
+       int i;
+
+       init_tree_desc(&t, NULL, 0UL);
+       strbuf_init(result_path, 0);
+       strbuf_addstr(&namebuf, name);
+       hashcpy(current_tree_sha1, tree_sha1);
+
+       while (1) {
+               int find_result;
+               char *first_slash;
+               char *remainder = NULL;
+
+               if (!t.buffer) {
+                       void *tree;
+                       unsigned char root[20];
+                       unsigned long size;
+                       tree = read_object_with_reference(current_tree_sha1,
+                                                         tree_type, &size,
+                                                         root);
+                       if (!tree)
+                               goto done;
+
+                       ALLOC_GROW(parents, parents_nr + 1, parents_alloc);
+                       parents[parents_nr].tree = tree;
+                       parents[parents_nr].size = size;
+                       hashcpy(parents[parents_nr].sha1, root);
+                       parents_nr++;
+
+                       if (namebuf.buf[0] == '\0') {
+                               hashcpy(result, root);
+                               retval = FOUND;
+                               goto done;
+                       }
+
+                       if (!size)
+                               goto done;
+
+                       /* descend */
+                       init_tree_desc(&t, tree, size);
+               }
+
+               /* Handle symlinks to e.g. a//b by removing leading slashes */
+               while (namebuf.buf[0] == '/') {
+                       strbuf_remove(&namebuf, 0, 1);
+               }
+
+               /* Split namebuf into a first component and a remainder */
+               if ((first_slash = strchr(namebuf.buf, '/'))) {
+                       *first_slash = 0;
+                       remainder = first_slash + 1;
+               }
+
+               if (!strcmp(namebuf.buf, "..")) {
+                       struct dir_state *parent;
+                       /*
+                        * We could end up with .. in the namebuf if it
+                        * appears in a symlink.
+                        */
+
+                       if (parents_nr == 1) {
+                               if (remainder)
+                                       *first_slash = '/';
+                               strbuf_add(result_path, namebuf.buf,
+                                          namebuf.len);
+                               *mode = 0;
+                               retval = FOUND;
+                               goto done;
+                       }
+                       parent = &parents[parents_nr - 1];
+                       free(parent->tree);
+                       parents_nr--;
+                       parent = &parents[parents_nr - 1];
+                       init_tree_desc(&t, parent->tree, parent->size);
+                       strbuf_remove(&namebuf, 0, remainder ? 3 : 2);
+                       continue;
+               }
+
+               /* We could end up here via a symlink to dir/.. */
+               if (namebuf.buf[0] == '\0') {
+                       hashcpy(result, parents[parents_nr - 1].sha1);
+                       retval = FOUND;
+                       goto done;
+               }
+
+               /* Look up the first (or only) path component in the tree. */
+               find_result = find_tree_entry(&t, namebuf.buf,
+                                             current_tree_sha1, mode);
+               if (find_result) {
+                       goto done;
+               }
+
+               if (S_ISDIR(*mode)) {
+                       if (!remainder) {
+                               hashcpy(result, current_tree_sha1);
+                               retval = FOUND;
+                               goto done;
+                       }
+                       /* Descend the tree */
+                       t.buffer = NULL;
+                       strbuf_remove(&namebuf, 0,
+                                     1 + first_slash - namebuf.buf);
+               } else if (S_ISREG(*mode)) {
+                       if (!remainder) {
+                               hashcpy(result, current_tree_sha1);
+                               retval = FOUND;
+                       } else {
+                               retval = NOT_DIR;
+                       }
+                       goto done;
+               } else if (S_ISLNK(*mode)) {
+                       /* Follow a symlink */
+                       unsigned long link_len;
+                       size_t len;
+                       char *contents, *contents_start;
+                       struct dir_state *parent;
+                       enum object_type type;
+
+                       if (follows_remaining-- == 0) {
+                               /* Too many symlinks followed */
+                               retval = SYMLINK_LOOP;
+                               goto done;
+                       }
+
+                       /*
+                        * At this point, we have followed at a least
+                        * one symlink, so on error we need to report this.
+                        */
+                       retval = DANGLING_SYMLINK;
+
+                       contents = read_sha1_file(current_tree_sha1, &type,
+                                                 &link_len);
+
+                       if (!contents)
+                               goto done;
+
+                       if (contents[0] == '/') {
+                               strbuf_addstr(result_path, contents);
+                               free(contents);
+                               *mode = 0;
+                               retval = FOUND;
+                               goto done;
+                       }
+
+                       if (remainder)
+                               len = first_slash - namebuf.buf;
+                       else
+                               len = namebuf.len;
+
+                       contents_start = contents;
+
+                       parent = &parents[parents_nr - 1];
+                       init_tree_desc(&t, parent->tree, parent->size);
+                       strbuf_splice(&namebuf, 0, len,
+                                     contents_start, link_len);
+                       if (remainder)
+                               namebuf.buf[link_len] = '/';
+                       free(contents);
+               }
+       }
+done:
+       for (i = 0; i < parents_nr; i++)
+               free(parents[i].tree);
+       free(parents);
+
+       strbuf_release(&namebuf);
+       return retval;
+}
+
 static int match_entry(const struct pathspec_item *item,
                       const struct name_entry *entry, int pathlen,
                       const char *match, int matchlen,
index ae7fb3a824a960c6c03efae799683cd7e18d402c..3b2f7bf17d37de5b475c415b41c6abb4281d5469 100644 (file)
@@ -40,6 +40,24 @@ struct traverse_info;
 typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
 int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);
 
+enum follow_symlinks_result {
+       FOUND = 0, /* This includes out-of-tree links */
+       MISSING_OBJECT = -1, /* The initial symlink is missing */
+       DANGLING_SYMLINK = -2, /*
+                               * The initial symlink is there, but
+                               * (transitively) points to a missing
+                               * in-tree file
+                               */
+       SYMLINK_LOOP = -3,
+       NOT_DIR = -4, /*
+                      * Somewhere along the symlink chain, a path is
+                      * requested which contains a file as a
+                      * non-final element.
+                      */
+};
+
+enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode);
+
 struct traverse_info {
        struct traverse_info *prev;
        struct name_entry name;
index be84ba2607ad2dbdb17397869c459418abad78e4..2927660d929eee776d43a87851a928df12b17716 100644 (file)
@@ -9,6 +9,7 @@
 #include "refs.h"
 #include "attr.h"
 #include "split-index.h"
+#include "dir.h"
 
 /*
  * Error messages expected by scripts out of plumbing commands such as
@@ -1259,8 +1260,10 @@ static int verify_uptodate_sparse(const struct cache_entry *ce,
 static void invalidate_ce_path(const struct cache_entry *ce,
                               struct unpack_trees_options *o)
 {
-       if (ce)
-               cache_tree_invalidate_path(o->src_index, ce->name);
+       if (!ce)
+               return;
+       cache_tree_invalidate_path(o->src_index, ce->name);
+       untracked_cache_invalidate_path(o->src_index, ce->name);
 }
 
 /*
index 1cb9a948aa05ac4ba274c1e729e64bdf09c483ea..89e832b64a0548ec79802dfc6911eff9f5c353be 100644 (file)
@@ -35,7 +35,11 @@ static int multi_ack;
 static int no_done;
 static int use_thin_pack, use_ofs_delta, use_include_tag;
 static int no_progress, daemon_mode;
-static int allow_tip_sha1_in_want;
+/* Allow specifying sha1 if it is a ref tip. */
+#define ALLOW_TIP_SHA1 01
+/* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */
+#define ALLOW_REACHABLE_SHA1   02
+static unsigned int allow_unadvertised_object_request;
 static int shallow_nr;
 static struct object_array have_obj;
 static struct object_array want_obj;
@@ -442,8 +446,9 @@ static int get_common_commits(void)
 
 static int is_our_ref(struct object *o)
 {
-       return o->flags &
-               ((allow_tip_sha1_in_want ? HIDDEN_REF : 0) | OUR_REF);
+       int allow_hidden_ref = (allow_unadvertised_object_request &
+                       (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
+       return o->flags & ((allow_hidden_ref ? HIDDEN_REF : 0) | OUR_REF);
 }
 
 static void check_non_tip(void)
@@ -456,8 +461,12 @@ static void check_non_tip(void)
        char namebuf[42]; /* ^ + SHA-1 + LF */
        int i;
 
-       /* In the normal in-process case non-tip request can never happen */
-       if (!stateless_rpc)
+       /*
+        * In the normal in-process case without
+        * uploadpack.allowReachableSHA1InWant,
+        * non-tip requests can never happen.
+        */
+       if (!stateless_rpc && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
                goto error;
 
        cmd.argv = argv;
@@ -726,10 +735,13 @@ static int send_ref(const char *refname, const struct object_id *oid,
                struct strbuf symref_info = STRBUF_INIT;
 
                format_symref_info(&symref_info, cb_data);
-               packet_write(1, "%s %s%c%s%s%s%s agent=%s\n",
+               packet_write(1, "%s %s%c%s%s%s%s%s agent=%s\n",
                             oid_to_hex(oid), refname_nons,
                             0, capabilities,
-                            allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
+                            (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ?
+                                    " allow-tip-sha1-in-want" : "",
+                            (allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1) ?
+                                    " allow-reachable-sha1-in-want" : "",
                             stateless_rpc ? " no-done" : "",
                             symref_info.buf,
                             git_user_agent_sanitized());
@@ -789,9 +801,17 @@ static void upload_pack(void)
 
 static int upload_pack_config(const char *var, const char *value, void *unused)
 {
-       if (!strcmp("uploadpack.allowtipsha1inwant", var))
-               allow_tip_sha1_in_want = git_config_bool(var, value);
-       else if (!strcmp("uploadpack.keepalive", var)) {
+       if (!strcmp("uploadpack.allowtipsha1inwant", var)) {
+               if (git_config_bool(var, value))
+                       allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
+               else
+                       allow_unadvertised_object_request &= ~ALLOW_TIP_SHA1;
+       } else if (!strcmp("uploadpack.allowreachablesha1inwant", var)) {
+               if (git_config_bool(var, value))
+                       allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
+               else
+                       allow_unadvertised_object_request &= ~ALLOW_REACHABLE_SHA1;
+       } else if (!strcmp("uploadpack.keepalive", var)) {
                keepalive = git_config_int(var, value);
                if (!keepalive)
                        keepalive = -1;
index 38cb165f124d610c789a8d82de42281c795111f7..c56c78fb6f6947a61a1d54b15da53ad967cc7d38 100644 (file)
@@ -585,6 +585,8 @@ static void wt_status_collect_untracked(struct wt_status *s)
                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
        if (s->show_ignored_files)
                dir.flags |= DIR_SHOW_IGNORED_TOO;
+       else
+               dir.untracked = the_index.untracked;
        setup_standard_excludes(&dir);
 
        fill_directory(&dir, &s->pathspec);
@@ -1532,21 +1534,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
 
        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
 
-       switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
-       case 0:
-               /* no base */
-               fputc(s->null_termination ? '\0' : '\n', s->fp);
-               return;
-       case -1:
-               /* with "gone" base */
+       if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
+               if (!base) {
+                       fputc(s->null_termination ? '\0' : '\n', s->fp);
+                       return;
+               }
+
                upstream_is_gone = 1;
-               break;
-       default:
-               /* with base */
-               break;
        }
 
-       base = branch->merge[0]->dst;
        base = shorten_unambiguous_ref(base, 0);
        color_fprintf(s->fp, header_color, "...");
        color_fprintf(s->fp, branch_color_remote, "%s", base);