Merge branch 'db/doc-config-section-names-with-bs'
authorJunio C Hamano <gitster@pobox.com>
Fri, 5 Jan 2018 21:28:09 +0000 (13:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 5 Jan 2018 21:28:09 +0000 (13:28 -0800)
Doc update.

* db/doc-config-section-names-with-bs:
config.txt: document behavior of backslashes in subsections

345 files changed:
.travis.yml
Documentation/Makefile
Documentation/RelNotes/2.16.0.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout.txt
Documentation/git-clone.txt
Documentation/git-config.txt
Documentation/git-describe.txt
Documentation/git-for-each-ref.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-merge.txt
Documentation/git-notes.txt
Documentation/git-pack-objects.txt
Documentation/git-prune.txt
Documentation/git-push.txt
Documentation/git-rebase.txt
Documentation/git-reflog.txt
Documentation/git-rev-list.txt
Documentation/git-send-email.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-update-index.txt
Documentation/git-worktree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/githooks.txt
Documentation/gitrepository-layout.txt
Documentation/gitworkflows.txt
Documentation/install-doc-quick.sh
Documentation/merge-config.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
Documentation/pretty-formats.txt
Documentation/rebase-config.txt [new file with mode: 0644]
Documentation/rev-list-options.txt
Documentation/revisions.txt
Documentation/technical/api-decorate.txt [deleted file]
Documentation/technical/api-directory-listing.txt
Documentation/technical/http-protocol.txt
Documentation/technical/index-format.txt
Documentation/technical/pack-protocol.txt
Documentation/technical/partial-clone.txt [new file with mode: 0644]
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
advice.c
advice.h
apply.c
apply.h
archive.c
bisect.c
bisect.h
blame.c
branch.c
branch.h
builtin/add.c
builtin/am.c
builtin/apply.c
builtin/bisect--helper.c
builtin/blame.c
builtin/branch.c
builtin/checkout-index.c
builtin/checkout.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/describe.c
builtin/diff-tree.c
builtin/diff.c
builtin/difftool.c
builtin/fast-export.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/fsck.c
builtin/grep.c
builtin/index-pack.c
builtin/log.c
builtin/ls-files.c
builtin/merge-base.c
builtin/merge-ours.c
builtin/merge.c
builtin/mv.c
builtin/notes.c
builtin/pack-objects.c
builtin/prune.c
builtin/pull.c
builtin/push.c
builtin/rebase--helper.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/replace.c
builtin/reset.c
builtin/rev-list.c
builtin/rev-parse.c
builtin/rm.c
builtin/show-branch.c
builtin/show-ref.c
builtin/submodule--helper.c
builtin/symbolic-ref.c
builtin/tag.c
builtin/update-index.c
builtin/update-ref.c
builtin/worktree.c
bundle.c
cache-tree.c
cache.h
checkout.c [new file with mode: 0644]
checkout.h [new file with mode: 0644]
ci/install-dependencies.sh
ci/lib-travisci.sh
color.c
combine-diff.c
commit.h
compat/bswap.h
config.c
config.h
connect.c
connected.c
connected.h
contrib/completion/git-completion.bash
contrib/completion/git-prompt.sh
contrib/git-jump/README
contrib/git-jump/git-jump
contrib/mw-to-git/Git/Mediawiki.pm
contrib/mw-to-git/git-remote-mediawiki.perl
convert.c
daemon.c
decorate.c
decorate.h
diff-lib.c
diff-no-index.c
diff.c
diff.h
diffcore-pickaxe.c
diffcore-rename.c
dir.c
dir.h
editor.c
entry.c
environment.c
fast-import.c
fetch-pack.c
fsmonitor.c [new file with mode: 0644]
fsmonitor.h [new file with mode: 0644]
generate-cmdlist.sh
git-bisect.sh
git-compat-util.h
git-cvsimport.perl
git-gui/Makefile
git-gui/lib/commit.tcl
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git-stash.sh
git-submodule.sh
git-svn.perl
git.c
gitweb/gitweb.perl
grep.c
hash.h
hashmap.h
help.c
hex.c
http-push.c
http.c
imap-send.c
list-objects-filter-options.c [new file with mode: 0644]
list-objects-filter-options.h [new file with mode: 0644]
list-objects-filter.c [new file with mode: 0644]
list-objects-filter.h [new file with mode: 0644]
list-objects.c
list-objects.h
lockfile.h
log-tree.c
log-tree.h
merge-recursive.c
merge.c
notes-cache.c
notes-merge.c
notes-utils.c
notes.c
object.h
oidmap.h
oidset.c
oidset.h
pack-bitmap.c
pack-bitmap.h
patch-ids.c
path.h
pathspec.c
pathspec.h
perl/Git/Packet.pm [new file with mode: 0644]
perl/Makefile
pkt-line.c
pkt-line.h
preload-index.c
pretty.c
pretty.h [new file with mode: 0644]
progress.c
progress.h
protocol.c [new file with mode: 0644]
protocol.h [new file with mode: 0644]
read-cache.c
ref-filter.c
reflog-walk.c
refs.c
refs.h
refs/files-backend.c
refs/packed-backend.c
refs/ref-cache.c
refs/refs-internal.h
remote-testsvn.c
remote.c
remote.h
repository.c
repository.h
revision.c
revision.h
run-command.c
sequencer.c
sequencer.h
setup.c
sha1_file.c
sha1_name.c
sideband.c
strbuf.c
strbuf.h
submodule-config.h
submodule.c
submodule.h
t/Makefile
t/README
t/helper/.gitignore
t/helper/test-date.c
t/helper/test-drop-caches.c [new file with mode: 0644]
t/helper/test-dump-fsmonitor.c [new file with mode: 0644]
t/helper/test-example-decorate.c [new file with mode: 0644]
t/helper/test-ref-store.c
t/interop/i5700-protocol-transition.sh [new file with mode: 0755]
t/lib-git-svn.sh
t/lib-httpd/apache.conf
t/lib-submodule-update.sh
t/perf/aggregate.perl
t/perf/lib-pack.sh [new file with mode: 0644]
t/perf/p4211-line-log.sh
t/perf/p5550-fetch-tags.sh
t/perf/p5551-fetch-rescan.sh [new file with mode: 0755]
t/perf/p7519-fsmonitor.sh [new file with mode: 0755]
t/perf/perf-lib.sh
t/perf/run
t/t0000-basic.sh
t/t0021/rot13-filter.pl
t/t0025-crlf-renormalize.sh [new file with mode: 0755]
t/t0027-auto-crlf.sh
t/t1300-repo-config.sh
t/t1430-bad-ref-name.sh
t/t1700-split-index.sh
t/t2020-checkout-detach.sh
t/t2025-worktree-add.sh
t/t3040-subprojects-basic.sh
t/t3404-rebase-interactive.sh
t/t3600-rm.sh
t/t3700-add.sh
t/t3903-stash.sh
t/t4001-diff-rename.sh
t/t4013-diff-various.sh
t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--raw_initial [new file with mode: 0644]
t/t4013/diff.noellipses-show_--patch-with-raw_side [new file with mode: 0644]
t/t4013/diff.noellipses-whatchanged_--root_master [new file with mode: 0644]
t/t4013/diff.noellipses-whatchanged_-SF_master [new file with mode: 0644]
t/t4013/diff.noellipses-whatchanged_master [new file with mode: 0644]
t/t4015-diff-whitespace.sh
t/t4045-diff-relative.sh
t/t4051-diff-function-context.sh
t/t4051/hello.c
t/t4065-diff-anchored.sh [new file with mode: 0755]
t/t4202-log.sh
t/t4208-log-magic-pathspec.sh
t/t5317-pack-objects-filter-objects.sh [new file with mode: 0755]
t/t5521-pull-options.sh
t/t5526-fetch-submodules.sh
t/t5545-push-options.sh
t/t5573-pull-verify-signatures.sh [new file with mode: 0755]
t/t5601-clone.sh
t/t5603-clone-dirname.sh
t/t5615-alternate-env.sh
t/t5700-protocol-v1.sh [new file with mode: 0755]
t/t6030-bisect-porcelain.sh
t/t6100-rev-list-in-order.sh [new file with mode: 0755]
t/t6112-rev-list-filters-objects.sh [new file with mode: 0755]
t/t6120-describe.sh
t/t6300-for-each-ref.sh
t/t7006-pager.sh
t/t7519-status-fsmonitor.sh [new file with mode: 0755]
t/t7519/fsmonitor-all [new file with mode: 0755]
t/t7519/fsmonitor-none [new file with mode: 0755]
t/t7519/fsmonitor-watchman [new file with mode: 0755]
t/t7520-ignored-hook-warning.sh [new file with mode: 0755]
t/t7521-ignored-mode.sh [new file with mode: 0755]
t/t7612-merge-verify-signatures.sh
t/t7810-grep.sh
t/t9004-example.sh [new file with mode: 0755]
t/t9169-git-svn-dcommit-crlf.sh [new file with mode: 0755]
t/t9300-fast-import.sh
t/test-lib.sh
tempfile.h
templates/hooks--fsmonitor-watchman.sample [new file with mode: 0755]
trace.c
trace.h
transport-helper.c
transport-internal.h [new file with mode: 0644]
transport.c
transport.h
tree-diff.c
tree-walk.c
unpack-trees.c
upload-pack.c
version.c
version.h
walker.c
worktree.c
worktree.h
wt-status.c
wt-status.h
xdiff/xdiff.h
xdiff/xemit.c
xdiff/xpatience.c
xdiff/xutils.c
index 281f101f3154e3884e71cde6184b59fd2b21c154..7c9aa0557eccad0f04885ba97646d3111d0d4b27 100644 (file)
@@ -21,30 +21,14 @@ addons:
     - git-svn
     - apache2
 
-env:
-  global:
-    - DEVELOPER=1
-    # The Linux build installs the defined dependency versions below.
-    # The OS X build installs the latest available versions. Keep that
-    # in mind when you encounter a broken OS X build!
-    - LINUX_P4_VERSION="16.2"
-    - LINUX_GIT_LFS_VERSION="1.5.2"
-    - DEFAULT_TEST_TARGET=prove
-    - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
-    - GIT_TEST_OPTS="--verbose-log"
-    - GIT_TEST_CLONE_2GB=YesPlease
-    # t9810 occasionally fails on Travis CI OS X
-    # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X
-    - GIT_SKIP_TESTS="t9810 t9816"
-
 matrix:
   include:
-    - env: GETTEXT_POISON=YesPlease
+    - env: jobname=GETTEXT_POISON
       os: linux
       compiler:
       addons:
       before_install:
-    - env: Windows
+    - env: jobname=Windows
       os: linux
       compiler:
       addons:
@@ -55,7 +39,7 @@ matrix:
           test "$TRAVIS_REPO_SLUG" != "git/git" ||
           ci/run-windows-build.sh $TRAVIS_BRANCH $(git rev-parse HEAD)
       after_failure:
-    - env: Linux32
+    - env: jobname=Linux32
       os: linux
       compiler:
       services:
@@ -63,7 +47,7 @@ matrix:
       before_install:
       before_script:
       script: ci/run-linux32-docker.sh
-    - env: Static Analysis
+    - env: jobname=StaticAnalysis
       os: linux
       compiler:
       addons:
@@ -74,7 +58,7 @@ matrix:
       before_script:
       script: ci/run-static-analysis.sh
       after_failure:
-    - env: Documentation
+    - env: jobname=Documentation
       os: linux
       compiler:
       addons:
index 2ab65561af628820f862a48ac05246cc9e773ee8..4ae9ba5c86e038fff94277c0d1a767b93634ec53 100644 (file)
@@ -39,6 +39,7 @@ MAN7_TXT += gitworkflows.txt
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT))
 MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
+GIT_MAN_REF = master
 
 OBSOLETE_HTML += everyday.html
 OBSOLETE_HTML += git-remote-helpers.html
@@ -437,14 +438,14 @@ require-manrepo::
        then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi
 
 quick-install-man: require-manrepo
-       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir)
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir) $(GIT_MAN_REF)
 
 require-htmlrepo::
        @if test ! -d $(HTML_REPO); \
        then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi
 
 quick-install-html: require-htmlrepo
-       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir)
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir) $(GIT_MAN_REF)
 
 print-man1:
        @for i in $(MAN1_TXT); do echo $$i; done
diff --git a/Documentation/RelNotes/2.16.0.txt b/Documentation/RelNotes/2.16.0.txt
new file mode 100644 (file)
index 0000000..8f0461e
--- /dev/null
@@ -0,0 +1,458 @@
+Git 2.16 Release Notes
+======================
+
+Backward compatibility notes and other notable changes.
+
+ * Use of an empty string as a pathspec element that is used for
+   'everything matches' is now an error.
+
+
+Updates since v2.15
+-------------------
+
+UI, Workflows & Features
+
+ * An empty string as a pathspec element that means "everything"
+   i.e. 'git add ""', is now illegal.  We started this by first
+   deprecating and warning a pathspec that has such an element in
+   2.11 (Nov 2016).
+
+ * A hook script that is set unexecutable is simply ignored.  Git
+   notifies when such a file is ignored, unless the message is
+   squelched via advice.ignoredHook configuration.
+
+ * "git pull" has been taught to accept "--[no-]signoff" option and
+   pass it down to "git merge".
+
+ * The "--push-option=<string>" option to "git push" now defaults to a
+   list of strings configured via push.pushOption variable.
+
+ * "gitweb" checks if a directory is searchable with Perl's "-x"
+   operator, which can be enhanced by using "filetest 'access'"
+   pragma, which now we do.
+
+ * "git stash save" has been deprecated in favour of "git stash push".
+
+ * The set of paths output from "git status --ignored" was tied
+   closely with its "--untracked=<mode>" option, but now it can be
+   controlled more flexibly.  Most notably, a directory that is
+   ignored because it is listed to be ignored in the ignore/exclude
+   mechanism can be handled differently from a directory that ends up
+   to be ignored only because all files in it are ignored.
+
+ * The remote-helper for talking to MediaWiki has been updated to
+   truncate an overlong pagename so that ".mw" suffix can still be
+   added.
+
+ * The remote-helper for talking to MediaWiki has been updated to
+   work with mediawiki namespaces.
+
+ * The "--format=..." option "git for-each-ref" takes learned to show
+   the name of the 'remote' repository and the ref at the remote side
+   that is affected for 'upstream' and 'push' via "%(push:remotename)"
+   and friends.
+
+ * Doc and message updates to teach users "bisect view" is a synonym
+   for "bisect visualize".
+
+ * "git bisect run" that did not specify any command to run used to go
+   ahead and treated all commits to be tested as 'good'.  This has
+   been corrected by making the command error out.
+
+ * The SubmittingPatches document has been converted to produce an
+   HTML version via AsciiDoc/Asciidoctor.
+
+ * We learned to talk to watchman to speed up "git status" and other
+   operations that need to see which paths have been modified.
+
+ * The "diff" family of commands learned to ignore differences in
+   carriage return at the end of line.
+
+ * Places that know about "sendemail.to", like documentation and shell
+   completion (in contrib/) have been taught about "sendemail.tocmd",
+   too.
+
+ * "git add --renormalize ." is a new and safer way to record the fact
+   that you are correcting the end-of-line convention and other
+   "convert_to_git()" glitches in the in-repository data.
+
+ * "git branch" and "git checkout -b" are now forbidden from creating
+   a branch whose name is "HEAD".
+
+ * "git branch --list" learned to show its output through the pager by
+   default when the output is going to a terminal, which is controlled
+   by the pager.branch configuration variable.  This is similar to a
+   recent change to "git tag --list".
+
+ * "git grep -W", "git diff -W" and their friends learned a heuristic
+   to extend a pre-context beyond the line that matches the "function
+   pattern" (aka "diff.*.xfuncname") to include a comment block, if
+   exists, that immediately precedes it.
+
+ * "git config --expiry-date gc.reflogexpire" can read "2.weeks" from
+   the configuration and report it as a timestamp, just like "--int"
+   would read "1k" and report 1024, to help consumption by scripts.
+
+ * The shell completion (in contrib/) learned that "git pull" can take
+   the "--autostash" option.
+
+ * The tagnames "git log --decorate" uses to annotate the commits can
+   now be limited to subset of available refs with the two additional
+   options, --decorate-refs[-exclude]=<pattern>.
+
+ * "git grep" compiled with libpcre2 sometimes triggered a segfault,
+   which is being fixed.
+
+ * "git send-email" tries to see if the sendmail program is available
+   in /usr/lib and /usr/sbin; extend the list of locations to be
+   checked to also include directories on $PATH.
+
+ * "git diff" learned, "--anchored", a variant of the "--patience"
+   algorithm, to which the user can specify which 'unique' line to be
+   used as anchoring points.
+
+ * The way "git worktree add" determines what branch to create from
+   where and checkout in the new worktree has been updated a bit.
+
+ * Ancient part of codebase still shows dots after an abbreviated
+   object name just to show that it is not a full object name, but
+   these ellipses are confusing to people who newly discovered Git
+   who are used to seeing abbreviated object names and find them
+   confusing with the range syntax.
+
+ * With a configuration variable rebase.abbreviateCommands set,
+   "git rebase -i" produces the todo list with a single-letter
+   command names.
+
+ * "git worktree add" learned to run the post-checkout hook, just like
+   "git checkout" does, after the initial checkout.
+
+ * "git svn" has been updated to strip CRs in the commit messages, as
+   recent versions of Subversion rejects them.
+
+ * "git imap-send" did not correctly quote the folder name when
+   making a request to the server, which has been corrected.
+
+ * Error messages from "git rebase" have been somewhat cleaned up.
+
+ * Git has been taught to support an https:// URL used for http.proxy
+   when using recent versions of libcurl.
+
+ * "git merge" learned to pay attention to merge.verifySignatures
+   configuration variable and pretend as if '--verify-signatures'
+   option was given from the command line.
+
+ * "git describe" was taught to dig trees deeper to find a
+   <commit-ish>:<path> that refers to a given blob object.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * An earlier update made it possible to use an on-stack in-core
+   lockfile structure (as opposed to having to deliberately leak an
+   on-heap one).  Many codepaths have been updated to take advantage
+   of this new facility.
+
+ * Calling cmd_foo() as if it is a general purpose helper function is
+   a no-no.  Correct two instances of such to set an example.
+
+ * We try to see if somebody runs our test suite with a shell that
+   does not support "local" like bash/dash does.
+
+ * An early part of piece-by-piece rewrite of "git bisect" in C.
+
+ * GSoC to piece-by-piece rewrite "git submodule" in C.
+
+ * Optimize the code to find shortest unique prefix of object names.
+
+ * Pathspec-limited revision traversal was taught not to keep finding
+   unneeded differences once it knows two trees are different inside
+   given pathspec.
+
+ * Conversion from uchar[20] to struct object_id continues.
+
+ * Code cleanup.
+
+ * A single-word "unsigned flags" in the diff options is being split
+   into a structure with many bitfields.
+
+ * TravisCI build updates.
+
+ * Parts of a test to drive the long-running content filter interface
+   has been split into its own module, hopefully to eventually become
+   reusable.
+
+ * Drop (perhaps overly cautious) sanity check before using the index
+   read from the filesystem at runtime.
+
+ * The build procedure has been taught to avoid some unnecessary
+   instability in the build products.
+
+ * A new mechanism to upgrade the wire protocol in place is proposed
+   and demonstrated that it works with the older versions of Git
+   without harming them.
+
+ * An infrastructure to define what hash function is used in Git is
+   introduced, and an effort to plumb that throughout various
+   codepaths has been started.
+
+ * The code to iterate over loose object files got optimized.
+
+ * An internal function that was left for backward compatibility has
+   been removed, as there is no remaining callers.
+
+ * Historically, the diff machinery for rename detection had a
+   hardcoded limit of 32k paths; this is being lifted to allow users
+   trade cycles with a (possibly) easier to read result.
+
+ * The tracing infrastructure has been optimized for cases where no
+   tracing is requested.
+
+ * In preparation for implementing narrow/partial clone, the object
+   walking machinery has been taught a way to tell it to "filter" some
+   objects from enumeration.
+
+ * A few structures and variables that are implementation details of
+   the decorate API have been renamed and then the API got documented
+   better.
+
+ * Assorted updates for TravisCI integration.
+   (merge 4f26366679 sg/travis-fixes later to maint).
+
+ * Introduce a helper to simplify code to parse a common pattern that
+   expects either "--key" or "--key=<something>".
+
+ * "git version --build-options" learned to report the host CPU and
+   the exact commit object name the binary was built from.
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.15
+-----------------
+
+ * "auto" as a value for the columnar output configuration ought to
+   judge "is the output consumed by humans?" with the same criteria as
+   "auto" for coloured output configuration, i.e. either the standard
+   output stream is going to tty, or a pager is in use.  We forgot the
+   latter, which has been fixed.
+
+ * The experimental "color moved lines differently in diff output"
+   feature was buggy around "ignore whitespace changes" edges, which
+   has been corrected.
+
+ * Instead of using custom line comparison and hashing functions to
+   implement "moved lines" coloring in the diff output, use the pair
+   of these functions from lower-layer xdiff/ code.
+
+ * Some codepaths did not check for errors when asking what branch the
+   HEAD points at, which have been fixed.
+
+ * "git commit", after making a commit, did not check for errors when
+   asking on what branch it made the commit, which has been corrected.
+
+ * "git status --ignored -u" did not stop at a working tree of a
+   separate project that is embedded in an ignored directory and
+   listed files in that other project, instead of just showing the
+   directory itself as ignored.
+
+ * A broken access to object databases in recent update to "git grep
+   --recurse-submodules" has been fixed.
+
+ * A recent regression in "git rebase -i" that broke execution of git
+   commands from subdirectories via "exec" instruction has been fixed.
+
+ * A (possibly flakey) test fix.
+
+ * "git check-ref-format --branch @{-1}" bit a "BUG()" when run
+   outside a repository for obvious reasons; clarify the documentation
+   and make sure we do not even try to expand the at-mark magic in
+   such a case, but still call the validation logic for branch names.
+
+ * "git fetch --recurse-submodules" now knows that submodules can be
+   moved around in the superproject in addition to getting updated,
+   and finds the ones that need to be fetched accordingly.
+
+ * Command line completion (in contrib/) update.
+
+ * Description of blame.{showroot,blankboundary,showemail,date}
+   configuration variables have been added to "git config --help".
+
+ * After an error from lstat(), diff_populate_filespec() function
+   sometimes still went ahead and used invalid data in struct stat,
+   which has been fixed.
+
+ * UNC paths are also relevant in Cygwin builds and they are now
+   tested just like Mingw builds.
+
+ * Correct start-up sequence so that a repository could be placed
+   immediately under the root directory again (which was broken at
+   around Git 2.13).
+
+ * The credential helper for libsecret (in contrib/) has been improved
+   to allow possibly prompting the end user to unlock secrets that are
+   currently locked (otherwise the secrets may not be loaded).
+
+ * MinGW updates.
+
+ * Error checking in "git imap-send" for empty response has been
+   improved.
+
+ * Recent update to the refs infrastructure implementation started
+   rewriting packed-refs file more often than before; this has been
+   optimized again for most trivial cases.
+
+ * Some error messages did not quote filenames shown in it, which have
+   been fixed.
+
+ * "git rebase -i" recently started misbehaving when a submodule that
+   is configured with 'submodule.<name>.ignore' is dirty; this has
+   been corrected.
+
+ * Building with NO_LIBPCRE1_JIT did not disable it, which has been fixed.
+
+ * We used to add an empty alternate object database to the system
+   that does not help anything; it has been corrected.
+
+ * Doc update around use of "format-patch --subject-prefix" etc.
+
+ * A fix for an ancient bug in "git apply --ignore-space-change" codepath.
+
+ * Clarify and enhance documentation for "merge-base --fork-point", as
+   it was clear what it computed but not why/what for.
+
+ * A few scripts (both in production and tests) incorrectly redirected
+   their error output.  These have been corrected.
+
+ * "git notes" sent its error message to its standard output stream,
+   which was corrected.
+
+ * The three-way merge performed by "git cherry-pick" was confused
+   when a new submodule was added in the meantime, which has been
+   fixed (or "papered over").
+
+ * The sequencer machinery (used by "git cherry-pick A..B", and "git
+   rebase -i", among other things) would have lost a commit if stopped
+   due to an unlockable index file, which has been fixed.
+
+ * "git apply --inaccurate-eof" when used with "--ignore-space-change"
+   triggered an internal sanity check, which has been fixed.
+
+ * Command line completion (in contrib/) has been taught about the
+   "--copy" option of "git branch".
+
+ * When "git rebase" prepared an mailbox of changes and fed it to "git
+   am" to replay them, it was confused when a stray "From " happened
+   to be in the log message of one of the replayed changes.  This has
+   been corrected.
+
+ * There was a recent semantic mismerge in the codepath to write out a
+   section of a configuration section, which has been corrected.
+
+ * Mentions of "git-rebase" and "git-am" (dashed form) still remained
+   in end-user visible strings emitted by the "git rebase" command;
+   they have been corrected.
+
+ * Contrary to the documentation, "git pull -4/-6 other-args" did not
+   ask the underlying "git fetch" to go over IPv4/IPv6, which has been
+   corrected.
+
+ * "git checkout --recursive" may overwrite and rewind the history of
+   the branch that happens to be checked out in submodule
+   repositories, which might not be desirable.  Detach the HEAD but
+   still allow the recursive checkout to succeed in such a case.
+   (merge 57f22bf997 sb/submodule-recursive-checkout-detach-head later to maint).
+
+ * "git branch --set-upstream" has been deprecated and (sort of)
+   removed, as "--set-upstream-to" is the preferred one these days.
+   The documentation still had "--set-upstream" listed on its
+   synopsis section, which has been corrected.
+   (merge a060f3d3d8 tz/branch-doc-remove-set-upstream later to maint).
+
+ * Internally we use 0{40} as a placeholder object name to signal the
+   codepath that there is no such object (e.g. the fast-forward check
+   while "git fetch" stores a new remote-tracking ref says "we know
+   there is no 'old' thing pointed at by the ref, as we are creating
+   it anew" by passing 0{40} for the 'old' side), and expect that a
+   codepath to locate an in-core object to return NULL as a sign that
+   the object does not exist.  A look-up for an object that does not
+   exist however is quite costly with a repository with large number
+   of packfiles.  This access pattern has been optimized.
+   (merge 87b5e236a1 jk/fewer-pack-rescan later to maint).
+
+ * In addition to "git stash -m message", the command learned to
+   accept "git stash -mmessage" form.
+   (merge 5675473fcb ph/stash-save-m-option-fix later to maint).
+
+ * @{-N} in "git checkout @{-N}" may refer to a detached HEAD state,
+   but the documentation was not clear about it, which has been fixed.
+   (merge 75ce149575 ks/doc-checkout-previous later to maint).
+
+ * A regression in the progress eye-candy was fixed.
+   (merge 9c5951cacf jk/progress-delay-fix later to maint).
+
+ * The code internal to the recursive merge strategy was not fully
+   prepared to see a path that is renamed to try overwriting another
+   path that is only different in case on case insensitive systems.
+   This does not matter in the current code, but will start to matter
+   once the rename detection logic starts taking hints from nearby
+   paths moving to some directory and moves a new path along with them.
+   (merge 4cba2b0108 en/merge-recursive-icase-removal later to maint).
+
+ * An v2.12-era regression in pathspec match logic, which made it look
+   into submodule tree even when it is not desired, has been fixed.
+   (merge eef3df5a93 bw/pathspec-match-submodule-boundary later to maint).
+
+ * Amending commits in git-gui broke the author name that is non-ascii
+   due to incorrect enconding conversion.
+
+ * Recent update to the submodule configuration code broke "diff-tree"
+   by accidentally stopping to read from the index upfront.
+   (merge fd66bcc31f bw/submodule-config-cleanup later to maint).
+
+ * Git shows a message to tell the user that it is waiting for the
+   user to finish editing when spawning an editor, in case the editor
+   opens to a hidden window or somewhere obscure and the user gets
+   lost.
+   (merge abfb04d0c7 ls/editor-waiting-message later to maint).
+
+ * The "safe crlf" check incorrectly triggered for contents that does
+   not use CRLF as line endings, which has been corrected.
+   (merge 649f1f0948 tb/check-crlf-for-safe-crlf later to maint).
+
+ * "git clone --shared" to borrow from a (secondary) worktree did not
+   work, even though "git clone --local" did.  Both are now accepted.
+   (merge b3b05971c1 es/clone-shared-worktree later to maint).
+
+ * The build procedure now allows not just the repositories but also
+   the refs to be used to take pre-formatted manpages and html
+   documents to install.
+   (merge 65289e9dcd rb/quick-install-doc later to maint).
+
+ * Update the shell prompt script (in contrib/) to strip trailing CR
+   from strings read from various "state" files.
+   (merge 041fe8fc83 ra/prompt-eread-fix later to maint).
+
+ * Other minor doc, test and build updates and code cleanups.
+   (merge 1a1fc2d5b5 rd/man-prune-progress later to maint).
+   (merge 0ba014035a rd/man-reflog-add-n later to maint).
+   (merge e54b63359f rd/doc-notes-prune-fix later to maint).
+   (merge ff4c9b413a sp/doc-info-attributes later to maint).
+   (merge 7db2cbf4f1 jc/receive-pack-hook-doc later to maint).
+   (merge 5a0526264b tg/t-readme-updates later to maint).
+   (merge 5e83cca0b8 jk/no-optional-locks later to maint).
+   (merge 826c778f7c js/hashmap-update-sample later to maint).
+   (merge 176b2d328c sg/setup-doc-update later to maint).
+   (merge 1b09073514 rs/am-builtin-leakfix later to maint).
+   (merge addcf6cfde rs/fmt-merge-msg-string-leak-fix later to maint).
+   (merge c3ff8f6c14 rs/strbuf-read-once-reset-length later to maint).
+   (merge 6b0eb884f9 db/doc-workflows-neuter-the-maintainer later to maint).
+   (merge 8c87bdfb21 jk/cvsimport-quoting later to maint).
+   (merge 176cb979fe rs/fmt-merge-msg-leakfix later to maint).
+   (merge 5a03360e73 tb/delimit-pretty-trailers-args-with-comma later to maint).
+   (merge d0e6326026 ot/pretty later to maint).
+   (merge 44103f4197 sb/test-helper-excludes later to maint).
+   (merge 170078693f jt/transport-no-more-rsync later to maint).
+   (merge c07b3adff1 bw/path-doc later to maint).
+   (merge bf9d7df950 tz/lib-git-svn-svnserve-tests later to maint).
+   (merge dec366c9a8 sr/http-sslverify-config-doc later to maint).
index a63b329ff4e8360f60095793cc0335a5ab6ee51b..0e25b2c92b309330f27ce562e0f21f4e10179879 100644 (file)
@@ -353,6 +353,12 @@ advice.*::
        addEmbeddedRepo::
                Advice on what to do when you've accidentally added one
                git repo inside of another.
+       ignoredHook::
+               Advice shown if an hook is ignored because the hook is not
+               set as executable.
+       waitingForEditor::
+               Print a message to the terminal whenever Git is waiting for
+               editor input from the user.
 --
 
 core.fileMode::
@@ -415,6 +421,13 @@ core.protectNTFS::
        8.3 "short" names.
        Defaults to `true` on Windows, and `false` elsewhere.
 
+core.fsmonitor::
+       If set, the value of this variable is used as a command which
+       will identify all files that may have changed since the
+       requested date/time. This information is used to speed up git by
+       avoiding unnecessary processing of files that have not changed.
+       See the "fsmonitor-watchman" section of linkgit:githooks[5].
+
 core.trustctime::
        If false, the ctime differences between the index and the
        working tree are ignored; useful when the inode change time
@@ -1957,8 +1970,8 @@ empty string.
 
 http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
-       over HTTPS. Can be overridden by the `GIT_SSL_NO_VERIFY` environment
-       variable.
+       over HTTPS. Defaults to true. Can be overridden by the
+       `GIT_SSL_NO_VERIFY` environment variable.
 
 http.sslCert::
        File containing the SSL certificate when fetching or pushing
@@ -2100,15 +2113,40 @@ matched against are those given directly to Git commands.  This means any URLs
 visited as a result of a redirection do not participate in matching.
 
 ssh.variant::
-       Depending on the value of the environment variables `GIT_SSH` or
-       `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git
-       auto-detects whether to adjust its command-line parameters for use
-       with plink or tortoiseplink, as opposed to the default (OpenSSH).
+       By default, Git determines the command line arguments to use
+       based on the basename of the configured SSH command (configured
+       using the environment variable `GIT_SSH` or `GIT_SSH_COMMAND` or
+       the config setting `core.sshCommand`). If the basename is
+       unrecognized, Git will attempt to detect support of OpenSSH
+       options by first invoking the configured SSH command with the
+       `-G` (print configuration) option and will subsequently use
+       OpenSSH options (if that is successful) or no options besides
+       the host and remote command (if it fails).
++
+The config variable `ssh.variant` can be set to override this detection.
+Valid values are `ssh` (to use OpenSSH options), `plink`, `putty`,
+`tortoiseplink`, `simple` (no options except the host and remote command).
+The default auto-detection can be explicitly requested using the value
+`auto`.  Any other value is treated as `ssh`.  This setting can also be
+overridden via the environment variable `GIT_SSH_VARIANT`.
++
+The current command-line parameters used for each variant are as
+follows:
 +
-The config variable `ssh.variant` can be set to override this auto-detection;
-valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value
-will be treated as normal ssh. This setting can be overridden via the
-environment variable `GIT_SSH_VARIANT`.
+--
+
+* `ssh` - [-p port] [-4] [-6] [-o option] [username@]host command
+
+* `simple` - [username@]host command
+
+* `plink` or `putty` - [-P port] [-4] [-6] [username@]host command
+
+* `tortoiseplink` - [-P port] [-4] [-6] -batch [username@]host command
+
+--
++
+Except for the `simple` variant, command-line parameters are likely to
+change as git gains new features.
 
 i18n.commitEncoding::
        Character encoding the commit messages are stored in; Git itself
@@ -2536,6 +2574,23 @@ The protocol names currently used by git are:
     `hg` to allow the `git-remote-hg` helper)
 --
 
+protocol.version::
+       Experimental. If set, clients will attempt to communicate with a
+       server using the specified protocol version.  If unset, no
+       attempt will be made by the client to communicate using a
+       particular protocol version, this results in protocol version 0
+       being used.
+       Supported versions:
++
+--
+
+* `0` - the original wire protocol.
+
+* `1` - the original wire protocol with the addition of a version string
+  in the initial response from the server.
+
+--
+
 pull.ff::
        By default, Git does not create an extra merge commit when merging
        a commit that is a descendant of the current commit. Instead, the
@@ -2640,6 +2695,35 @@ push.gpgSign::
        override a value from a lower-priority config file. An explicit
        command-line flag always overrides this config option.
 
+push.pushOption::
+       When no `--push-option=<option>` argument is given from the
+       command line, `git push` behaves as if each <value> of
+       this variable is given as `--push-option=<value>`.
++
+This is a multi-valued variable, and an empty value can be used in a
+higher priority configuration file (e.g. `.git/config` in a
+repository) to clear the values inherited from a lower priority
+configuration files (e.g. `$HOME/.gitconfig`).
++
+--
+
+Example:
+
+/etc/gitconfig
+  push.pushoption = a
+  push.pushoption = b
+
+~/.gitconfig
+  push.pushoption = c
+
+repo/.git/config
+  push.pushoption =
+  push.pushoption = b
+
+This will result in only b (a and c are cleared).
+
+--
+
 push.recurseSubmodules::
        Make sure all submodule commits used by the revisions to be pushed
        are available on a remote-tracking branch. If the value is 'check'
@@ -2654,36 +2738,7 @@ push.recurseSubmodules::
        is retained. You may override this configuration at time of push by
        specifying '--recurse-submodules=check|on-demand|no'.
 
-rebase.stat::
-       Whether to show a diffstat of what changed upstream since the last
-       rebase. False by default.
-
-rebase.autoSquash::
-       If set to true enable `--autosquash` option by default.
-
-rebase.autoStash::
-       When set to true, automatically create a temporary stash entry
-       before the operation begins, and apply it after the operation
-       ends.  This means that you can run rebase on a dirty worktree.
-       However, use with care: the final stash application after a
-       successful rebase might result in non-trivial conflicts.
-       Defaults to false.
-
-rebase.missingCommitsCheck::
-       If set to "warn", git rebase -i will print a warning if some
-       commits are removed (e.g. a line was deleted), however the
-       rebase will still proceed. If set to "error", it will print
-       the previous warning and stop the rebase, 'git rebase
-       --edit-todo' can then be used to correct the error. If set to
-       "ignore", no checking is done.
-       To drop a commit without warning or error, use the `drop`
-       command in the todo-list.
-       Defaults to "ignore".
-
-rebase.instructionFormat::
-       A format string, as specified in linkgit:git-log[1], to be used for
-       the instruction list during an interactive rebase.  The format will automatically
-       have the long commit hash prepended to the format.
+include::rebase-config.txt[]
 
 receive.advertiseAtomic::
        By default, git-receive-pack will advertise the atomic push
@@ -2970,6 +3025,7 @@ sendemail.smtpPass::
 sendemail.suppresscc::
 sendemail.suppressFrom::
 sendemail.to::
+sendemail.tocmd::
 sendemail.smtpDomain::
 sendemail.smtpServer::
 sendemail.smtpServerPort::
@@ -3388,3 +3444,13 @@ web.browser::
        Specify a web browser that may be used by some commands.
        Currently only linkgit:git-instaweb[1] and linkgit:git-help[1]
        may use it.
+
+worktree.guessRemote::
+       With `add`, if no branch argument, and neither of `-b` nor
+       `-B` nor `--detach` are given, the command defaults to
+       creating a new branch from HEAD.  If `worktree.guessRemote` is
+       set to true, `worktree add` tries to find a remote-tracking
+       branch whose name uniquely matches the new branch name.  If
+       such a branch exists, it is checked out and set as "upstream"
+       for the new branch.  If no such match can be found, it falls
+       back to creating a new branch from the current HEAD.
index dd0dba5b1d1951e762f0a43f1b2476bfa1029ad9..9d1586b95659befe40d77201c7da88906b9b8b02 100644 (file)
@@ -80,6 +80,16 @@ endif::git-format-patch[]
 --histogram::
        Generate a diff using the "histogram diff" algorithm.
 
+--anchored=<text>::
+       Generate a diff using the "anchored diff" algorithm.
++
+This option may be specified more than once.
++
+If a line exists in both the source and destination, exists only once,
+and starts with this text, this algorithm attempts to prevent it from
+appearing as a deletion or addition in the output. It uses the "patience
+diff" algorithm internally.
+
 --diff-algorithm={patience|minimal|histogram|myers}::
        Choose a diff algorithm. The variants are as follows:
 +
@@ -557,6 +567,9 @@ endif::git-format-patch[]
 --text::
        Treat all files as text.
 
+--ignore-cr-at-eol::
+       Ignore carrige-return at the end of line when doing a comparison.
+
 --ignore-space-at-eol::
        Ignore changes in whitespace at EOL.
 
index b700beaff5ad2eaa407bfb3818e016a5bb27d87c..d50fa339dcc523158896fda8cdccd1a2784dfdd7 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
          [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
-         [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing]
+         [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
          [--chmod=(+|-)x] [--] [<pathspec>...]
 
 DESCRIPTION
@@ -175,6 +175,13 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
        warning (e.g., if you are manually performing operations on
        submodules).
 
+--renormalize::
+       Apply the "clean" process freshly to all tracked files to
+       forcibly add them again to the index.  This is useful after
+       changing `core.autocrlf` configuration or the `text` attribute
+       in order to correct files added with wrong CRLF/LF line endings.
+       This option implies `-u`.
+
 --chmod=(+|-)x::
        Override the executable bit of the added files.  The executable
        bit is only changed in the index, the files on disk are left
index 6c42abf070df93187e11dd31d263b199346cace2..4a1417bdcd7826d444dbfd4cbc438ec9ec2edf1b 100644 (file)
@@ -23,7 +23,7 @@ on the subcommand:
  git bisect terms [--term-good | --term-bad]
  git bisect skip [(<rev>|<range>)...]
  git bisect reset [<commit>]
- git bisect visualize
+ git bisect (visualize|view)
  git bisect replay <logfile>
  git bisect log
  git bisect run <cmd>...
@@ -193,24 +193,23 @@ git bisect start --term-new fixed --term-old broken
 Then, use `git bisect <term-old>` and `git bisect <term-new>` instead
 of `git bisect good` and `git bisect bad` to mark commits.
 
-Bisect visualize
-~~~~~~~~~~~~~~~~
+Bisect visualize/view
+~~~~~~~~~~~~~~~~~~~~~
 
 To see the currently remaining suspects in 'gitk', issue the following
-command during the bisection process:
+command during the bisection process (the subcommand `view` can be used
+as an alternative to `visualize`):
 
 ------------
 $ git bisect visualize
 ------------
 
-`view` may also be used as a synonym for `visualize`.
-
 If the `DISPLAY` environment variable is not set, 'git log' is used
 instead.  You can also give command-line options such as `-p` and
 `--stat`.
 
 ------------
-$ git bisect view --stat
+$ git bisect visualize --stat
 ------------
 
 Bisect log and bisect replay
index d6587c5e96f4dbcdb4176d9dd5f6a992c0e83f3b..b3084c99c1cabdccc690e4cec5071b22774bf6c8 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
        [(--merged | --no-merged) [<commit>]]
        [--contains [<commit]] [--no-contains [<commit>]]
        [--points-at <object>] [--format=<format>] [<pattern>...]
-'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -86,7 +86,7 @@ OPTIONS
 --delete::
        Delete a branch. The branch must be fully merged in its
        upstream branch, or in `HEAD` if no upstream was set with
-       `--track` or `--set-upstream`.
+       `--track` or `--set-upstream-to`.
 
 -D::
        Shortcut for `--delete --force`.
@@ -281,6 +281,12 @@ start-point is either a local or remote-tracking branch.
        and the object it points at.  The format is the same as
        that of linkgit:git-for-each-ref[1].
 
+CONFIGURATION
+-------------
+`pager.branch` is only respected when listing branches, i.e., when
+`--list` is used or implied. The default is to use a pager.
+See linkgit:git-config[1].
+
 Examples
 --------
 
index cf0a0b7df28e560a243805618bf9e5b093354eeb..d9de9925856d79784441bc6d66623f533b844396 100644 (file)
@@ -79,16 +79,21 @@ reference name expressions (see linkgit:gitrevisions[7]):
 
 With the `--branch` option, the command takes a name and checks if
 it can be used as a valid branch name (e.g. when creating a new
-branch).  The rule `git check-ref-format --branch $name` implements
+branch). But be cautious when using the
+previous checkout syntax that may refer to a detached HEAD state.
+The rule `git check-ref-format --branch $name` implements
 may be stricter than what `git check-ref-format refs/heads/$name`
 says (e.g. a dash may appear at the beginning of a ref component,
 but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
-expanded for the ``previous branch syntax''
-`@{-n}`.  For example, `@{-1}` is a way to refer the last branch you
-were on.  This option should be used by porcelains to accept this
-syntax anywhere a branch name is expected, so they can act as if you
-typed the branch name.
+expanded for the ``previous checkout syntax''
+`@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
+was checked out using "git checkout" operation. This option should be
+used by porcelains to accept this syntax anywhere a branch name is
+expected, so they can act as if you typed the branch name. As an
+exception note that, the ``previous checkout operation'' might result
+in a commit object name when the N-th last thing checked out was not
+a branch.
 
 OPTIONS
 -------
@@ -116,7 +121,7 @@ OPTIONS
 EXAMPLES
 --------
 
-* Print the name of the previous branch:
+* Print the name of the previous thing checked out:
 +
 ------------
 $ git check-ref-format --branch @{-1}
index e108b0f74bb6dc4d9f3fc27e7c3fe571b19a9d2f..ca5fc9c79887652e38e3d11dc86dba657fe585c0 100644 (file)
@@ -264,6 +264,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
        local modifications in a submodule would be overwritten the checkout
        will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
        is used, the work trees of submodules will not be updated.
+       Just like linkgit:git-submodule[1], this will detach the
+       submodules HEAD.
 
 <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
@@ -272,11 +274,11 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
        commit, your HEAD becomes "detached" and you are no longer on
        any branch (see below for details).
 +
-As a special case, the `"@{-N}"` syntax for the N-th last branch/commit
-checks out branches (instead of detaching).  You may also specify
-`-` which is synonymous with `"@{-1}"`.
+You can use the `"@{-N}"` syntax to refer to the N-th last
+branch/commit checked out using "git checkout" operation. You may
+also specify `-` which is synonymous to `"@{-1}`.
 +
-As a further special case, you may use `"A...B"` as a shortcut for the
+As a special case, you may use `"A...B"` as a shortcut for the
 merge base of `A` and `B` if there is exactly one merge base. You can
 leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
index 83c8e9b394a3a83639852210e2f6e237a0077ed2..42ca7b50956aa8560b6aa58b5d7741913c145a43 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--dissociate] [--separate-git-dir <git dir>]
          [--depth <depth>] [--[no-]single-branch] [--no-tags]
-         [--recurse-submodules] [--[no-]shallow-submodules]
+         [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
          [--jobs <n>] [--] <repository> [<directory>]
 
 DESCRIPTION
@@ -231,14 +231,17 @@ branch of some repository for search indexing.
        After the clone is created, initialize and clone submodules
        within based on the provided pathspec.  If no pathspec is
        provided, all submodules are initialized and cloned.
-       Submodules are initialized and cloned using their default
-       settings.  The resulting clone has `submodule.active` set to
+       This option can be given multiple times for pathspecs consisting
+       of multiple entries.  The resulting clone has `submodule.active` set to
        the provided pathspec, or "." (meaning all submodules) if no
-       pathspec is provided.  This is equivalent to running
-       `git submodule update --init --recursive` immediately after
-       the clone is finished. This option is ignored if the cloned
-       repository does not have a worktree/checkout (i.e. if any of
-       `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
+       pathspec is provided.
++
+Submodules are initialized and cloned using their default settings. This is
+equivalent to running
+`git submodule update --init --recursive <pathspec>` immediately after
+the clone is finished. This option is ignored if the cloned repository does
+not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`,
+or `--mirror` is given)
 
 --[no-]shallow-submodules::
        All submodules which are cloned will be shallow with a depth of 1.
index 4edd09fc6b0742042f3236598d8ecb793773d8ab..14da5fc157ee0ea7467e7b9bd9d33d465c1f7a3f 100644 (file)
@@ -180,6 +180,11 @@ See also <<FILES>>.
        value (but you can use `git config section.variable ~/`
        from the command line to let your shell do the expansion).
 
+--expiry-date::
+       `git config` will ensure that the output is converted from
+       a fixed or relative date-string to a timestamp. This option
+       has no effect when setting the value.
+
 -z::
 --null::
        For all options that output values and/or keys, always
index c924c945ba8b79e578ddbb7598aa29fabcdf5887..e027fb8c4b8269bb2266ca98f87c9c18ca45fad3 100644 (file)
@@ -3,14 +3,14 @@ git-describe(1)
 
 NAME
 ----
-git-describe - Describe a commit using the most recent tag reachable from it
-
+git-describe - Give an object a human readable name based on an available ref
 
 SYNOPSIS
 --------
 [verse]
 'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>...]
 'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
+'git describe' <blob>
 
 DESCRIPTION
 -----------
@@ -24,6 +24,12 @@ By default (without --all or --tags) `git describe` only shows
 annotated tags.  For more information about creating annotated tags
 see the -a and -s options to linkgit:git-tag[1].
 
+If the given object refers to a blob, it will be described
+as `<commit-ish>:<path>`, such that the blob can be found
+at `<path>` in the `<commit-ish>`, which itself describes the
+first commit in which this blob occurs in a reverse revision walk
+from HEAD.
+
 OPTIONS
 -------
 <commit-ish>...::
@@ -186,6 +192,14 @@ selected and output.  Here fewest commits different is defined as
 the number of commits which would be shown by `git log tag..input`
 will be the smallest number of commits possible.
 
+BUGS
+----
+
+Tree objects as well as tag objects not pointing at commits, cannot be described.
+When describing blobs, the lightweight tags pointing at blobs are ignored,
+but the blob is still described as <committ-ish>:<path> despite the lightweight
+tag being favorable.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 1d420e4cde8230de00aae583a296128fdd59140f..dffa14a7950e074bbff73ec79defdbbdcc9702be 100644 (file)
@@ -145,18 +145,25 @@ upstream::
        (behind), "<>" (ahead and behind), or "=" (in sync). `:track`
        also prints "[gone]" whenever unknown upstream ref is
        encountered. Append `:track,nobracket` to show tracking
-       information without brackets (i.e "ahead N, behind M").  Has
-       no effect if the ref does not have tracking information
-       associated with it.  All the options apart from `nobracket`
-       are mutually exclusive, but if used together the last option
-       is selected.
+       information without brackets (i.e "ahead N, behind M").
++
+For any remote-tracking branch `%(upstream)`, `%(upstream:remotename)`
+and `%(upstream:remoteref)` refer to the name of the remote and the
+name of the tracked remote ref, respectively. In other words, the
+remote-tracking branch can be updated explicitly and individually by
+using the refspec `%(upstream:remoteref):%(upstream)` to fetch from
+`%(upstream:remotename)`.
++
+Has no effect if the ref does not have tracking information associated
+with it.  All the options apart from `nobracket` are mutually exclusive,
+but if used together the last option is selected.
 
 push::
        The name of a local ref which represents the `@{push}`
        location for the displayed ref. Respects `:short`, `:lstrip`,
-       `:rstrip`, `:track`, and `:trackshort` options as `upstream`
-       does. Produces an empty string if no `@{push}` ref is
-       configured.
+       `:rstrip`, `:track`, `:trackshort`, `:remotename`, and `:remoteref`
+       options as `upstream` does. Produces an empty string if no `@{push}`
+       ref is configured.
 
 HEAD::
        '*' if HEAD matches current ref (the checked out branch), ' '
index 32246fdb007e9b25f414cff092b9bf0426c55d15..5437f8b0f0e6699eca662879290d47df85387f0f 100644 (file)
@@ -38,6 +38,13 @@ OPTIONS
        are shown as if 'short' were given, otherwise no ref names are
        shown. The default option is 'short'.
 
+--decorate-refs=<pattern>::
+--decorate-refs-exclude=<pattern>::
+       If no `--decorate-refs` is given, pretend as if all refs were
+       included.  For each candidate, do not use it for decoration if it
+       matches any patterns given to `--decorate-refs-exclude` or if it
+       doesn't match any of the patterns given to `--decorate-refs`.
+
 --source::
        Print out the ref name given on the command line by which each
        commit was reached.
index d153c17e0660d8ab9edb0b10a53172007d355510..3ac3e3a77d171d55a2992b263e03350cf4c08b3d 100644 (file)
@@ -9,7 +9,7 @@ git-ls-files - Show information about files in the index and the working tree
 SYNOPSIS
 --------
 [verse]
-'git ls-files' [-z] [-t] [-v]
+'git ls-files' [-z] [-t] [-v] [-f]
                (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*
                (-[c|d|o|i|s|u|k|m])*
                [--eol]
@@ -133,6 +133,11 @@ a space) at the start of each line:
        that are marked as 'assume unchanged' (see
        linkgit:git-update-index[1]).
 
+-f::
+       Similar to `-t`, but use lowercase letters for files
+       that are marked as 'fsmonitor valid' (see
+       linkgit:git-update-index[1]).
+
 --full-name::
        When run from a subdirectory, the command usually
        outputs paths relative to the current directory.  This
index 3c6927b1fb87940c53ff01339a297d6b85dc1676..d5dfd8430f3370dc168e42e3991d1d4a9b854617 100644 (file)
@@ -64,14 +64,6 @@ OPTIONS
 -------
 include::merge-options.txt[]
 
---signoff::
-       Add Signed-off-by line by the committer at the end of the commit
-       log message.  The meaning of a signoff depends on the project,
-       but it typically certifies that committer has
-       the rights to submit this work under the same license and
-       agrees to a Developer Certificate of Origin
-       (see http://developercertificate.org/ for more information).
-
 -m <msg>::
        Set the commit message to be used for the merge commit (in
        case one is created).
index 43677297f3b85814c18c972a159b81c8076bc445..e8dec1b3c89a66dc2f9e78737fa7d243bee85d37 100644 (file)
@@ -18,7 +18,7 @@ SYNOPSIS
 'git notes' merge --commit [-v | -q]
 'git notes' merge --abort [-v | -q]
 'git notes' remove [--ignore-missing] [--stdin] [<object>...]
-'git notes' prune [-n | -v]
+'git notes' prune [-n] [-v]
 'git notes' get-ref
 
 
index 473a16135abf867a77af26b35016dab0a2b6954d..aa403d02f33699cde77c507c0402d9dfa0b7fb2f 100644 (file)
@@ -12,7 +12,8 @@ SYNOPSIS
 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
        [--no-reuse-delta] [--delta-base-offset] [--non-empty]
        [--local] [--incremental] [--window=<n>] [--depth=<n>]
-       [--revs [--unpacked | --all]] [--stdout | base-name]
+       [--revs [--unpacked | --all]]
+       [--stdout [--filter=<filter-spec>] | base-name]
        [--shallow] [--keep-true-parents] < object-list
 
 
@@ -236,6 +237,25 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.
        With this option, parents that are hidden by grafts are packed
        nevertheless.
 
+--filter=<filter-spec>::
+       Requires `--stdout`.  Omits certain objects (usually blobs) from
+       the resulting packfile.  See linkgit:git-rev-list[1] for valid
+       `<filter-spec>` forms.
+
+--no-filter::
+       Turns off any previous `--filter=` argument.
+
+--missing=<missing-action>::
+       A debug option to help with future "partial clone" development.
+       This option specifies how missing objects are handled.
++
+The form '--missing=error' requests that pack-objects stop with an error if
+a missing object is encountered.  This is the default action.
++
+The form '--missing=allow-any' will allow object traversal to continue
+if a missing object is encountered.  Missing objects will silently be
+omitted from the results.
+
 SEE ALSO
 --------
 linkgit:git-rev-list[1]
index 7a493c80f776092265abd4fb2575639e683bcf5c..a37c0af9313e8e47806c89a584c70c09517244d1 100644 (file)
@@ -9,7 +9,7 @@ git-prune - Prune all unreachable objects from the object database
 SYNOPSIS
 --------
 [verse]
-'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
+'git prune' [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]
 
 DESCRIPTION
 -----------
@@ -42,12 +42,15 @@ OPTIONS
 --verbose::
        Report all removed objects.
 
-\--::
-       Do not interpret any more arguments as options.
+--progress::
+       Show progress.
 
 --expire <time>::
        Only expire loose objects older than <time>.
 
+\--::
+       Do not interpret any more arguments as options.
+
 <head>...::
        In addition to objects
        reachable from any of our references, keep objects
index 3e76e99f38f67a530d43e2da3b3129c2a99e3366..5b08302fc2299fe1b42fc0137b50a3bab774ca10 100644 (file)
@@ -156,11 +156,17 @@ already exists on the remote side.
        Either all refs are updated, or on error, no refs are updated.
        If the server does not support atomic pushes the push will fail.
 
--o::
---push-option::
+-o <option>::
+--push-option=<option>::
        Transmit the given string to the server, which passes them to
        the pre-receive as well as the post-receive hook. The given string
        must not contain a NUL or LF character.
+       When multiple `--push-option=<option>` are given, they are
+       all sent to the other side in the order listed on the
+       command line.
+       When no `--push-option=<option>` is given from the command
+       line, the values of configuration variable `push.pushOption`
+       are used instead.
 
 --receive-pack=<git-receive-pack>::
 --exec=<git-receive-pack>::
index 3cedfb0fd22be1c060ab2e7b9a141a0d1deb03af..8a861c1e0d69eda71fa2eec791e67c136b63b177 100644 (file)
@@ -203,24 +203,7 @@ Alternatively, you can undo the 'git rebase' with
 CONFIGURATION
 -------------
 
-rebase.stat::
-       Whether to show a diffstat of what changed upstream since the last
-       rebase. False by default.
-
-rebase.autoSquash::
-       If set to true enable `--autosquash` option by default.
-
-rebase.autoStash::
-       If set to true enable `--autostash` option by default.
-
-rebase.missingCommitsCheck::
-       If set to "warn", print warnings about removed commits in
-       interactive mode. If set to "error", print the warnings and
-       stop the rebase. If set to "ignore", no checking is
-       done. "ignore" by default.
-
-rebase.instructionFormat::
-       Custom commit list format to use during an `--interactive` rebase.
+include::rebase-config.txt[]
 
 OPTIONS
 -------
index 44c736f1a8e581bc072d5de3b719284f7376ec3b..472a6808cd60899f4554e998b9fb8e4f3103aac2 100644 (file)
@@ -20,9 +20,9 @@ depending on the subcommand:
 'git reflog' ['show'] [log-options] [<ref>]
 'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
        [--rewrite] [--updateref] [--stale-fix]
-       [--dry-run] [--verbose] [--all | <refs>...]
+       [--dry-run | -n] [--verbose] [--all | <refs>...]
 'git reflog delete' [--rewrite] [--updateref]
-       [--dry-run] [--verbose] ref@\{specifier\}...
+       [--dry-run | -n] [--verbose] ref@\{specifier\}...
 'git reflog exists' <ref>
 
 Reference logs, or "reflogs", record when the tips of branches and
index ef22f1775b634812a6d0595ca3d88ce0b0c51506..88609ff4351bb7793ffa0ce9f21efb39a8157da4 100644 (file)
@@ -47,7 +47,9 @@ SYNOPSIS
             [ --fixed-strings | -F ]
             [ --date=<format>]
             [ [ --objects | --objects-edge | --objects-edge-aggressive ]
-              [ --unpacked ] ]
+              [ --unpacked ]
+              [ --filter=<filter-spec> [ --filter-print-omitted ] ] ]
+            [ --missing=<missing-action> ]
             [ --pretty | --header ]
             [ --bisect ]
             [ --bisect-vars ]
index bac9014ac71775e5116ad7686af697eb22286ee2..8060ea35c5f932c1e4328f000a501ff399418f27 100644 (file)
@@ -203,9 +203,9 @@ a password is obtained using 'git-credential'.
        specify a full pathname of a sendmail-like program instead;
        the program must support the `-i` option.  Default value can
        be specified by the `sendemail.smtpServer` configuration
-       option; the built-in default is `/usr/sbin/sendmail` or
-       `/usr/lib/sendmail` if such program is available, or
-       `localhost` otherwise.
+       option; the built-in default is to search for `sendmail` in
+       `/usr/sbin`, `/usr/lib` and $PATH if such program is
+       available, falling back to `localhost` otherwise.
 
 --smtp-server-port=<port>::
        Specifies a port different from the default port (SMTP
index 00f95fee1faf52138ce014104148c444be89dabe..056dfb86612d0c5be0d01f16ac6b126fac80b92c 100644 (file)
@@ -13,8 +13,6 @@ SYNOPSIS
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
-'git stash' save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
-            [-u|--include-untracked] [-a|--all] [<message>]
 'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
             [-u|--include-untracked] [-a|--all] [-m|--message <message>]]
             [--] [<pathspec>...]]
@@ -33,7 +31,7 @@ and reverts the working directory to match the `HEAD` commit.
 The modifications stashed away by this command can be listed with
 `git stash list`, inspected with `git stash show`, and restored
 (potentially on top of a different commit) with `git stash apply`.
-Calling `git stash` without any arguments is equivalent to `git stash save`.
+Calling `git stash` without any arguments is equivalent to `git stash push`.
 A stash is by default listed as "WIP on 'branchname' ...", but
 you can give a more descriptive message on the command line when
 you create one.
@@ -48,7 +46,6 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
 OPTIONS
 -------
 
-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
 push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]::
 
        Save your local modifications to a new 'stash entry' and roll them
@@ -87,6 +84,12 @@ linkgit:git-add[1] to learn how to operate the `--patch` mode.
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
 
+save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
+
+       This option is deprecated in favour of 'git stash push'.  It
+       differs from "stash push" in that it cannot take pathspecs,
+       and any non-option arguments form the message.
+
 list [<options>]::
 
        List the stash entries that you currently have.  Each 'stash entry' is
@@ -118,7 +121,7 @@ pop [--index] [-q|--quiet] [<stash>]::
 
        Remove a single stashed state from the stash list and apply it
        on top of the current working tree state, i.e., do the inverse
-       operation of `git stash save`. The working directory must
+       operation of `git stash push`. The working directory must
        match the index.
 +
 Applying the state can fail with conflicts; in this case, it is not
@@ -137,7 +140,7 @@ apply [--index] [-q|--quiet] [<stash>]::
 
        Like `pop`, but do not remove the state from the stash list. Unlike `pop`,
        `<stash>` may be any commit that looks like a commit created by
-       `stash save` or `stash create`.
+       `stash push` or `stash create`.
 
 branch <branchname> [<stash>]::
 
@@ -148,7 +151,7 @@ branch <branchname> [<stash>]::
        `stash@{<revision>}`, it then drops the `<stash>`. When no `<stash>`
        is given, applies the latest one.
 +
-This is useful if the branch on which you ran `git stash save` has
+This is useful if the branch on which you ran `git stash push` has
 changed enough that `git stash apply` fails due to conflicts. Since
 the stash entry is applied on top of the commit that was HEAD at the
 time `git stash` was run, it restores the originally stashed state
@@ -172,14 +175,14 @@ create::
        return its object name, without storing it anywhere in the ref
        namespace.
        This is intended to be useful for scripts.  It is probably not
-       the command you want to use; see "save" above.
+       the command you want to use; see "push" above.
 
 store::
 
        Store a given stash created via 'git stash create' (which is a
        dangling merge commit) in the stash ref, updating the stash
        reflog.  This is intended to be useful for scripts.  It is
-       probably not the command you want to use; see "save" above.
+       probably not the command you want to use; see "push" above.
 
 DISCUSSION
 ----------
@@ -255,14 +258,14 @@ $ git stash pop
 
 Testing partial commits::
 
-You can use `git stash save --keep-index` when you want to make two or
+You can use `git stash push --keep-index` when you want to make two or
 more commits out of the changes in the work tree, and you want to test
 each change before committing:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
 $ git add --patch foo            # add just first part to the index
-$ git stash save --keep-index    # save all other changes to the stash
+$ git stash push --keep-index    # save all other changes to the stash
 $ edit/build/test first part
 $ git commit -m 'First part'     # commit fully tested change
 $ git stash pop                  # prepare to work on all other changes
index 9f3a78a36c48c55318ee0eea8e96a64ccce5bfa2..81cab9aefb014690d93f9602ebebc9058c3d168e 100644 (file)
@@ -97,8 +97,27 @@ configuration variable documented in linkgit:git-config[1].
        (and suppresses the output of submodule summaries when the config option
        `status.submoduleSummary` is set).
 
---ignored::
+--ignored[=<mode>]::
        Show ignored files as well.
++
+The mode parameter is used to specify the handling of ignored files.
+It is optional: it defaults to 'traditional'.
++
+The possible options are:
++
+       - 'traditional' - Shows ignored files and directories, unless
+                         --untracked-files=all is specifed, in which case
+                         individual files in ignored directories are
+                         displayed.
+       - 'no'          - Show no ignored files.
+       - 'matching'    - Shows ignored files and directories matching an
+                         ignore pattern.
++
+When 'matching' mode is specified, paths that explicity match an
+ignored pattern are shown. If a directory matches an ignore pattern,
+then it is shown, but not paths contained in the ignored directory. If
+a directory does not match an ignore pattern, but all contents are
+ignored, then the directory is not shown, but all contents are shown.
 
 -z::
        Terminate entries with NUL, instead of LF.  This implies
@@ -368,6 +387,19 @@ ignored submodules you can either use the --ignore-submodules=dirty command
 line option or the 'git submodule summary' command, which shows a similar
 output but does not honor these settings.
 
+BACKGROUND REFRESH
+------------------
+
+By default, `git status` will automatically refresh the index, updating
+the cached stat information from the working tree and writing out the
+result. Writing out the updated index is an optimization that isn't
+strictly necessary (`status` computes the values for itself, but writing
+them out is just to save subsequent programs from repeating our
+computation). When `status` is run in the background, the lock held
+during the write may conflict with other simultaneous processes, causing
+them to fail. Scripts running `status` in the background should consider
+using `git --no-optional-locks status` (see linkgit:git[1] for details).
+
 SEE ALSO
 --------
 linkgit:gitignore[5]
index 75c7dd9dea4d2b6c26b84b99fb21ba75cf1cdcf3..bdb0342593229e32425bf73d17ad6fcdd0dd3732 100644 (file)
@@ -16,9 +16,11 @@ SYNOPSIS
             [--chmod=(+|-)x]
             [--[no-]assume-unchanged]
             [--[no-]skip-worktree]
+            [--[no-]fsmonitor-valid]
             [--ignore-submodules]
             [--[no-]split-index]
             [--[no-|test-|force-]untracked-cache]
+            [--[no-]fsmonitor]
             [--really-refresh] [--unresolve] [--again | -g]
             [--info-only] [--index-info]
             [-z] [--stdin] [--index-version <n>]
@@ -111,6 +113,12 @@ you will need to handle the situation manually.
        set and unset the "skip-worktree" bit for the paths. See
        section "Skip-worktree bit" below for more information.
 
+--[no-]fsmonitor-valid::
+       When one of these flags is specified, the object name recorded
+       for the paths are not updated. Instead, these options
+       set and unset the "fsmonitor valid" bit for the paths. See
+       section "File System Monitor" below for more information.
+
 -g::
 --again::
        Runs 'git update-index' itself on the paths whose index
@@ -201,6 +209,15 @@ will remove the intended effect of the option.
        `--untracked-cache` used to imply `--test-untracked-cache` but
        this option would enable the extension unconditionally.
 
+--fsmonitor::
+--no-fsmonitor::
+       Enable or disable files system monitor feature. These options
+       take effect whatever the value of the `core.fsmonitor`
+       configuration variable (see linkgit:git-config[1]). But a warning
+       is emitted when the change goes against the configured value, as
+       the configured value will take effect next time the index is
+       read and this will remove the intended effect of the option.
+
 \--::
        Do not interpret any more arguments as options.
 
@@ -447,6 +464,34 @@ command reads the index; while when `--[no-|force-]untracked-cache`
 are used, the untracked cache is immediately added to or removed from
 the index.
 
+File System Monitor
+-------------------
+
+This feature is intended to speed up git operations for repos that have
+large working directories.
+
+It enables git to work together with a file system monitor (see the
+"fsmonitor-watchman" section of linkgit:githooks[5]) that can
+inform it as to what files have been modified. This enables git to avoid
+having to lstat() every file to find modified files.
+
+When used in conjunction with the untracked cache, it can further improve
+performance by avoiding the cost of scanning the entire working directory
+looking for new files.
+
+If you want to enable (or disable) this feature, it is easier to use
+the `core.fsmonitor` configuration variable (see
+linkgit:git-config[1]) than using the `--fsmonitor` option to
+`git update-index` in each repository, especially if you want to do so
+across all repositories you use, because you can set the configuration
+variable to `true` (or `false`) in your `$HOME/.gitconfig` just once
+and have it affect all repositories you touch.
+
+When the `core.fsmonitor` configuration variable is changed, the
+file system monitor is added to or removed from the index the next time
+a command reads the index. When `--[no-]fsmonitor` are used, the file
+system monitor is immediately added to or removed from the index.
+
 Configuration
 -------------
 
index b472acc3567f2cb0ed574a48459adb1c9a6f0334..f850e8ffb66a41d8d76c0cc67967f7a27782bc3c 100644 (file)
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
 SYNOPSIS
 --------
 [verse]
-'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<branch>]
+'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -45,14 +45,22 @@ specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
-add <path> [<branch>]::
+add <path> [<commit-ish>]::
 
-Create `<path>` and checkout `<branch>` into it. The new working directory
+Create `<path>` and checkout `<commit-ish>` into it. The new working directory
 is linked to the current repository, sharing everything except working
 directory specific files such as HEAD, index, etc. `-` may also be
-specified as `<branch>`; it is synonymous with `@{-1}`.
+specified as `<commit-ish>`; it is synonymous with `@{-1}`.
 +
-If `<branch>` is omitted and neither `-b` nor `-B` nor `--detach` used,
+If <commit-ish> is a branch name (call it `<branch>` and is not found,
+and neither `-b` nor `-B` nor `--detach` are used, but there does
+exist a tracking branch in exactly one remote (call it `<remote>`)
+with a matching name, treat as equivalent to
+------------
+$ git worktree add --track -b <branch> <path> <remote>/<branch>
+------------
++
+If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, a new branch based at HEAD is created automatically,
 as if `-b $(basename <path>)` was specified.
 
@@ -84,29 +92,45 @@ OPTIONS
 
 -f::
 --force::
-       By default, `add` refuses to create a new working tree when `<branch>`
+       By default, `add` refuses to create a new working tree when `<commit-ish>` is a branch name and
        is already checked out by another working tree. This option overrides
        that safeguard.
 
 -b <new-branch>::
 -B <new-branch>::
        With `add`, create a new branch named `<new-branch>` starting at
-       `<branch>`, and check out `<new-branch>` into the new working tree.
-       If `<branch>` is omitted, it defaults to HEAD.
+       `<commit-ish>`, and check out `<new-branch>` into the new working tree.
+       If `<commit-ish>` is omitted, it defaults to HEAD.
        By default, `-b` refuses to create a new branch if it already
        exists. `-B` overrides this safeguard, resetting `<new-branch>` to
-       `<branch>`.
+       `<commit-ish>`.
 
 --detach::
        With `add`, detach HEAD in the new working tree. See "DETACHED HEAD"
        in linkgit:git-checkout[1].
 
 --[no-]checkout::
-       By default, `add` checks out `<branch>`, however, `--no-checkout` can
+       By default, `add` checks out `<commit-ish>`, however, `--no-checkout` can
        be used to suppress checkout in order to make customizations,
        such as configuring sparse-checkout. See "Sparse checkout"
        in linkgit:git-read-tree[1].
 
+--[no-]guess-remote::
+       With `worktree add <path>`, without `<commit-ish>`, instead
+       of creating a new branch from HEAD, if there exists a tracking
+       branch in exactly one remote matching the basename of `<path>,
+       base the new branch on the remote-tracking branch, and mark
+       the remote-tracking branch as "upstream" from the new branch.
++
+This can also be set up as the default behaviour by using the
+`worktree.guessRemote` config option.
+
+--[no-]track::
+       When creating a new branch, if `<commit-ish>` is a branch,
+       mark it as "upstream" from the new branch.  This is the
+       default if `<commit-ish>` is a remote-tracking branch.  See
+       "--track" in linkgit:git-branch[1] for details.
+
 --lock::
        Keep the working tree locked after creation. This is the
        equivalent of `git worktree lock` after `git worktree add`,
index 463b0eb0f5c3c8e694a509d3e06aee29056f970d..3f4161a799300c55e17fec86e2debe503e6ec903 100644 (file)
@@ -522,11 +522,10 @@ other
        If either of these environment variables is set then 'git fetch'
        and 'git push' will use the specified command instead of 'ssh'
        when they need to connect to a remote system.
-       The command will be given exactly two or four arguments: the
-       'username@host' (or just 'host') from the URL and the shell
-       command to execute on that remote system, optionally preceded by
-       `-p` (literally) and the 'port' from the URL when it specifies
-       something other than the default SSH port.
+       The command-line parameters passed to the configured command are
+       determined by the ssh variant.  See `ssh.variant` option in
+       linkgit:git-config[1] for details.
+
 +
 `$GIT_SSH_COMMAND` takes precedence over `$GIT_SSH`, and is interpreted
 by the shell, which allows additional arguments to be included.
@@ -595,6 +594,10 @@ into it.
 Unsetting the variable, or setting it to empty, "0" or
 "false" (case insensitive) disables trace messages.
 
+`GIT_TRACE_FSMONITOR`::
+       Enables trace messages for the filesystem monitor extension.
+       See `GIT_TRACE` for available trace output options.
+
 `GIT_TRACE_PACK_ACCESS`::
        Enables trace messages for all accesses to any packs. For each
        access, the pack file name and an offset in the pack is
@@ -701,6 +704,12 @@ of clones and fetches.
        which feed potentially-untrusted URLS to git commands.  See
        linkgit:git-config[1] for more details.
 
+`GIT_PROTOCOL`::
+       For internal use only.  Used in handshaking the wire protocol.
+       Contains a colon ':' separated list of keys with optional values
+       'key[=value]'.  Presence of unknown keys and values must be
+       ignored.
+
 `GIT_OPTIONAL_LOCKS`::
        If set to `0`, Git will complete any requested operation without
        performing any optional sub-operations that require taking a lock.
@@ -727,6 +736,15 @@ corresponding standard handle, and if `GIT_REDIRECT_STDERR` is
 `2>&1`, standard error will be redirected to the same handle as
 standard output.
 
+`GIT_PRINT_SHA1_ELLIPSIS` (deprecated)::
+       If set to `yes`, print an ellipsis following an
+       (abbreviated) SHA-1 value.  This affects indications of
+       detached HEADs (linkgit:git-checkout[1]) and the raw
+       diff output (linkgit:git-diff[1]).  Printing an
+       ellipsis in the cases mentioned is no longer considered
+       adequate and support for it is likely to be removed in the
+       foreseeable future (along with the variable).
+
 Discussion[[Discussion]]
 ------------------------
 
index 4c68bc19d5108d1ba0c91b05d35acf8060eb2c9d..30687de81a6e40b3d9cc120f659817b1a8d03603 100644 (file)
@@ -232,8 +232,7 @@ From a clean working directory:
 
 -------------------------------------------------
 $ echo "* text=auto" >.gitattributes
-$ git read-tree --empty   # Clean index, force re-scan of working directory
-$ git add .
+$ git add --renormalize .
 $ git status        # Show files that will be normalized
 $ git commit -m "Introduce end-of-line normalization"
 -------------------------------------------------
@@ -328,6 +327,9 @@ You can declare that a filter turns a content that by itself is unusable
 into a usable content by setting the filter.<driver>.required configuration
 variable to `true`.
 
+Note: Whenever the clean filter is changed, the repo should be renormalized:
+$ git add --renormalize .
+
 For example, in .gitattributes, you would assign the `filter`
 attribute for paths.
 
index 5d3f45560ea9c29ec382c8772522f06ab5f81f73..f877f7b7cd19c3fb1d2ce1263d64c94798c5130d 100644 (file)
@@ -170,7 +170,8 @@ This hook cannot affect the outcome of 'git checkout'.
 
 It is also run after 'git clone', unless the --no-checkout (-n) option is
 used. The first parameter given to the hook is the null-ref, the second the
-ref of the new HEAD and the flag is always 1.
+ref of the new HEAD and the flag is always 1. Likewise for 'git worktree add'
+unless --no-checkout is used.
 
 This hook can be used to perform repository validity checks, auto-display
 differences from the previous HEAD if different, or set working dir metadata
@@ -223,8 +224,8 @@ to the user by writing to standard error.
 pre-receive
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 Just before starting to update refs on the remote repository, the
 pre-receive hook is invoked.  Its exit status determines the success
 or failure of the update.
@@ -264,8 +265,8 @@ linkgit:git-receive-pack[1] for some caveats.
 update
 ~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 Just before updating the ref on the remote repository, the update hook
 is invoked.  Its exit status determines the success or failure of
 the ref update.
@@ -309,8 +310,8 @@ unannotated tags to be pushed.
 post-receive
 ~~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -348,8 +349,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
 post-update
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -379,8 +380,8 @@ for the user.
 push-to-checkout
 ~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository, when
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository, and when
 the push tries to update the branch that is currently checked out
 and the `receive.denyCurrentBranch` configuration variable is set to
 `updateInstead`.  Such a push by default is refused if the working
@@ -454,6 +455,34 @@ the name of the file that holds the e-mail to be sent.  Exiting with a
 non-zero status causes 'git send-email' to abort before sending any
 e-mails.
 
+fsmonitor-watchman
+~~~~~~~~~~~~~~~~~~
+
+This hook is invoked when the configuration option core.fsmonitor is
+set to .git/hooks/fsmonitor-watchman.  It takes two arguments, a version
+(currently 1) and the time in elapsed nanoseconds since midnight,
+January 1, 1970.
+
+The hook should output to stdout the list of all files in the working
+directory that may have changed since the requested time.  The logic
+should be inclusive so that it does not miss any potential changes.
+The paths should be relative to the root of the working directory
+and be separated by a single NUL.
+
+It is OK to include files which have not actually changed.  All changes
+including newly-created and deleted files should be included. When
+files are renamed, both the old and the new name should be included.
+
+Git will limit what files it checks for changes as well as which
+directories are checked for untracked files based on the path names
+given.
+
+An optimized way to tell git "all files have changed" is to return
+the filename '/'.
+
+The exit status determines whether git will use the data from the
+hook to limit its search.  On error, it will fall back to verifying
+all files and folders.
 
 GIT
 ---
index adf9554ad2f84cb69a6b0a940e4d3e0e336df9ff..c60bcad44aa581b2449a7a638b6487c1c73e8c23 100644 (file)
@@ -208,6 +208,10 @@ info/exclude::
        'git clean' look at it but the core Git commands do not look
        at it.  See also: linkgit:gitignore[5].
 
+info/attributes::
+       Defines which attributes to assign to a path, similar to per-directory
+       `.gitattributes` files.   See also: linkgit:gitattributes[5].
+
 info/sparse-checkout::
        This file stores sparse checkout patterns.
        See also: linkgit:git-read-tree[1].
index 177610e44ea31eede07035292a66b0dca167c9ae..926e044d098bc1399c923c4f204bca5d4a7a37bf 100644 (file)
@@ -40,7 +40,7 @@ beginning. It is always easier to squash a few commits together than
 to split one big commit into several.  Don't be afraid of making too
 small or imperfect steps along the way. You can always go back later
 and edit the commits with `git rebase --interactive` before you
-publish them.  You can use `git stash save --keep-index` to run the
+publish them.  You can use `git stash push --keep-index` to run the
 test suite independent of other uncommitted changes; see the EXAMPLES
 section of linkgit:git-stash[1].
 
@@ -407,8 +407,8 @@ follows.
 `git pull <url> <branch>`
 =====================================
 
-Occasionally, the maintainer may get merge conflicts when he tries to
-pull changes from downstream.  In this case, he can ask downstream to
+Occasionally, the maintainer may get merge conflicts when they try to
+pull changes from downstream.  In this case, they can ask downstream to
 do the merge and resolve the conflicts themselves (perhaps they will
 know better how to resolve them).  It is one of the rare cases where
 downstream 'should' merge from upstream.
index 327f69bcf5a20c7cf33a712b08bed9d4ccfe9d2e..17231d8e5959a9b16753b4c4a617e8c2decf5171 100755 (executable)
@@ -3,11 +3,12 @@
 
 repository=${1?repository}
 destdir=${2?destination}
+GIT_MAN_REF=${3?master}
 
-head=master GIT_DIR=
+GIT_DIR=
 for d in "$repository/.git" "$repository"
 do
-       if GIT_DIR="$d" git rev-parse refs/heads/master >/dev/null 2>&1
+       if GIT_DIR="$d" git rev-parse "$GIT_MAN_REF" >/dev/null 2>&1
        then
                GIT_DIR="$d"
                export GIT_DIR
@@ -27,12 +28,12 @@ export GIT_INDEX_FILE GIT_WORK_TREE
 rm -f "$GIT_INDEX_FILE"
 trap 'rm -f "$GIT_INDEX_FILE"' 0
 
-git read-tree $head
+git read-tree "$GIT_MAN_REF"
 git checkout-index -a -f --prefix="$destdir"/
 
 if test -n "$GZ"
 then
-       git ls-tree -r --name-only $head |
+       git ls-tree -r --name-only "$GIT_MAN_REF" |
        xargs printf "$destdir/%s\n" |
        xargs gzip -f
 fi
index df3ea3779be369ce0f935cfac945e79f08ff4b5a..12b6bbf591ee801e0723f59db0e0abe7dc88ec73 100644 (file)
@@ -26,6 +26,10 @@ merge.ff::
        allowed (equivalent to giving the `--ff-only` option from the
        command line).
 
+merge.verifySignatures::
+       If true, this is equivalent to the --verify-signatures command
+       line option. See linkgit:git-merge[1] for details.
+
 include::fmt-merge-msg-config.txt[]
 
 merge.renameLimit::
index 2552ab8e8d5104d09c1c95010ab028068c7d18ae..3888c3ff85e2dc5b137e4e3ed50e39327760a02a 100644 (file)
@@ -57,6 +57,16 @@ set to `no` at the beginning of them.
 With --no-log do not list one-line descriptions from the
 actual commits being merged.
 
+--signoff::
+--no-signoff::
+       Add Signed-off-by line by the committer at the end of the commit
+       log message.  The meaning of a signoff depends on the project,
+       but it typically certifies that committer has
+       the rights to submit this work under the same license and
+       agrees to a Developer Certificate of Origin
+       (see http://developercertificate.org/ for more information).
++
+With --no-signoff do not add a Signed-off-by line.
 
 --stat::
 -n::
index a09d5974639fa772ba86c5547fe70716fd044f73..fd5d748d1b508c9cef1063227b5c478952d3bc7e 100644 (file)
@@ -58,11 +58,12 @@ diff-algorithm=[patience|minimal|histogram|myers];;
 ignore-space-change;;
 ignore-all-space;;
 ignore-space-at-eol;;
+ignore-cr-at-eol;;
        Treats lines with the indicated type of whitespace change as
        unchanged for the sake of a three-way merge.  Whitespace
        changes mixed with other changes to a line are not ignored.
-       See also linkgit:git-diff[1] `-b`, `-w`, and
-       `--ignore-space-at-eol`.
+       See also linkgit:git-diff[1] `-b`, `-w`,
+       `--ignore-space-at-eol`, and `--ignore-cr-at-eol`.
 +
 * If 'their' version only introduces whitespace changes to a line,
   'our' version is used;
index d433d50f8104dd5c86e49c844047208fe455b6a0..e664c088a5e6a4bf1d09425ff37e36fe11d9f2f3 100644 (file)
@@ -204,11 +204,13 @@ endif::git-rev-list[]
   than given and there are spaces on its left, use those spaces
 - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)'
   respectively, but padding both sides (i.e. the text is centered)
-- %(trailers): display the trailers of the body as interpreted by
-  linkgit:git-interpret-trailers[1]. If the `:only` option is given,
-  omit non-trailer lines from the trailer block.  If the `:unfold`
-  option is given, behave as if interpret-trailer's `--unfold` option
-  was given. E.g., `%(trailers:only:unfold)` to do both.
+- %(trailers[:options]): display the trailers of the body as interpreted
+  by linkgit:git-interpret-trailers[1]. The `trailers` string may be
+  followed by a colon and zero or more comma-separated options. If the
+  `only` option is given, omit non-trailer lines from the trailer block.
+  If the `unfold` option is given, behave as if interpret-trailer's
+  `--unfold` option was given.  E.g., `%(trailers:only,unfold)` to do
+  both.
 
 NOTE: Some placeholders may depend on other options given to the
 revision traversal engine. For example, the `%g*` reflog options will
diff --git a/Documentation/rebase-config.txt b/Documentation/rebase-config.txt
new file mode 100644 (file)
index 0000000..42e1ba7
--- /dev/null
@@ -0,0 +1,52 @@
+rebase.stat::
+       Whether to show a diffstat of what changed upstream since the last
+       rebase. False by default.
+
+rebase.autoSquash::
+       If set to true enable `--autosquash` option by default.
+
+rebase.autoStash::
+       When set to true, automatically create a temporary stash entry
+       before the operation begins, and apply it after the operation
+       ends.  This means that you can run rebase on a dirty worktree.
+       However, use with care: the final stash application after a
+       successful rebase might result in non-trivial conflicts.
+       This option can be overridden by the `--no-autostash` and
+       `--autostash` options of linkgit:git-rebase[1].
+       Defaults to false.
+
+rebase.missingCommitsCheck::
+       If set to "warn", git rebase -i will print a warning if some
+       commits are removed (e.g. a line was deleted), however the
+       rebase will still proceed. If set to "error", it will print
+       the previous warning and stop the rebase, 'git rebase
+       --edit-todo' can then be used to correct the error. If set to
+       "ignore", no checking is done.
+       To drop a commit without warning or error, use the `drop`
+       command in the todo list.
+       Defaults to "ignore".
+
+rebase.instructionFormat::
+       A format string, as specified in linkgit:git-log[1], to be used for the
+       todo list during an interactive rebase.  The format will
+       automatically have the long commit hash prepended to the format.
+
+rebase.abbreviateCommands::
+       If set to true, `git rebase` will use abbreviated command names in the
+       todo list resulting in something like this:
++
+-------------------------------------------
+       p deadbee The oneline of the commit
+       p fa1afe1 The oneline of the next commit
+       ...
+-------------------------------------------
++
+instead of:
++
+-------------------------------------------
+       pick deadbee The oneline of the commit
+       pick fa1afe1 The oneline of the next commit
+       ...
+-------------------------------------------
++
+Defaults to false.
index 13501e1556e25b9571c089e58c4bd092fee50b63..22f5c9b43dd01c02710e44e36ee6a9dd2afc9b60 100644 (file)
@@ -686,6 +686,11 @@ ifdef::git-rev-list[]
        all object IDs which I need to download if I have the commit
        object _bar_ but not _foo_''.
 
+--in-commit-order::
+       Print tree and blob ids in order of the commits. The tree
+       and blob ids are printed after they are first referenced
+       by a commit.
+
 --objects-edge::
        Similar to `--objects`, but also print the IDs of excluded
        commits prefixed with a ``-'' character.  This is used by
@@ -706,6 +711,47 @@ ifdef::git-rev-list[]
 --unpacked::
        Only useful with `--objects`; print the object IDs that are not
        in packs.
+
+--filter=<filter-spec>::
+       Only useful with one of the `--objects*`; omits objects (usually
+       blobs) from the list of printed objects.  The '<filter-spec>'
+       may be one of the following:
++
+The form '--filter=blob:none' omits all blobs.
++
+The form '--filter=blob:limit=<n>[kmg]' omits blobs larger than n bytes
+or units.  n may be zero.  The suffixes k, m, and g can be used to name
+units in KiB, MiB, or GiB.  For example, 'blob:limit=1k' is the same
+as 'blob:limit=1024'.
++
+The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout
+specification contained in the blob (or blob-expression) '<blob-ish>'
+to omit blobs that would not be not required for a sparse checkout on
+the requested refs.
++
+The form '--filter=sparse:path=<path>' similarly uses a sparse-checkout
+specification contained in <path>.
+
+--no-filter::
+       Turn off any previous `--filter=` argument.
+
+--filter-print-omitted::
+       Only useful with `--filter=`; prints a list of the objects omitted
+       by the filter.  Object IDs are prefixed with a ``~'' character.
+
+--missing=<missing-action>::
+       A debug option to help with future "partial clone" development.
+       This option specifies how missing objects are handled.
++
+The form '--missing=error' requests that rev-list stop with an error if
+a missing object is encountered.  This is the default action.
++
+The form '--missing=allow-any' will allow object traversal to continue
+if a missing object is encountered.  Missing objects will silently be
+omitted from the results.
++
+The form '--missing=print' is like 'allow-any', but will also print a
+list of the missing objects.  Object IDs are prefixed with a ``?'' character.
 endif::git-rev-list[]
 
 --no-walk[=(sorted|unsorted)]::
index 61277469c874933fbc21f7d64a3687374e9324c2..dfcc49c72c0fe8f90c5e66d8de30d7ad27582c2b 100644 (file)
@@ -271,7 +271,7 @@ The '..' (two-dot) Range Notation::
  for commits that are reachable from r2 excluding those that are reachable
  from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'.
 
-The '...' (three dot) Symmetric Difference Notation::
+The '...' (three-dot) Symmetric Difference Notation::
  A similar notation 'r1\...r2' is called symmetric difference
  of 'r1' and 'r2' and is defined as
  'r1 r2 --not $(git merge-base --all r1 r2)'.
diff --git a/Documentation/technical/api-decorate.txt b/Documentation/technical/api-decorate.txt
deleted file mode 100644 (file)
index 1d52a6c..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-decorate API
-============
-
-Talk about <decorate.h>
-
-(Linus)
index 6c77b4920c92a0b327fb88d2b461ec0d918ec99a..7fae00f44fe1798da82bfdbb902479a03d583843 100644 (file)
@@ -22,16 +22,20 @@ The notable options are:
 
 `flags`::
 
-       A bit-field of options (the `*IGNORED*` flags are mutually exclusive):
+       A bit-field of options:
 
 `DIR_SHOW_IGNORED`:::
 
-       Return just ignored files in `entries[]`, not untracked files.
+       Return just ignored files in `entries[]`, not untracked
+       files. This flag is mutually exclusive with
+       `DIR_SHOW_IGNORED_TOO`.
 
 `DIR_SHOW_IGNORED_TOO`:::
 
-       Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
-       in addition to untracked files in `entries[]`.
+       Similar to `DIR_SHOW_IGNORED`, but return ignored files in
+       `ignored[]` in addition to untracked files in
+       `entries[]`. This flag is mutually exclusive with
+       `DIR_SHOW_IGNORED`.
 
 `DIR_KEEP_UNTRACKED_CONTENTS`:::
 
@@ -39,6 +43,21 @@ The notable options are:
        untracked contents of untracked directories are also returned in
        `entries[]`.
 
+`DIR_SHOW_IGNORED_TOO_MODE_MATCHING`:::
+
+       Only has meaning if `DIR_SHOW_IGNORED_TOO` is also set; if
+       this is set, returns ignored files and directories that match
+       an exclude pattern. If a directory matches an exclude pattern,
+       then the directory is returned and the contained paths are
+       not. A directory that does not match an exclude pattern will
+       not be returned even if all of its contents are ignored. In
+       this case, the contents are returned as individual entries.
++
+If this is set, files and directories that explicity match an ignore
+pattern are reported. Implicity ignored directories (directories that
+do not match an ignore pattern, but whose contents are all ignored)
+are not reported, instead all of the contents are reported.
+
 `DIR_COLLECT_IGNORED`:::
 
        Special mode for git-add. Return ignored files in `ignored[]` and
index 1c561bdd9204fe221512064f765bea9eb7ec5945..a0e45f2889e6e52db71f88d7368bbb2efc02616a 100644 (file)
@@ -219,6 +219,10 @@ smart server reply:
    S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n
    S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n
 
+The client may send Extra Parameters (see
+Documentation/technical/pack-protocol.txt) as a colon-separated string
+in the Git-Protocol HTTP header.
+
 Dumb Server Response
 ^^^^^^^^^^^^^^^^^^^^
 Dumb servers MUST respond with the dumb server reply format.
@@ -269,7 +273,11 @@ the C locale ordering.  The stream SHOULD include the default ref
 named `HEAD` as the first ref.  The stream MUST include capability
 declarations behind a NUL on the first ref.
 
+The returned response contains "version 1" if "version=1" was sent as an
+Extra Parameter.
+
   smart_reply     =  PKT-LINE("# service=$servicename" LF)
+                    *1("version 1")
                     ref_list
                     "0000"
   ref_list        =  empty_list / non_empty_list
index ade0b0c4454651812e88b292a846f23a58925f5f..db3572626beba6fc04f169b4ccccca0084d7326b 100644 (file)
@@ -295,3 +295,22 @@ The remaining data of each directory block is grouped by type:
     in the previous ewah bitmap.
 
   - One NUL.
+
+== File System Monitor cache
+
+  The file system monitor cache tracks files for which the core.fsmonitor
+  hook has told us about changes.  The signature for this extension is
+  { 'F', 'S', 'M', 'N' }.
+
+  The extension starts with
+
+  - 32-bit version number: the current supported version is 1.
+
+  - 64-bit time: the extension data reflects all changes through the given
+       time which is stored as the nanoseconds elapsed since midnight,
+       January 1, 1970.
+
+  - 32-bit bitmap size: the size of the CE_FSMONITOR_VALID bitmap.
+
+  - An ewah bitmap, the n-th bit indicates whether the n-th index entry
+    is not CE_FSMONITOR_VALID.
index ed1eae8b83a651d34f9ce2488f58d02a763fbfd4..cd31edc91ea1989adc7f2321327c0a97685fe62f 100644 (file)
@@ -39,6 +39,19 @@ communicates with that invoked process over the SSH connection.
 The file:// transport runs the 'upload-pack' or 'receive-pack'
 process locally and communicates with it over a pipe.
 
+Extra Parameters
+----------------
+
+The protocol provides a mechanism in which clients can send additional
+information in its first message to the server. These are called "Extra
+Parameters", and are supported by the Git, SSH, and HTTP protocols.
+
+Each Extra Parameter takes the form of `<key>=<value>` or `<key>`.
+
+Servers that receive any such Extra Parameters MUST ignore all
+unrecognized keys. Currently, the only Extra Parameter recognized is
+"version=1".
+
 Git Transport
 -------------
 
@@ -46,18 +59,25 @@ The Git transport starts off by sending the command and repository
 on the wire using the pkt-line format, followed by a NUL byte and a
 hostname parameter, terminated by a NUL byte.
 
-   0032git-upload-pack /project.git\0host=myserver.com\0
+   0033git-upload-pack /project.git\0host=myserver.com\0
+
+The transport may send Extra Parameters by adding an additional NUL
+byte, and then adding one or more NUL-terminated strings:
+
+   003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0
 
 --
-   git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
+   git-proto-request = request-command SP pathname NUL
+                      [ host-parameter NUL ] [ NUL extra-parameters ]
    request-command   = "git-upload-pack" / "git-receive-pack" /
                       "git-upload-archive"   ; case sensitive
    pathname          = *( %x01-ff ) ; exclude NUL
    host-parameter    = "host=" hostname [ ":" port ]
+   extra-parameters  = 1*extra-parameter
+   extra-parameter   = 1*( %x01-ff ) NUL
 --
 
-Only host-parameter is allowed in the git-proto-request. Clients
-MUST NOT attempt to send additional parameters. It is used for the
+host-parameter is used for the
 git-daemon name based virtual hosting.  See --interpolated-path
 option to git daemon, with the %H/%CH format characters.
 
@@ -117,6 +137,12 @@ we execute it without the leading '/'.
                     v
    ssh user@example.com "git-upload-pack '~alice/project.git'"
 
+Depending on the value of the `protocol.version` configuration variable,
+Git may attempt to send Extra Parameters as a colon-separated string in
+the GIT_PROTOCOL environment variable. This is done only if
+the `ssh.variant` configuration variable indicates that the ssh command
+supports passing environment variables as an argument.
+
 A few things to remember here:
 
 - The "command name" is spelled with dash (e.g. git-upload-pack), but
@@ -137,11 +163,13 @@ Reference Discovery
 -------------------
 
 When the client initially connects the server will immediately respond
-with a listing of each reference it has (all branches and tags) along
+with a version number (if "version=1" is sent as an Extra Parameter),
+and a listing of each reference it has (all branches and tags) along
 with the object name that each reference currently points to.
 
-   $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+   $ echo -e -n "0044git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
       nc -v example.com 9418
+   000aversion 1
    00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
                side-band side-band-64k ofs-delta shallow no-progress include-tag
    00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
@@ -165,7 +193,8 @@ immediately after the ref itself, if presented. A conforming server
 MUST peel the ref if it's an annotated tag.
 
 ----
-  advertised-refs  =  (no-refs / list-of-refs)
+  advertised-refs  =  *1("version 1")
+                     (no-refs / list-of-refs)
                      *shallow
                      flush-pkt
 
diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt
new file mode 100644 (file)
index 0000000..0bed247
--- /dev/null
@@ -0,0 +1,324 @@
+Partial Clone Design Notes
+==========================
+
+The "Partial Clone" feature is a performance optimization for Git that
+allows Git to function without having a complete copy of the repository.
+The goal of this work is to allow Git better handle extremely large
+repositories.
+
+During clone and fetch operations, Git downloads the complete contents
+and history of the repository.  This includes all commits, trees, and
+blobs for the complete life of the repository.  For extremely large
+repositories, clones can take hours (or days) and consume 100+GiB of disk
+space.
+
+Often in these repositories there are many blobs and trees that the user
+does not need such as:
+
+  1. files outside of the user's work area in the tree.  For example, in
+     a repository with 500K directories and 3.5M files in every commit,
+     we can avoid downloading many objects if the user only needs a
+     narrow "cone" of the source tree.
+
+  2. large binary assets.  For example, in a repository where large build
+     artifacts are checked into the tree, we can avoid downloading all
+     previous versions of these non-mergeable binary assets and only
+     download versions that are actually referenced.
+
+Partial clone allows us to avoid downloading such unneeded objects *in
+advance* during clone and fetch operations and thereby reduce download
+times and disk usage.  Missing objects can later be "demand fetched"
+if/when needed.
+
+Use of partial clone requires that the user be online and the origin
+remote be available for on-demand fetching of missing objects.  This may
+or may not be problematic for the user.  For example, if the user can
+stay within the pre-selected subset of the source tree, they may not
+encounter any missing objects.  Alternatively, the user could try to
+pre-fetch various objects if they know that they are going offline.
+
+
+Non-Goals
+---------
+
+Partial clone is a mechanism to limit the number of blobs and trees downloaded
+*within* a given range of commits -- and is therefore independent of and not
+intended to conflict with existing DAG-level mechanisms to limit the set of
+requested commits (i.e. shallow clone, single branch, or fetch '<refspec>').
+
+
+Design Overview
+---------------
+
+Partial clone logically consists of the following parts:
+
+- A mechanism for the client to describe unneeded or unwanted objects to
+  the server.
+
+- A mechanism for the server to omit such unwanted objects from packfiles
+  sent to the client.
+
+- A mechanism for the client to gracefully handle missing objects (that
+  were previously omitted by the server).
+
+- A mechanism for the client to backfill missing objects as needed.
+
+
+Design Details
+--------------
+
+- A new pack-protocol capability "filter" is added to the fetch-pack and
+  upload-pack negotiation.
+
+  This uses the existing capability discovery mechanism.
+  See "filter" in Documentation/technical/pack-protocol.txt.
+
+- Clients pass a "filter-spec" to clone and fetch which is passed to the
+  server to request filtering during packfile construction.
+
+  There are various filters available to accommodate different situations.
+  See "--filter=<filter-spec>" in Documentation/rev-list-options.txt.
+
+- On the server pack-objects applies the requested filter-spec as it
+  creates "filtered" packfiles for the client.
+
+  These filtered packfiles are *incomplete* in the traditional sense because
+  they may contain objects that reference objects not contained in the
+  packfile and that the client doesn't already have.  For example, the
+  filtered packfile may contain trees or tags that reference missing blobs
+  or commits that reference missing trees.
+
+- On the client these incomplete packfiles are marked as "promisor packfiles"
+  and treated differently by various commands.
+
+- On the client a repository extension is added to the local config to
+  prevent older versions of git from failing mid-operation because of
+  missing objects that they cannot handle.
+  See "extensions.partialClone" in Documentation/technical/repository-version.txt"
+
+
+Handling Missing Objects
+------------------------
+
+- An object may be missing due to a partial clone or fetch, or missing due
+  to repository corruption.  To differentiate these cases, the local
+  repository specially indicates such filtered packfiles obtained from the
+  promisor remote as "promisor packfiles".
+
+  These promisor packfiles consist of a "<name>.promisor" file with
+  arbitrary contents (like the "<name>.keep" files), in addition to
+  their "<name>.pack" and "<name>.idx" files.
+
+- The local repository considers a "promisor object" to be an object that
+  it knows (to the best of its ability) that the promisor remote has promised
+  that it has, either because the local repository has that object in one of
+  its promisor packfiles, or because another promisor object refers to it.
+
+  When Git encounters a missing object, Git can see if it a promisor object
+  and handle it appropriately.  If not, Git can report a corruption.
+
+  This means that there is no need for the client to explicitly maintain an
+  expensive-to-modify list of missing objects.[a]
+
+- Since almost all Git code currently expects any referenced object to be
+  present locally and because we do not want to force every command to do
+  a dry-run first, a fallback mechanism is added to allow Git to attempt
+  to dynamically fetch missing objects from the promisor remote.
+
+  When the normal object lookup fails to find an object, Git invokes
+  fetch-object to try to get the object from the server and then retry
+  the object lookup.  This allows objects to be "faulted in" without
+  complicated prediction algorithms.
+
+  For efficiency reasons, no check as to whether the missing object is
+  actually a promisor object is performed.
+
+  Dynamic object fetching tends to be slow as objects are fetched one at
+  a time.
+
+- `checkout` (and any other command using `unpack-trees`) has been taught
+  to bulk pre-fetch all required missing blobs in a single batch.
+
+- `rev-list` has been taught to print missing objects.
+
+  This can be used by other commands to bulk prefetch objects.
+  For example, a "git log -p A..B" may internally want to first do
+  something like "git rev-list --objects --quiet --missing=print A..B"
+  and prefetch those objects in bulk.
+
+- `fsck` has been updated to be fully aware of promisor objects.
+
+- `repack` in GC has been updated to not touch promisor packfiles at all,
+  and to only repack other objects.
+
+- The global variable "fetch_if_missing" is used to control whether an
+  object lookup will attempt to dynamically fetch a missing object or
+  report an error.
+
+  We are not happy with this global variable and would like to remove it,
+  but that requires significant refactoring of the object code to pass an
+  additional flag.  We hope that concurrent efforts to add an ODB API can
+  encompass this.
+
+
+Fetching Missing Objects
+------------------------
+
+- Fetching of objects is done using the existing transport mechanism using
+  transport_fetch_refs(), setting a new transport option
+  TRANS_OPT_NO_DEPENDENTS to indicate that only the objects themselves are
+  desired, not any object that they refer to.
+
+  Because some transports invoke fetch_pack() in the same process, fetch_pack()
+  has been updated to not use any object flags when the corresponding argument
+  (no_dependents) is set.
+
+- The local repository sends a request with the hashes of all requested
+  objects as "want" lines, and does not perform any packfile negotiation.
+  It then receives a packfile.
+
+- Because we are reusing the existing fetch-pack mechanism, fetching
+  currently fetches all objects referred to by the requested objects, even
+  though they are not necessary.
+
+
+Current Limitations
+-------------------
+
+- The remote used for a partial clone (or the first partial fetch
+  following a regular clone) is marked as the "promisor remote".
+
+  We are currently limited to a single promisor remote and only that
+  remote may be used for subsequent partial fetches.
+
+  We accept this limitation because we believe initial users of this
+  feature will be using it on repositories with a strong single central
+  server.
+
+- Dynamic object fetching will only ask the promisor remote for missing
+  objects.  We assume that the promisor remote has a complete view of the
+  repository and can satisfy all such requests.
+
+- Repack essentially treats promisor and non-promisor packfiles as 2
+  distinct partitions and does not mix them.  Repack currently only works
+  on non-promisor packfiles and loose objects.
+
+- Dynamic object fetching invokes fetch-pack once *for each item*
+  because most algorithms stumble upon a missing object and need to have
+  it resolved before continuing their work.  This may incur significant
+  overhead -- and multiple authentication requests -- if many objects are
+  needed.
+
+- Dynamic object fetching currently uses the existing pack protocol V0
+  which means that each object is requested via fetch-pack.  The server
+  will send a full set of info/refs when the connection is established.
+  If there are large number of refs, this may incur significant overhead.
+
+
+Future Work
+-----------
+
+- Allow more than one promisor remote and define a strategy for fetching
+  missing objects from specific promisor remotes or of iterating over the
+  set of promisor remotes until a missing object is found.
+
+  A user might want to have multiple geographically-close cache servers
+  for fetching missing blobs while continuing to do filtered `git-fetch`
+  commands from the central server, for example.
+
+  Or the user might want to work in a triangular work flow with multiple
+  promisor remotes that each have an incomplete view of the repository.
+
+- Allow repack to work on promisor packfiles (while keeping them distinct
+  from non-promisor packfiles).
+
+- Allow non-pathname-based filters to make use of packfile bitmaps (when
+  present).  This was just an omission during the initial implementation.
+
+- Investigate use of a long-running process to dynamically fetch a series
+  of objects, such as proposed in [5,6] to reduce process startup and
+  overhead costs.
+
+  It would be nice if pack protocol V2 could allow that long-running
+  process to make a series of requests over a single long-running
+  connection.
+
+- Investigate pack protocol V2 to avoid the info/refs broadcast on
+  each connection with the server to dynamically fetch missing objects.
+
+- Investigate the need to handle loose promisor objects.
+
+  Objects in promisor packfiles are allowed to reference missing objects
+  that can be dynamically fetched from the server.  An assumption was
+  made that loose objects are only created locally and therefore should
+  not reference a missing object.  We may need to revisit that assumption
+  if, for example, we dynamically fetch a missing tree and store it as a
+  loose object rather than a single object packfile.
+
+  This does not necessarily mean we need to mark loose objects as promisor;
+  it may be sufficient to relax the object lookup or is-promisor functions.
+
+
+Non-Tasks
+---------
+
+- Every time the subject of "demand loading blobs" comes up it seems
+  that someone suggests that the server be allowed to "guess" and send
+  additional objects that may be related to the requested objects.
+
+  No work has gone into actually doing that; we're just documenting that
+  it is a common suggestion.  We're not sure how it would work and have
+  no plans to work on it.
+
+  It is valid for the server to send more objects than requested (even
+  for a dynamic object fetch), but we are not building on that.
+
+
+Footnotes
+---------
+
+[a] expensive-to-modify list of missing objects:  Earlier in the design of
+    partial clone we discussed the need for a single list of missing objects.
+    This would essentially be a sorted linear list of OIDs that the were
+    omitted by the server during a clone or subsequent fetches.
+
+    This file would need to be loaded into memory on every object lookup.
+    It would need to be read, updated, and re-written (like the .git/index)
+    on every explicit "git fetch" command *and* on any dynamic object fetch.
+
+    The cost to read, update, and write this file could add significant
+    overhead to every command if there are many missing objects.  For example,
+    if there are 100M missing blobs, this file would be at least 2GiB on disk.
+
+    With the "promisor" concept, we *infer* a missing object based upon the
+    type of packfile that references it.
+
+
+Related Links
+-------------
+[0] https://bugs.chromium.org/p/git/issues/detail?id=2
+    Chromium work item for: Partial Clone
+
+[1] https://public-inbox.org/git/20170113155253.1644-1-benpeart@microsoft.com/
+    Subject: [RFC] Add support for downloading blobs on demand
+    Date: Fri, 13 Jan 2017 10:52:53 -0500
+
+[2] https://public-inbox.org/git/cover.1506714999.git.jonathantanmy@google.com/
+    Subject: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)
+    Date: Fri, 29 Sep 2017 13:11:36 -0700
+
+[3] https://public-inbox.org/git/20170426221346.25337-1-jonathantanmy@google.com/
+    Subject: Proposal for missing blob support in Git repos
+    Date: Wed, 26 Apr 2017 15:13:46 -0700
+
+[4] https://public-inbox.org/git/1488999039-37631-1-git-send-email-git@jeffhostetler.com/
+    Subject: [PATCH 00/10] RFC Partial Clone and Fetch
+    Date: Wed,  8 Mar 2017 18:50:29 +0000
+
+[5] https://public-inbox.org/git/20170505152802.6724-1-benpeart@microsoft.com/
+    Subject: [PATCH v7 00/10] refactor the filter process code into a reusable module
+    Date: Fri,  5 May 2017 11:27:52 -0400
+
+[6] https://public-inbox.org/git/20170714132651.170708-1-benpeart@microsoft.com/
+    Subject: [RFC/PATCH v2 0/1] Add support for downloading blobs on demand
+    Date: Fri, 14 Jul 2017 09:26:50 -0400
index b4d88af133e8eafd92f86443bd74de063bad4835..eff78902742ab0c4c9855eed11b67cf576c0e4d4 100644 (file)
@@ -319,7 +319,7 @@ do so (now or later) by using -b with the checkout command again. Example:
 
   git checkout -b new_branch_name
 
-HEAD is now at 427abfa... Linux v2.6.17
+HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
 
 The HEAD then refers to the SHA-1 of the commit instead of to a branch,
@@ -508,7 +508,7 @@ Bisecting: 3537 revisions left to test after this
 
 If you run `git branch` at this point, you'll see that Git has
 temporarily moved you in "(no branch)". HEAD is now detached from any
-branch and points directly to a commit (with commit id 65934...) that
+branch and points directly to a commit (with commit id 65934) that
 is reachable from "master" but not from v2.6.18. Compile and test it,
 and see whether it crashes. Assume it does crash. Then:
 
@@ -549,14 +549,14 @@ says "bisect".  Choose a safe-looking commit nearby, note its commit
 id, and check it out with:
 
 -------------------------------------------------
-$ git reset --hard fb47ddb2db...
+$ git reset --hard fb47ddb2db
 -------------------------------------------------
 
 then test, run `bisect good` or `bisect bad` as appropriate, and
 continue.
 
 Instead of `git bisect visualize` and then `git reset --hard
-fb47ddb2db...`, you might just want to tell Git that you want to skip
+fb47ddb2db`, you might just want to tell Git that you want to skip
 the current commit:
 
 -------------------------------------------------
@@ -1556,7 +1556,7 @@ so on a different branch and then coming back), unstash the
 work-in-progress changes.
 
 ------------------------------------------------
-$ git stash save "work in progress for foo feature"
+$ git stash push -m "work in progress for foo feature"
 ------------------------------------------------
 
 This command will save your changes away to the `stash`, and
@@ -3416,7 +3416,7 @@ commit abc
 Author:
 Date:
 ...
-:100644 100644 4b9458b... newsha... M somedirectory/myfile
+:100644 100644 4b9458b newsha M somedirectory/myfile
 
 
 commit xyz
@@ -3424,7 +3424,7 @@ Author:
 Date:
 
 ...
-:100644 100644 oldsha... 4b9458b... M somedirectory/myfile
+:100644 100644 oldsha 4b9458b M somedirectory/myfile
 ------------------------------------------------
 
 This tells you that the immediately following version of the file was
@@ -3449,7 +3449,7 @@ and your repository is good again!
 $ git log --raw --all
 ------------------------------------------------
 
-and just looked for the sha of the missing object (4b9458b..) in that
+and just looked for the sha of the missing object (4b9458b) in that
 whole thing. It's up to you--Git does *have* a lot of information, it is
 just missing one particular blob version.
 
@@ -4114,9 +4114,9 @@ program, e.g.  `diff3`, `merge`, or Git's own merge-file, on
 the blob objects from these three stages yourself, like this:
 
 ------------------------------------------------
-$ git cat-file blob 263414f... >hello.c~1
-$ git cat-file blob 06fa6a2... >hello.c~2
-$ git cat-file blob cc44c73... >hello.c~3
+$ git cat-file blob 263414f >hello.c~1
+$ git cat-file blob 06fa6a2 >hello.c~2
+$ git cat-file blob cc44c73 >hello.c~3
 $ git merge-file hello.c~2 hello.c~1 hello.c~3
 ------------------------------------------------
 
@@ -4374,7 +4374,7 @@ $ git log --no-merges t/
 ------------------------
 
 In the pager (`less`), just search for "bundle", go a few lines back,
-and see that it is in commit 18449ab0...  Now just copy this object name,
+and see that it is in commit 18449ab0.  Now just copy this object name,
 and paste it into the command line
 
 -------------------
index 879178c550165c3b5c81db129f904758b904c808..4eba7701a0ff7667d241ad2741a5d3b2611ec9d7 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.15.1
+DEF_VER=v2.16.0-rc0
 
 LF='
 '
index ee9d5eb11ee213e61d395bac6f2420278698389c..bb5b7cdfb695ee7f15128d5885ea74a4e34f5b9b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -425,6 +425,13 @@ all::
 #
 # to say "export LESS=FRX (and LV=-c) if the environment variable
 # LESS (and LV) is not set, respectively".
+#
+# Define TEST_SHELL_PATH if you want to use a shell besides SHELL_PATH for
+# running the test scripts (e.g., bash has better support for "set -x"
+# tracing).
+#
+# When cross-compiling, define HOST_CPU as the canonical name of the CPU on
+# which the built Git will run (for instance "x86_64").
 
 GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -646,9 +653,12 @@ TEST_PROGRAMS_NEED_X += test-ctype
 TEST_PROGRAMS_NEED_X += test-config
 TEST_PROGRAMS_NEED_X += test-date
 TEST_PROGRAMS_NEED_X += test-delta
+TEST_PROGRAMS_NEED_X += test-drop-caches
 TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-dump-fsmonitor
 TEST_PROGRAMS_NEED_X += test-dump-split-index
 TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
+TEST_PROGRAMS_NEED_X += test-example-decorate
 TEST_PROGRAMS_NEED_X += test-fake-ssh
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-hashmap
@@ -727,6 +737,8 @@ endif
 export PERL_PATH
 export PYTHON_PATH
 
+TEST_SHELL_PATH = $(SHELL_PATH)
+
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
 VCSSVN_LIB = vcs-svn/lib.a
@@ -757,6 +769,7 @@ LIB_OBJS += branch.o
 LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
+LIB_OBJS += checkout.o
 LIB_OBJS += color.o
 LIB_OBJS += column.o
 LIB_OBJS += combine-diff.o
@@ -794,6 +807,7 @@ LIB_OBJS += ewah/ewah_rlw.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fetch-pack.o
 LIB_OBJS += fsck.o
+LIB_OBJS += fsmonitor.o
 LIB_OBJS += gettext.o
 LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
@@ -807,6 +821,8 @@ LIB_OBJS += levenshtein.o
 LIB_OBJS += line-log.o
 LIB_OBJS += line-range.o
 LIB_OBJS += list-objects.o
+LIB_OBJS += list-objects-filter.o
+LIB_OBJS += list-objects-filter-options.o
 LIB_OBJS += ll-merge.o
 LIB_OBJS += lockfile.o
 LIB_OBJS += log-tree.o
@@ -846,6 +862,7 @@ LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
 LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
+LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
@@ -1091,6 +1108,12 @@ else
 BROKEN_PATH_FIX = '/^\# @@BROKEN_PATH_FIX@@$$/d'
 endif
 
+ifeq (,$(HOST_CPU))
+       BASIC_CFLAGS += -DGIT_HOST_CPU="\"$(firstword $(subst -, ,$(uname_M)))\""
+else
+       BASIC_CFLAGS += -DGIT_HOST_CPU="\"$(HOST_CPU)\""
+endif
+
 ifneq (,$(INLINE))
        BASIC_CFLAGS += -Dinline=$(INLINE)
 endif
@@ -1721,6 +1744,7 @@ prefix_SQ = $(subst ','\'',$(prefix))
 gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
@@ -1889,7 +1913,9 @@ builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
 version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT
 version.sp version.s version.o: EXTRA_CPPFLAGS = \
        '-DGIT_VERSION="$(GIT_VERSION)"' \
-       '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)'
+       '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)' \
+       '-DGIT_BUILT_FROM_COMMIT="$(shell GIT_CEILING_DIRECTORIES=\"$(CURDIR)/..\" \
+               git rev-parse -q --verify HEAD || :)"'
 
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && \
@@ -2351,6 +2377,7 @@ GIT-LDFLAGS: FORCE
 # and the first level quoting from the shell that runs "echo".
 GIT-BUILD-OPTIONS: FORCE
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@+
+       @echo TEST_SHELL_PATH=\''$(subst ','\'',$(TEST_SHELL_PATH_SQ))'\' >>$@+
        @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@+
        @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@+
        @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@+
index 6162eb4395ea78354187619db50545c443772b73..bec73d6767c965a8cfa684c96eaa8eefb66901d3 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.15.2.txt
\ No newline at end of file
+Documentation/RelNotes/2.16.0.txt
\ No newline at end of file
index d81e1cb7425b0f2d75461865b5c965a21ada5587..406efc183ba272b94a39d4cc7e97d7483a40a9d4 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -17,6 +17,8 @@ int advice_set_upstream_failure = 1;
 int advice_object_name_warning = 1;
 int advice_rm_hints = 1;
 int advice_add_embedded_repo = 1;
+int advice_ignored_hook = 1;
+int advice_waiting_for_editor = 1;
 
 static struct {
        const char *name;
@@ -38,6 +40,8 @@ static struct {
        { "objectnamewarning", &advice_object_name_warning },
        { "rmhints", &advice_rm_hints },
        { "addembeddedrepo", &advice_add_embedded_repo },
+       { "ignoredhook", &advice_ignored_hook },
+       { "waitingforeditor", &advice_waiting_for_editor },
 
        /* make this an alias for backward compatibility */
        { "pushnonfastforward", &advice_push_update_rejected }
index c84a44531c7d8fcc9867b2df4ba9dc11d0766508..70568fa7922d8aea403e415f9b9b554a88ace293 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -19,6 +19,8 @@ extern int advice_set_upstream_failure;
 extern int advice_object_name_warning;
 extern int advice_rm_hints;
 extern int advice_add_embedded_repo;
+extern int advice_ignored_hook;
+extern int advice_waiting_for_editor;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
diff --git a/apply.c b/apply.c
index d3dc1b292b9ecc38ed6c257a063a86daa7c98d66..321a9fa68d491f7e5e89dc9e398b067f67a10c77 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -75,13 +75,10 @@ static int parse_ignorewhitespace_option(struct apply_state *state,
 }
 
 int init_apply_state(struct apply_state *state,
-                    const char *prefix,
-                    struct lock_file *lock_file)
+                    const char *prefix)
 {
        memset(state, 0, sizeof(*state));
        state->prefix = prefix;
-       state->lock_file = lock_file;
-       state->newfd = -1;
        state->apply = 1;
        state->line_termination = '\n';
        state->p_value = 1;
@@ -146,8 +143,6 @@ int check_apply_state(struct apply_state *state, int force_apply)
        }
        if (state->check_index)
                state->unsafe_paths = 0;
-       if (!state->lock_file)
-               return error("BUG: state->lock_file should not be NULL");
 
        if (state->apply_verbosity <= verbosity_silent) {
                state->saved_error_routine = get_error_routine();
@@ -4692,13 +4687,13 @@ static int apply_patch(struct apply_state *state,
                state->apply = 0;
 
        state->update_index = state->check_index && state->apply;
-       if (state->update_index && state->newfd < 0) {
+       if (state->update_index && !is_lock_file_locked(&state->lock_file)) {
                if (state->index_file)
-                       state->newfd = hold_lock_file_for_update(state->lock_file,
-                                                                state->index_file,
-                                                                LOCK_DIE_ON_ERROR);
+                       hold_lock_file_for_update(&state->lock_file,
+                                                 state->index_file,
+                                                 LOCK_DIE_ON_ERROR);
                else
-                       state->newfd = hold_locked_index(state->lock_file, LOCK_DIE_ON_ERROR);
+                       hold_locked_index(&state->lock_file, LOCK_DIE_ON_ERROR);
        }
 
        if (state->check_index && read_apply_cache(state) < 0) {
@@ -4894,22 +4889,18 @@ int apply_all_patches(struct apply_state *state,
        }
 
        if (state->update_index) {
-               res = write_locked_index(&the_index, state->lock_file, COMMIT_LOCK);
+               res = write_locked_index(&the_index, &state->lock_file, COMMIT_LOCK);
                if (res) {
                        error(_("Unable to write new index file"));
                        res = -128;
                        goto end;
                }
-               state->newfd = -1;
        }
 
        res = !!errs;
 
 end:
-       if (state->newfd >= 0) {
-               rollback_lock_file(state->lock_file);
-               state->newfd = -1;
-       }
+       rollback_lock_file(&state->lock_file);
 
        if (state->apply_verbosity <= verbosity_silent) {
                set_error_routine(state->saved_error_routine);
diff --git a/apply.h b/apply.h
index d9b395770364b5495fc1136248efd64bd3a5e66d..dc4a0190570d060d23e8464f465232eddcf76deb 100644 (file)
--- a/apply.h
+++ b/apply.h
@@ -36,9 +36,8 @@ enum apply_verbosity {
 struct apply_state {
        const char *prefix;
 
-       /* These are lock_file related */
-       struct lock_file *lock_file;
-       int newfd;
+       /* Lock file */
+       struct lock_file lock_file;
 
        /* These control what gets looked at and modified */
        int apply; /* this is not a dry-run */
@@ -116,8 +115,7 @@ extern int apply_parse_options(int argc, const char **argv,
                               int *force_apply, int *options,
                               const char * const *apply_usage);
 extern int init_apply_state(struct apply_state *state,
-                           const char *prefix,
-                           struct lock_file *lock_file);
+                           const char *prefix);
 extern void clear_apply_state(struct apply_state *state);
 extern int check_apply_state(struct apply_state *state, int force_apply);
 
index 1e41f4bbeb1d8730da83aad12bc8987abedcad8a..0b7b62af0c3ecee10a26e9bd2d274690604ffcad 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -371,7 +371,7 @@ static void parse_treeish_arg(const char **argv,
                const char *colon = strchrnul(name, ':');
                int refnamelen = colon - name;
 
-               if (!dwim_ref(name, refnamelen, oid.hash, &ref))
+               if (!dwim_ref(name, refnamelen, &oid, &ref))
                        die("no such ref: %.*s", refnamelen, name);
                free(ref);
        }
index 3756f127b0fbaee3959011dbcd2beebbf067c5af..0fca17c02bba89d6c65df95e03e88ef6dd02971e 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -439,7 +439,12 @@ static int read_bisect_refs(void)
 
 static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
 static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
+static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
+static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
+static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
+static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
 static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
+static GIT_PATH_FUNC(git_path_head_name, "head-name")
 
 static void read_bisect_paths(struct argv_array *array)
 {
@@ -691,11 +696,12 @@ static int bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
        char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
 
        memcpy(bisect_rev_hex, oid_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
-       update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev->hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+       update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 
        argv_checkout[2] = bisect_rev_hex;
        if (no_checkout) {
-               update_ref(NULL, "BISECT_HEAD", bisect_rev->hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+               update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0,
+                          UPDATE_REFS_DIE_ON_ERR);
        } else {
                int res;
                res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
@@ -1049,3 +1055,40 @@ int estimate_bisect_steps(int all)
 
        return (e < 3 * x) ? n : n - 1;
 }
+
+static int mark_for_removal(const char *refname, const struct object_id *oid,
+                           int flag, void *cb_data)
+{
+       struct string_list *refs = cb_data;
+       char *ref = xstrfmt("refs/bisect%s", refname);
+       string_list_append(refs, ref);
+       return 0;
+}
+
+int bisect_clean_state(void)
+{
+       int result = 0;
+
+       /* There may be some refs packed during bisection */
+       struct string_list refs_for_removal = STRING_LIST_INIT_NODUP;
+       for_each_ref_in("refs/bisect", mark_for_removal, (void *) &refs_for_removal);
+       string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD"));
+       result = delete_refs("bisect: remove", &refs_for_removal, REF_NO_DEREF);
+       refs_for_removal.strdup_strings = 1;
+       string_list_clear(&refs_for_removal, 0);
+       unlink_or_warn(git_path_bisect_expected_rev());
+       unlink_or_warn(git_path_bisect_ancestors_ok());
+       unlink_or_warn(git_path_bisect_log());
+       unlink_or_warn(git_path_bisect_names());
+       unlink_or_warn(git_path_bisect_run());
+       unlink_or_warn(git_path_bisect_terms());
+       /* Cleanup head-name if it got left by an old version of git-bisect */
+       unlink_or_warn(git_path_head_name());
+       /*
+        * Cleanup BISECT_START last to support the --no-checkout option
+        * introduced in the commit 4796e823a.
+        */
+       unlink_or_warn(git_path_bisect_start());
+
+       return result;
+}
index c535e6d12e434a36569c18da81ea9cf916393363..a5d9248a47675194e7e0d16aed37018cbb67eb33 100644 (file)
--- a/bisect.h
+++ b/bisect.h
@@ -34,4 +34,6 @@ extern int estimate_bisect_steps(int all);
 
 extern void read_bisect_terms(const char **bad, const char **good);
 
+extern int bisect_clean_state(void);
+
 #endif
diff --git a/blame.c b/blame.c
index f575e9cbf4e3224049fe43b7f794d2dd8770eda7..2893f3c1030aab91a42ff9e0daf8a54ba8c3ef3c 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -166,7 +166,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        commit->date = now;
        parent_tail = &commit->parents;
 
-       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
                die("no such ref: HEAD");
 
        parent_tail = append_parent(parent_tail, &head_oid);
@@ -209,7 +209,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
 
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
-                       if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+                       if (opt->flags.allow_textconv &&
                            textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len))
                                strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
                        else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
@@ -293,7 +293,7 @@ static void fill_origin_blob(struct diff_options *opt,
                unsigned long file_size;
 
                (*num_read_blob)++;
-               if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+               if (opt->flags.allow_textconv &&
                    textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
                        ;
                else
@@ -541,7 +541,7 @@ static struct blame_origin *find_origin(struct commit *parent,
         * same and diff-tree is fairly efficient about this.
         */
        diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
+       diff_opts.flags.recursive = 1;
        diff_opts.detect_rename = 0;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        paths[0] = origin->path;
@@ -615,7 +615,7 @@ static struct blame_origin *find_rename(struct commit *parent,
        int i;
 
        diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
+       diff_opts.flags.recursive = 1;
        diff_opts.detect_rename = DIFF_DETECT_RENAME;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = origin->path;
@@ -1238,7 +1238,7 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
                return; /* nothing remains for this target */
 
        diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
+       diff_opts.flags.recursive = 1;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
 
        diff_setup_done(&diff_opts);
@@ -1253,7 +1253,7 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
        if ((opt & PICKAXE_BLAME_COPY_HARDEST)
            || ((opt & PICKAXE_BLAME_COPY_HARDER)
                && (!porigin || strcmp(target->path, porigin->path))))
-               DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+               diff_opts.flags.find_copies_harder = 1;
 
        if (is_null_oid(&target->commit->object.oid))
                do_diff_cache(&parent->tree->object.oid, &diff_opts);
@@ -1262,7 +1262,7 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
                              &target->commit->tree->object.oid,
                              "", &diff_opts);
 
-       if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
+       if (!diff_opts.flags.find_copies_harder)
                diffcore_std(&diff_opts);
 
        do {
@@ -1689,7 +1689,7 @@ static struct commit *dwim_reverse_initial(struct rev_info *revs,
                return NULL;
 
        /* Do we have HEAD? */
-       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
                return NULL;
        head_commit = lookup_commit_reference_gently(&head_oid, 1);
        if (!head_commit)
@@ -1825,7 +1825,7 @@ void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blam
                if (fill_blob_sha1_and_mode(o))
                        die(_("no such path %s in %s"), path, final_commit_name);
 
-               if (DIFF_OPT_TST(&sb->revs->diffopt, ALLOW_TEXTCONV) &&
+               if (sb->revs->diffopt.flags.allow_textconv &&
                    textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf,
                                    &sb->final_buf_size))
                        ;
index 4377ce2fb17d7467ee92d4db02cb4859f6397505..2672054f0b5423cd09010302285935650435f1dc 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -178,24 +178,40 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
        return 0;
 }
 
-int validate_new_branchname(const char *name, struct strbuf *ref,
-                           int force, int attr_only)
+/*
+ * Check if 'name' can be a valid name for a branch; die otherwise.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+int validate_branchname(const char *name, struct strbuf *ref)
 {
        if (strbuf_check_branch_ref(ref, name))
                die(_("'%s' is not a valid branch name."), name);
 
-       if (!ref_exists(ref->buf))
+       return ref_exists(ref->buf);
+}
+
+/*
+ * Check if a branch 'name' can be created as a new branch; die otherwise.
+ * 'force' can be used when it is OK for the named branch already exists.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force)
+{
+       const char *head;
+
+       if (!validate_branchname(name, ref))
                return 0;
-       else if (!force && !attr_only)
-               die(_("A branch named '%s' already exists."), ref->buf + strlen("refs/heads/"));
 
-       if (!attr_only) {
-               const char *head;
+       if (!force)
+               die(_("A branch named '%s' already exists."),
+                   ref->buf + strlen("refs/heads/"));
+
+       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+       if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+               die(_("Cannot force update the current branch."));
 
-               head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
-               if (!is_bare_repository() && head && !strcmp(head, ref->buf))
-                       die(_("Cannot force update the current branch."));
-       }
        return 1;
 }
 
@@ -228,7 +244,7 @@ N_("\n"
 "\"git push -u\" to set the upstream config as you push.");
 
 void create_branch(const char *name, const char *start_name,
-                  int force, int reflog, int clobber_head,
+                  int force, int clobber_head_ok, int reflog,
                   int quiet, enum branch_track track)
 {
        struct commit *commit;
@@ -242,9 +258,9 @@ void create_branch(const char *name, const char *start_name,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if (validate_new_branchname(name, &ref, force,
-                                   track == BRANCH_TRACK_OVERRIDE ||
-                                   clobber_head)) {
+       if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)
+           ? validate_branchname(name, &ref)
+           : validate_new_branchname(name, &ref, force)) {
                if (!force)
                        dont_change_ref = 1;
                else
@@ -264,7 +280,7 @@ void create_branch(const char *name, const char *start_name,
                die(_("Not a valid object name: '%s'."), start_name);
        }
 
-       switch (dwim_ref(start_name, strlen(start_name), oid.hash, &real_ref)) {
+       switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref)) {
        case 0:
                /* Not branching from any existing branch */
                if (explicit_tracking)
@@ -305,7 +321,7 @@ void create_branch(const char *name, const char *start_name,
                transaction = ref_transaction_begin(&err);
                if (!transaction ||
                    ref_transaction_update(transaction, ref.buf,
-                                          oid.hash, forcing ? NULL : null_sha1,
+                                          &oid, forcing ? NULL : &null_oid,
                                           0, msg, &err) ||
                    ref_transaction_commit(transaction, &err))
                        die("%s", err.buf);
index b07788558c0e76a701b571f73c0462dcf2abcc63..473d0a93e910d8f053e6e8c919e430048a3033a0 100644 (file)
--- a/branch.h
+++ b/branch.h
  *
  *   - force enables overwriting an existing (non-head) branch
  *
+ *   - clobber_head_ok allows the currently checked out (hence existing)
+ *     branch to be overwritten; without 'force', it has no effect.
+ *
  *   - reflog creates a reflog for the branch
  *
+ *   - quiet suppresses tracking information
+ *
  *   - track causes the new branch to be configured to merge the remote branch
  *     that start_name is a tracking branch for (if any).
+ *
  */
 void create_branch(const char *name, const char *start_name,
-                  int force, int reflog,
-                  int clobber_head, int quiet, enum branch_track track);
+                  int force, int clobber_head_ok,
+                  int reflog, int quiet, enum branch_track track);
 
 /*
- * Validates that the requested branch may be created, returning the
- * interpreted ref in ref, force indicates whether (non-head) branches
- * may be overwritten. A non-zero return value indicates that the force
- * parameter was non-zero and the branch already exists.
- *
- * Contrary to all of the above, when attr_only is 1, the caller is
- * not interested in verifying if it is Ok to update the named
- * branch to point at a potentially different commit. It is merely
- * asking if it is OK to change some attribute for the named branch
- * (e.g. tracking upstream).
- *
- * NEEDSWORK: This needs to be split into two separate functions in the
- * longer run for sanity.
- *
+ * Check if 'name' can be a valid name for a branch; die otherwise.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+extern int validate_branchname(const char *name, struct strbuf *ref);
+
+/*
+ * Check if a branch 'name' can be created as a new branch; die otherwise.
+ * 'force' can be used when it is OK for the named branch already exists.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
  */
-int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only);
+extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);
 
 /*
  * Remove information about the state of working on the current
index a648cf4c56c9f56eb60a97ca50ccf0b191dc50ff..bf01d89e28eaef0f8113d42c6b9d4142b8e912e4 100644 (file)
@@ -26,6 +26,7 @@ static const char * const builtin_add_usage[] = {
 };
 static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
+static int add_renormalize;
 
 struct update_callback_data {
        int flags;
@@ -116,13 +117,32 @@ int add_files_to_cache(const char *prefix,
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
        rev.diffopt.format_callback_data = &data;
-       rev.diffopt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
+       rev.diffopt.flags.override_submodule_config = 1;
        rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
        clear_pathspec(&rev.prune_data);
        return !!data.add_errors;
 }
 
+static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
+{
+       int i, retval = 0;
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+
+               if (ce_stage(ce))
+                       continue; /* do not touch unmerged paths */
+               if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
+                       continue; /* do not touch non blobs */
+               if (pathspec && !ce_path_match(ce, pathspec, NULL))
+                       continue;
+               retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE);
+       }
+
+       return retval;
+}
+
 static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
 {
        char *seen;
@@ -218,7 +238,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
        argc = setup_revisions(argc, argv, &rev, NULL);
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
        rev.diffopt.use_color = 0;
-       DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
+       rev.diffopt.flags.ignore_dirty_submodules = 1;
        out = open(file, O_CREAT | O_WRONLY, 0666);
        if (out < 0)
                die(_("Could not open '%s' for writing."), file);
@@ -276,6 +296,7 @@ static struct option builtin_add_options[] = {
        OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
        OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")),
        OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
+       OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
        OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
        OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
        { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
@@ -406,7 +427,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                          chmod_arg[1] != 'x' || chmod_arg[2]))
                die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
 
-       add_new_files = !take_worktree_changes && !refresh_only;
+       add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
        require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
 
        hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
@@ -500,7 +521,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        plug_bulk_checkin();
 
-       exit_status |= add_files_to_cache(prefix, &pathspec, flags);
+       if (add_renormalize)
+               exit_status |= renormalize_tracked_files(&pathspec, flags);
+       else
+               exit_status |= add_files_to_cache(prefix, &pathspec, flags);
 
        if (add_new_files)
                exit_status |= add_files(&dir, flags);
index d7513f53759bc5834aa02bd904432f64e036ccb6..06b824518fbdfd883e1905eaafdb0001b87f91a0 100644 (file)
@@ -708,6 +708,7 @@ static int split_mail_mbox(struct am_state *state, const char **paths,
 {
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf last = STRBUF_INIT;
+       int ret;
 
        cp.git_cmd = 1;
        argv_array_push(&cp.args, "mailsplit");
@@ -721,13 +722,16 @@ static int split_mail_mbox(struct am_state *state, const char **paths,
        argv_array_push(&cp.args, "--");
        argv_array_pushv(&cp.args, paths);
 
-       if (capture_command(&cp, &last, 8))
-               return -1;
+       ret = capture_command(&cp, &last, 8);
+       if (ret)
+               goto exit;
 
        state->cur = 1;
        state->last = strtol(last.buf, NULL, 10);
 
-       return 0;
+exit:
+       strbuf_release(&last);
+       return ret ? -1 : 0;
 }
 
 /**
@@ -1068,8 +1072,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
        if (!get_oid("HEAD", &curr_head)) {
                write_state_text(state, "abort-safety", oid_to_hex(&curr_head));
                if (!state->rebasing)
-                       update_ref_oid("am", "ORIG_HEAD", &curr_head, NULL, 0,
-                                       UPDATE_REFS_DIE_ON_ERR);
+                       update_ref("am", "ORIG_HEAD", &curr_head, NULL, 0,
+                                  UPDATE_REFS_DIE_ON_ERR);
        } else {
                write_state_text(state, "abort-safety", "");
                if (!state->rebasing)
@@ -1134,11 +1138,11 @@ static const char *msgnum(const struct am_state *state)
  */
 static void refresh_and_write_cache(void)
 {
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       struct lock_file lock_file = LOCK_INIT;
 
-       hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write index file"));
 }
 
@@ -1157,9 +1161,9 @@ static int index_has_changes(struct strbuf *sb)
                struct diff_options opt;
 
                diff_setup(&opt);
-               DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
+               opt.flags.exit_with_status = 1;
                if (!sb)
-                       DIFF_OPT_SET(&opt, QUICK);
+                       opt.flags.quick = 1;
                do_diff_cache(&head, &opt);
                diffcore_std(&opt);
                for (i = 0; sb && i < diff_queued_diff.nr; i++) {
@@ -1168,7 +1172,7 @@ static int index_has_changes(struct strbuf *sb)
                        strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
                }
                diff_flush(&opt);
-               return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;
+               return opt.flags.has_changes != 0;
        } else {
                for (i = 0; sb && i < active_nr; i++) {
                        if (i)
@@ -1409,8 +1413,8 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm
        rev_info.show_root_diff = 1;
        rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
        rev_info.no_commit_id = 1;
-       DIFF_OPT_SET(&rev_info.diffopt, BINARY);
-       DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
+       rev_info.diffopt.flags.binary = 1;
+       rev_info.diffopt.flags.full_index = 1;
        rev_info.diffopt.use_color = 0;
        rev_info.diffopt.file = fp;
        rev_info.diffopt.close_file = 1;
@@ -1433,7 +1437,7 @@ static void write_index_patch(const struct am_state *state)
        if (!get_oid_tree("HEAD", &head))
                tree = lookup_tree(&head);
        else
-               tree = lookup_tree(&empty_tree_oid);
+               tree = lookup_tree(the_hash_algo->empty_tree);
 
        fp = xfopen(am_path(state, "patch"), "w");
        init_revisions(&rev_info, NULL);
@@ -1488,11 +1492,10 @@ static int run_apply(const struct am_state *state, const char *index_file)
        struct argv_array apply_opts = ARGV_ARRAY_INIT;
        struct apply_state apply_state;
        int res, opts_left;
-       static struct lock_file lock_file;
        int force_apply = 0;
        int options = 0;
 
-       if (init_apply_state(&apply_state, NULL, &lock_file))
+       if (init_apply_state(&apply_state, NULL))
                die("BUG: init_apply_state() failed");
 
        argv_array_push(&apply_opts, "apply");
@@ -1686,8 +1689,8 @@ static void do_commit(const struct am_state *state)
        strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
                        state->msg);
 
-       update_ref_oid(sb.buf, "HEAD", &commit, old_oid, 0,
-                       UPDATE_REFS_DIE_ON_ERR);
+       update_ref(sb.buf, "HEAD", &commit, old_oid, 0,
+                  UPDATE_REFS_DIE_ON_ERR);
 
        if (state->rebasing) {
                FILE *fp = xfopen(am_path(state, "rewritten"), "a");
@@ -1946,15 +1949,14 @@ static void am_resolve(struct am_state *state)
  */
 static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
 {
-       struct lock_file *lock_file;
+       struct lock_file lock_file = LOCK_INIT;
        struct unpack_trees_options opts;
        struct tree_desc t[2];
 
        if (parse_tree(head) || parse_tree(remote))
                return -1;
 
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-       hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
        refresh_cache(REFRESH_QUIET);
 
@@ -1970,11 +1972,11 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
        init_tree_desc(&t[1], remote->buffer, remote->size);
 
        if (unpack_trees(2, t, &opts)) {
-               rollback_lock_file(lock_file);
+               rollback_lock_file(&lock_file);
                return -1;
        }
 
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
        return 0;
@@ -1986,15 +1988,14 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
  */
 static int merge_tree(struct tree *tree)
 {
-       struct lock_file *lock_file;
+       struct lock_file lock_file = LOCK_INIT;
        struct unpack_trees_options opts;
        struct tree_desc t[1];
 
        if (parse_tree(tree))
                return -1;
 
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-       hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
@@ -2005,11 +2006,11 @@ static int merge_tree(struct tree *tree)
        init_tree_desc(&t[0], tree->buffer, tree->size);
 
        if (unpack_trees(1, t, &opts)) {
-               rollback_lock_file(lock_file);
+               rollback_lock_file(&lock_file);
                return -1;
        }
 
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
        return 0;
@@ -2135,7 +2136,7 @@ static void am_abort(struct am_state *state)
 
        am_rerere_clear();
 
-       curr_branch = resolve_refdup("HEAD", 0, curr_head.hash, NULL);
+       curr_branch = resolve_refdup("HEAD", 0, &curr_head, NULL);
        has_curr_head = curr_branch && !is_null_oid(&curr_head);
        if (!has_curr_head)
                hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN);
@@ -2147,11 +2148,11 @@ static void am_abort(struct am_state *state)
        clean_index(&curr_head, &orig_head);
 
        if (has_orig_head)
-               update_ref_oid("am --abort", "HEAD", &orig_head,
-                               has_curr_head ? &curr_head : NULL, 0,
-                               UPDATE_REFS_DIE_ON_ERR);
+               update_ref("am --abort", "HEAD", &orig_head,
+                          has_curr_head ? &curr_head : NULL, 0,
+                          UPDATE_REFS_DIE_ON_ERR);
        else if (curr_branch)
-               delete_ref(NULL, curr_branch, NULL, REF_NODEREF);
+               delete_ref(NULL, curr_branch, NULL, REF_NO_DEREF);
 
        free(curr_branch);
        am_destroy(state);
index 81b9a61c3713a718a912d49916cf2632478cb47c..48d3989331d9b7deeb034642a027729bd1267e38 100644 (file)
@@ -9,8 +9,6 @@ static const char * const apply_usage[] = {
        NULL
 };
 
-static struct lock_file lock_file;
-
 int cmd_apply(int argc, const char **argv, const char *prefix)
 {
        int force_apply = 0;
@@ -18,7 +16,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
        int ret;
        struct apply_state state;
 
-       if (init_apply_state(&state, prefix, &lock_file))
+       if (init_apply_state(&state, prefix))
                exit(128);
 
        argc = apply_parse_options(argc, argv,
index 3324229025300f7d7de600753c03af3ac22e2152..4b5fadcbe1ae6a1b10fe88e80a5fcffd3296cfa7 100644 (file)
 #include "cache.h"
 #include "parse-options.h"
 #include "bisect.h"
+#include "refs.h"
+
+static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
+static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
+static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
 
 static const char * const git_bisect_helper_usage[] = {
        N_("git bisect--helper --next-all [--no-checkout]"),
+       N_("git bisect--helper --write-terms <bad_term> <good_term>"),
+       N_("git bisect--helper --bisect-clean-state"),
        NULL
 };
 
+/*
+ * Check whether the string `term` belongs to the set of strings
+ * included in the variable arguments.
+ */
+LAST_ARG_MUST_BE_NULL
+static int one_of(const char *term, ...)
+{
+       int res = 0;
+       va_list matches;
+       const char *match;
+
+       va_start(matches, term);
+       while (!res && (match = va_arg(matches, const char *)))
+               res = !strcmp(term, match);
+       va_end(matches);
+
+       return res;
+}
+
+static int check_term_format(const char *term, const char *orig_term)
+{
+       int res;
+       char *new_term = xstrfmt("refs/bisect/%s", term);
+
+       res = check_refname_format(new_term, 0);
+       free(new_term);
+
+       if (res)
+               return error(_("'%s' is not a valid term"), term);
+
+       if (one_of(term, "help", "start", "skip", "next", "reset",
+                       "visualize", "view", "replay", "log", "run", "terms", NULL))
+               return error(_("can't use the builtin command '%s' as a term"), term);
+
+       /*
+        * In theory, nothing prevents swapping completely good and bad,
+        * but this situation could be confusing and hasn't been tested
+        * enough. Forbid it for now.
+        */
+
+       if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) ||
+                (strcmp(orig_term, "good") && one_of(term, "good", "old", NULL)))
+               return error(_("can't change the meaning of the term '%s'"), term);
+
+       return 0;
+}
+
+static int write_terms(const char *bad, const char *good)
+{
+       FILE *fp = NULL;
+       int res;
+
+       if (!strcmp(bad, good))
+               return error(_("please use two different terms"));
+
+       if (check_term_format(bad, "bad") || check_term_format(good, "good"))
+               return -1;
+
+       fp = fopen(git_path_bisect_terms(), "w");
+       if (!fp)
+               return error_errno(_("could not open the file BISECT_TERMS"));
+
+       res = fprintf(fp, "%s\n%s\n", bad, good);
+       res |= fclose(fp);
+       return (res < 0) ? -1 : 0;
+}
+
+static int is_expected_rev(const char *expected_hex)
+{
+       struct strbuf actual_hex = STRBUF_INIT;
+       int res = 0;
+       if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) {
+               strbuf_trim(&actual_hex);
+               res = !strcmp(actual_hex.buf, expected_hex);
+       }
+       strbuf_release(&actual_hex);
+       return res;
+}
+
+static void check_expected_revs(const char **revs, int rev_nr)
+{
+       int i;
+
+       for (i = 0; i < rev_nr; i++) {
+               if (!is_expected_rev(revs[i])) {
+                       unlink_or_warn(git_path_bisect_ancestors_ok());
+                       unlink_or_warn(git_path_bisect_expected_rev());
+               }
+       }
+}
+
 int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 {
-       int next_all = 0;
+       enum {
+               NEXT_ALL = 1,
+               WRITE_TERMS,
+               BISECT_CLEAN_STATE,
+               CHECK_EXPECTED_REVS
+       } cmdmode = 0;
        int no_checkout = 0;
        struct option options[] = {
-               OPT_BOOL(0, "next-all", &next_all,
-                        N_("perform 'git bisect next'")),
+               OPT_CMDMODE(0, "next-all", &cmdmode,
+                        N_("perform 'git bisect next'"), NEXT_ALL),
+               OPT_CMDMODE(0, "write-terms", &cmdmode,
+                        N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS),
+               OPT_CMDMODE(0, "bisect-clean-state", &cmdmode,
+                        N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
+               OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
+                        N_("check for expected revs"), CHECK_EXPECTED_REVS),
                OPT_BOOL(0, "no-checkout", &no_checkout,
                         N_("update BISECT_HEAD instead of checking out the current commit")),
                OPT_END()
@@ -23,9 +132,25 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, options,
                             git_bisect_helper_usage, 0);
 
-       if (!next_all)
+       if (!cmdmode)
                usage_with_options(git_bisect_helper_usage, options);
 
-       /* next-all */
-       return bisect_next_all(prefix, no_checkout);
+       switch (cmdmode) {
+       case NEXT_ALL:
+               return bisect_next_all(prefix, no_checkout);
+       case WRITE_TERMS:
+               if (argc != 2)
+                       return error(_("--write-terms requires two arguments"));
+               return write_terms(argv[0], argv[1]);
+       case BISECT_CLEAN_STATE:
+               if (argc != 0)
+                       return error(_("--bisect-clean-state requires no arguments"));
+               return bisect_clean_state();
+       case CHECK_EXPECTED_REVS:
+               check_expected_revs(argv, argc);
+               return 0;
+       default:
+               return error("BUG: unknown subcommand '%d'", cmdmode);
+       }
+       return 0;
 }
index 67adaef4d80ee4e2fcd28755255935f0fcae6c03..005f55aaa257fc34f81517650223a3c195c03178 100644 (file)
@@ -708,8 +708,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        git_config(git_blame_config, &output_option);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
-       DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
-       DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES);
+       revs.diffopt.flags.allow_textconv = 1;
+       revs.diffopt.flags.follow_renames = 1;
 
        save_commit_buffer = 0;
        dashdash_pos = 0;
@@ -734,9 +734,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
        }
 parse_done:
-       no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES);
+       no_whole_file_rename = !revs.diffopt.flags.follow_renames;
        xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
-       DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
+       revs.diffopt.flags.follow_renames = 0;
        argc = parse_options_end(&ctx);
 
        if (incremental || (output_option & OUTPUT_PORCELAIN)) {
@@ -803,7 +803,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
        blame_date_width -= 1; /* strip the null */
 
-       if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
+       if (revs.diffopt.flags.find_copies_harder)
                opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
                        PICKAXE_BLAME_COPY_HARDER);
 
index 79dc9181fd6c0008d4b8c3b841f35cc1223d6d74..8dcc2ed058be6e653f885e48c9aa5f465a5f9749 100644 (file)
@@ -125,7 +125,7 @@ static int branch_merged(int kind, const char *name,
                if (upstream &&
                    (reference_name = reference_name_to_free =
                     resolve_refdup(upstream, RESOLVE_REF_READING,
-                                   oid.hash, NULL)) != NULL)
+                                   &oid, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(&oid);
        }
        if (!reference_rev)
@@ -241,7 +241,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                                        RESOLVE_REF_READING
                                        | RESOLVE_REF_NO_RECURSE
                                        | RESOLVE_REF_ALLOW_BAD_NAME,
-                                       oid.hash, &flags);
+                                       &oid, &flags);
                if (!target) {
                        error(remote_branch
                              ? _("remote-tracking branch '%s' not found.")
@@ -257,8 +257,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                        goto next;
                }
 
-               if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash,
-                              REF_NODEREF)) {
+               if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid,
+                              REF_NO_DEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
                              : _("Error deleting branch '%s'"),
@@ -462,8 +462,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 {
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+       const char *interpreted_oldname = NULL;
+       const char *interpreted_newname = NULL;
        int recovery = 0;
-       int clobber_head_ok;
 
        if (!oldname) {
                if (copy)
@@ -487,12 +488,18 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
         * A command like "git branch -M currentbranch currentbranch" cannot
         * cause the worktree to become inconsistent with HEAD, so allow it.
         */
-       clobber_head_ok = !strcmp(oldname, newname);
-
-       validate_new_branchname(newname, &newref, force, clobber_head_ok);
+       if (!strcmp(oldname, newname))
+               validate_branchname(newname, &newref);
+       else
+               validate_new_branchname(newname, &newref, force);
 
        reject_rebase_or_bisect_branch(oldref.buf);
 
+       if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
+           !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
+               die("BUG: expected prefix missing for refs");
+       }
+
        if (copy)
                strbuf_addf(&logmsg, "Branch: copied %s to %s",
                            oldref.buf, newref.buf);
@@ -507,11 +514,11 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 
        if (recovery) {
                if (copy)
-                       warning(_("Copied a misnamed branch '%s' away"),
-                               oldref.buf + 11);
+                       warning(_("Created a copy of a misnamed branch '%s'"),
+                               interpreted_oldname);
                else
                        warning(_("Renamed a misnamed branch '%s' away"),
-                               oldref.buf + 11);
+                               interpreted_oldname);
        }
 
        if (!copy &&
@@ -520,9 +527,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 
        strbuf_release(&logmsg);
 
-       strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
+       strbuf_addf(&oldsection, "branch.%s", interpreted_oldname);
        strbuf_release(&oldref);
-       strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
+       strbuf_addf(&newsection, "branch.%s", interpreted_newname);
        strbuf_release(&newref);
        if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
                die(_("Branch is renamed, but update of config-file failed"));
@@ -636,7 +643,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        track = git_branch_track;
 
-       head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
+       head = resolve_refdup("HEAD", 0, &head_oid, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
@@ -675,6 +682,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                copy *= 2;
        }
 
+       if (list)
+               setup_auto_pager("branch", 1);
+
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
@@ -793,9 +803,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        } else if (argc > 0 && argc <= 2) {
                struct branch *branch = branch_get(argv[0]);
 
-               if (!strcmp(argv[0], "HEAD"))
-                       die(_("it does not make sense to create 'HEAD' manually"));
-
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
 
@@ -806,7 +813,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
 
                create_branch(argv[0], (argc == 2) ? argv[1] : head,
-                             force, reflog, 0, quiet, track);
+                             force, 0, reflog, quiet, track);
 
        } else
                usage_with_options(builtin_branch_usage, options);
index 39c8be05dc4beda2f0cd179e5c5f495ce079f8d9..b0e78b819db3138f1c5ac4fe4b5d850ab306b470 100644 (file)
@@ -129,8 +129,6 @@ static const char * const builtin_checkout_index_usage[] = {
        NULL
 };
 
-static struct lock_file lock_file;
-
 static int option_parse_stage(const struct option *opt,
                              const char *arg, int unset)
 {
@@ -150,7 +148,7 @@ static int option_parse_stage(const struct option *opt,
 int cmd_checkout_index(int argc, const char **argv, const char *prefix)
 {
        int i;
-       int newfd = -1;
+       struct lock_file lock_file = LOCK_INIT;
        int all = 0;
        int read_from_stdin = 0;
        int prefix_length;
@@ -206,7 +204,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        if (index_opt && !state.base_dir_len && !to_tempfile) {
                state.refresh_cache = 1;
                state.istate = &the_index;
-               newfd = hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+               hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
        }
 
        /* Check out named files first */
@@ -251,7 +249,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        if (all)
                checkout_all(prefix, prefix_length);
 
-       if (0 <= newfd &&
+       if (is_lock_file_locked(&lock_file) &&
            write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die("Unable to write new index file");
        return 0;
index fc4f8fd2ea29c7c23d3b1c7ca0ba78824c255a91..8bdc927d3f561e62802ab53be0b3029325e6ebcd 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "config.h"
+#include "checkout.h"
 #include "lockfile.h"
 #include "parse-options.h"
 #include "refs.h"
@@ -247,7 +248,7 @@ static int checkout_paths(const struct checkout_opts *opts,
        struct object_id rev;
        struct commit *head;
        int errs = 0;
-       struct lock_file *lock_file;
+       struct lock_file lock_file = LOCK_INIT;
 
        if (opts->track != BRANCH_TRACK_UNSPECIFIED)
                die(_("'%s' cannot be used with updating paths"), "--track");
@@ -275,9 +276,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                return run_add_interactive(revision, "--patch=checkout",
                                           &opts->pathspec);
 
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-
-       hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
        if (read_cache_preload(&opts->pathspec) < 0)
                return error(_("index file corrupt"));
 
@@ -376,10 +375,10 @@ static int checkout_paths(const struct checkout_opts *opts,
        }
        errs |= finish_delayed_checkout(&state);
 
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
-       read_ref_full("HEAD", 0, rev.hash, NULL);
+       read_ref_full("HEAD", 0, &rev, NULL);
        head = lookup_commit_reference_gently(&rev, 1);
 
        errs |= post_checkout_hook(head, head, 0);
@@ -402,10 +401,16 @@ static void show_local_changes(struct object *head,
 static void describe_detached_head(const char *msg, struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
+
        if (!parse_commit(commit))
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
-       fprintf(stderr, "%s %s... %s\n", msg,
-               find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
+       if (print_sha1_ellipsis()) {
+               fprintf(stderr, "%s %s... %s\n", msg,
+                       find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
+       } else {
+               fprintf(stderr, "%s %s %s\n", msg,
+                       find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
+       }
        strbuf_release(&sb);
 }
 
@@ -472,9 +477,9 @@ static int merge_working_tree(const struct checkout_opts *opts,
                              int *writeout_error)
 {
        int ret;
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       struct lock_file lock_file = LOCK_INIT;
 
-       hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
        if (read_cache_preload(NULL) < 0)
                return error(_("index file corrupt"));
 
@@ -516,7 +521,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                }
                tree = parse_tree_indirect(old->commit ?
                                           &old->commit->object.oid :
-                                          &empty_tree_oid);
+                                          the_hash_algo->empty_tree);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
                tree = parse_tree_indirect(&new->commit->object.oid);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
@@ -591,7 +596,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
        if (!cache_tree_fully_valid(active_cache_tree))
                cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
 
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
        if (!opts->force && !opts->quiet)
@@ -642,8 +647,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                else
                        create_branch(opts->new_branch, new->name,
                                      opts->new_branch_force ? 1 : 0,
-                                     opts->new_branch_log,
                                      opts->new_branch_force ? 1 : 0,
+                                     opts->new_branch_log,
                                      opts->quiet,
                                      opts->track);
                new->name = opts->new_branch;
@@ -664,8 +669,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
        if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
                /* Nothing to do. */
        } else if (opts->force_detach || !new->path) {  /* No longer on any branch. */
-               update_ref(msg.buf, "HEAD", new->commit->object.oid.hash, NULL,
-                          REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
+               update_ref(msg.buf, "HEAD", &new->commit->object.oid, NULL,
+                          REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path &&
                            advice_detached_head && !opts->force_detach)
@@ -827,7 +832,7 @@ static int switch_branches(const struct checkout_opts *opts,
        struct object_id rev;
        int flag, writeout_error = 0;
        memset(&old, 0, sizeof(old));
-       old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag);
+       old.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
        if (old.path)
                old.commit = lookup_commit_reference_gently(&rev, 1);
        if (!(flag & REF_ISSYMREF))
@@ -874,46 +879,6 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
        return git_xmerge_config(var, value, NULL);
 }
 
-struct tracking_name_data {
-       /* const */ char *src_ref;
-       char *dst_ref;
-       struct object_id *dst_oid;
-       int unique;
-};
-
-static int check_tracking_name(struct remote *remote, void *cb_data)
-{
-       struct tracking_name_data *cb = cb_data;
-       struct refspec query;
-       memset(&query, 0, sizeof(struct refspec));
-       query.src = cb->src_ref;
-       if (remote_find_tracking(remote, &query) ||
-           get_oid(query.dst, cb->dst_oid)) {
-               free(query.dst);
-               return 0;
-       }
-       if (cb->dst_ref) {
-               free(query.dst);
-               cb->unique = 0;
-               return 0;
-       }
-       cb->dst_ref = query.dst;
-       return 0;
-}
-
-static const char *unique_tracking_name(const char *name, struct object_id *oid)
-{
-       struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
-       cb_data.src_ref = xstrfmt("refs/heads/%s", name);
-       cb_data.dst_oid = oid;
-       for_each_remote(check_tracking_name, &cb_data);
-       free(cb_data.src_ref);
-       if (cb_data.unique)
-               return cb_data.dst_ref;
-       free(cb_data.dst_ref);
-       return NULL;
-}
-
 static int parse_branchname_arg(int argc, const char **argv,
                                int dwim_new_local_branch_ok,
                                struct branch_info *new,
@@ -1038,7 +1003,7 @@ static int parse_branchname_arg(int argc, const char **argv,
        setup_branch_path(new);
 
        if (!check_refname_format(new->path, 0) &&
-           !read_ref(new->path, branch_rev.hash))
+           !read_ref(new->path, &branch_rev))
                oidcpy(rev, &branch_rev);
        else
                new->path = NULL; /* not an existing branch */
@@ -1136,7 +1101,7 @@ static int checkout_branch(struct checkout_opts *opts,
                struct object_id rev;
                int flag;
 
-               if (!read_ref_full("HEAD", 0, rev.hash, &flag) &&
+               if (!read_ref_full("HEAD", 0, &rev, &flag) &&
                    (flag & REF_ISSYMREF) && is_null_oid(&rev))
                        return switch_unborn_to_new_branch(opts);
        }
@@ -1289,11 +1254,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
 
-               opts.branch_exists =
-                       validate_new_branchname(opts.new_branch, &buf,
-                                               !!opts.new_branch_force,
-                                               !!opts.new_branch_force);
-
+               if (opts.new_branch_force)
+                       opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+               else
+                       opts.branch_exists =
+                               validate_new_branchname(opts.new_branch, &buf, 0);
                strbuf_release(&buf);
        }
 
index dbddd98f80d6660f2cde190a0e121022f079071d..2da71db10752bf95647052dcd5b7c4b28e7a50ac 100644 (file)
@@ -452,7 +452,8 @@ static void clone_local(const char *src_repo, const char *dest_repo)
 {
        if (option_shared) {
                struct strbuf alt = STRBUF_INIT;
-               strbuf_addf(&alt, "%s/objects", src_repo);
+               get_common_dir(&alt, src_repo);
+               strbuf_addstr(&alt, "/objects");
                add_to_alternates_file(alt.buf);
                strbuf_release(&alt);
        } else {
@@ -588,7 +589,7 @@ static void write_remote_refs(const struct ref *local_refs)
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
-               if (ref_transaction_create(t, r->peer_ref->name, r->old_oid.hash,
+               if (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,
                                           0, NULL, &err))
                        die("%s", err.buf);
        }
@@ -610,12 +611,12 @@ static void write_followtags(const struct ref *refs, const char *msg)
                        continue;
                if (!has_object_file(&ref->old_oid))
                        continue;
-               update_ref(msg, ref->name, ref->old_oid.hash,
-                          NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+               update_ref(msg, ref->name, &ref->old_oid, NULL, 0,
+                          UPDATE_REFS_DIE_ON_ERR);
        }
 }
 
-static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+static int iterate_ref_map(void *cb_data, struct object_id *oid)
 {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
@@ -630,7 +631,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
        if (!ref)
                return -1;
 
-       hashcpy(sha1, ref->old_oid.hash);
+       oidcpy(oid, &ref->old_oid);
        *rm = ref->next;
        return 0;
 }
@@ -682,23 +683,23 @@ static void update_head(const struct ref *our, const struct ref *remote,
                if (create_symref("HEAD", our->name, NULL) < 0)
                        die(_("unable to update HEAD"));
                if (!option_bare) {
-                       update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0,
+                       update_ref(msg, "HEAD", &our->old_oid, NULL, 0,
                                   UPDATE_REFS_DIE_ON_ERR);
                        install_branch_config(0, head, option_origin, our->name);
                }
        } else if (our) {
                struct commit *c = lookup_commit_reference(&our->old_oid);
                /* --branch specifies a non-branch (i.e. tags), detach HEAD */
-               update_ref(msg, "HEAD", c->object.oid.hash,
-                          NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
+               update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
+                          UPDATE_REFS_DIE_ON_ERR);
        } else if (remote) {
                /*
                 * We know remote HEAD points to a non-branch, or
                 * HEAD points to a branch but we don't know which one.
                 * Detach HEAD in all these cases.
                 */
-               update_ref(msg, "HEAD", remote->old_oid.hash,
-                          NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
+               update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF,
+                          UPDATE_REFS_DIE_ON_ERR);
        }
 }
 
@@ -706,7 +707,7 @@ static int checkout(int submodule_progress)
 {
        struct object_id oid;
        char *head;
-       struct lock_file *lock_file;
+       struct lock_file lock_file = LOCK_INIT;
        struct unpack_trees_options opts;
        struct tree *tree;
        struct tree_desc t;
@@ -715,7 +716,7 @@ static int checkout(int submodule_progress)
        if (option_no_checkout)
                return 0;
 
-       head = resolve_refdup("HEAD", RESOLVE_REF_READING, oid.hash, NULL);
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL);
        if (!head) {
                warning(_("remote HEAD refers to nonexistent ref, "
                          "unable to checkout.\n"));
@@ -733,8 +734,7 @@ static int checkout(int submodule_progress)
        /* We need to be in the new work tree for the checkout */
        setup_work_tree();
 
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-       hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
        memset(&opts, 0, sizeof opts);
        opts.update = 1;
@@ -750,7 +750,7 @@ static int checkout(int submodule_progress)
        if (unpack_trees(1, &t, &opts) < 0)
                die(_("unable to checkout working tree"));
 
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
        err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
@@ -1083,9 +1083,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                warning(_("--local is ignored"));
        transport->cloning = 1;
 
-       if (!transport->get_refs_list || (!is_local && !transport->fetch))
-               die(_("Don't know how to clone %s"), transport->url);
-
        transport_set_option(transport, TRANS_OPT_KEEP, "yes");
 
        if (option_depth)
index be370f64a995804ec6b62e03f7c6940044e08e24..8a877014145435516930c787dec37b8c4ac3da90 100644 (file)
@@ -118,7 +118,7 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit;
 
 /*
@@ -139,7 +139,7 @@ static const char *cleanup_arg;
 static enum commit_whence whence;
 static int sequencer_in_use;
 static int use_editor = 1, include_status = 1;
-static int show_ignored_in_status, have_option_m;
+static int have_option_m;
 static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@@ -355,7 +355,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 
                refresh_cache_or_die(refresh_flags);
 
-               if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
+               if (write_locked_index(&the_index, &index_lock, 0))
                        die(_("unable to create temporary index"));
 
                old_index_env = getenv(INDEX_ENVIRONMENT);
@@ -374,7 +374,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
                        if (reopen_lock_file(&index_lock) < 0)
                                die(_("unable to write index file"));
-                       if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
+                       if (write_locked_index(&the_index, &index_lock, 0))
                                die(_("unable to update temporary index"));
                } else
                        warning(_("Failed to update main cache tree"));
@@ -401,7 +401,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
                refresh_cache_or_die(refresh_flags);
                update_main_cache_tree(WRITE_TREE_SILENT);
-               if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
+               if (write_locked_index(&the_index, &index_lock, 0))
                        die(_("unable to write new_index file"));
                commit_style = COMMIT_NORMAL;
                ret = get_lock_file_path(&index_lock);
@@ -474,7 +474,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
        update_main_cache_tree(WRITE_TREE_SILENT);
-       if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
+       if (write_locked_index(&the_index, &index_lock, 0))
                die(_("unable to write new_index file"));
 
        hold_lock_file_for_update(&false_lock,
@@ -486,7 +486,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 
-       if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK))
+       if (write_locked_index(&the_index, &false_lock, 0))
                die(_("unable to write temporary index file"));
 
        discard_cache();
@@ -912,11 +912,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                         * submodules which were manually staged, which would
                         * be really confusing.
                         */
-                       int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
+                       struct diff_flags flags = DIFF_FLAGS_INIT;
+                       flags.override_submodule_config = 1;
                        if (ignore_submodule_arg &&
                            !strcmp(ignore_submodule_arg, "all"))
-                               diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
-                       commitable = index_differs_from(parent, diff_flags, 1);
+                               flags.ignore_submodules = 1;
+                       commitable = index_differs_from(parent, &flags, 1);
                }
        }
        strbuf_release(&committer_ident);
@@ -1075,6 +1076,19 @@ static const char *find_author_by_nickname(const char *name)
        die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
 }
 
+static void handle_ignored_arg(struct wt_status *s)
+{
+       if (!ignored_arg)
+               ; /* default already initialized */
+       else if (!strcmp(ignored_arg, "traditional"))
+               s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED;
+       else if (!strcmp(ignored_arg, "no"))
+               s->show_ignored_mode = SHOW_NO_IGNORED;
+       else if (!strcmp(ignored_arg, "matching"))
+               s->show_ignored_mode = SHOW_MATCHING_IGNORED;
+       else
+               die(_("Invalid ignored mode '%s'"), ignored_arg);
+}
 
 static void handle_untracked_files_arg(struct wt_status *s)
 {
@@ -1363,8 +1377,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                  N_("mode"),
                  N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
-               OPT_BOOL(0, "ignored", &show_ignored_in_status,
-                        N_("show ignored files")),
+               { OPTION_STRING, 0, "ignored", &ignored_arg,
+                 N_("mode"),
+                 N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"),
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" },
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
@@ -1383,8 +1399,12 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        finalize_deferred_config(&s);
 
        handle_untracked_files_arg(&s);
-       if (show_ignored_in_status)
-               s.show_ignored_files = 1;
+       handle_ignored_arg(&s);
+
+       if (s.show_ignored_mode == SHOW_MATCHING_IGNORED &&
+           s.show_untracked_files == SHOW_NO_UNTRACKED_FILES)
+               die(_("Unsupported combination of ignored and untracked-files arguments"));
+
        parse_pathspec(&s.pathspec, 0,
                       PATHSPEC_PREFER_FULL,
                       prefix, argv);
@@ -1790,9 +1810,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, "HEAD", oid.hash,
+           ref_transaction_update(transaction, "HEAD", &oid,
                                   current_head
-                                  ? current_head->object.oid.hash : null_sha1,
+                                  ? &current_head->object.oid : &null_oid,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                rollback_index_files();
index d13daeeb55927758ceec816f39412123d2ce5846..ab5f95476e6c726798fd84b4a6300577bf0fafba 100644 (file)
@@ -52,6 +52,7 @@ static int show_origin;
 #define TYPE_INT (1<<1)
 #define TYPE_BOOL_OR_INT (1<<2)
 #define TYPE_PATH (1<<3)
+#define TYPE_EXPIRY_DATE (1<<4)
 
 static struct option builtin_config_options[] = {
        OPT_GROUP(N_("Config file location")),
@@ -80,6 +81,7 @@ static struct option builtin_config_options[] = {
        OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT),
        OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
        OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH),
+       OPT_BIT(0, "expiry-date", &types, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
        OPT_GROUP(N_("Other")),
        OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
        OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
@@ -159,6 +161,11 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value
                                return -1;
                        strbuf_addstr(buf, v);
                        free((char *)v);
+               } else if (types == TYPE_EXPIRY_DATE) {
+                       timestamp_t t;
+                       if (git_config_expiry_date(&t, key_, value_) < 0)
+                               return -1;
+                       strbuf_addf(buf, "%"PRItime, t);
                } else if (value_) {
                        strbuf_addstr(buf, value_);
                } else {
@@ -273,12 +280,13 @@ static char *normalize_value(const char *key, const char *value)
        if (!value)
                return NULL;
 
-       if (types == 0 || types == TYPE_PATH)
+       if (types == 0 || types == TYPE_PATH || types == TYPE_EXPIRY_DATE)
                /*
                 * We don't do normalization for TYPE_PATH here: If
                 * the path is like ~/foobar/, we prefer to store
                 * "~/foobar/" in the config file, and to expand the ~
                 * when retrieving the value.
+                * Also don't do normalization for expiry dates.
                 */
                return xstrdup(value);
        if (types == TYPE_INT)
index 29075dbd0f884513420b646cdb06d7c0b3d33643..3b0b204b1e495626f3490d62f281291ee039ba42 100644 (file)
@@ -3,16 +3,19 @@
 #include "lockfile.h"
 #include "commit.h"
 #include "tag.h"
+#include "blob.h"
 #include "refs.h"
 #include "builtin.h"
 #include "exec_cmd.h"
 #include "parse-options.h"
+#include "revision.h"
 #include "diff.h"
 #include "hashmap.h"
 #include "argv-array.h"
 #include "run-command.h"
+#include "revision.h"
+#include "list-objects.h"
 
-#define SEEN           (1u << 0)
 #define MAX_TAGS       (FLAG_BITS - 1)
 
 static const char * const describe_usage[] = {
@@ -181,7 +184,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
        }
 
        /* Is it annotated? */
-       if (!peel_ref(path, peeled.hash)) {
+       if (!peel_ref(path, &peeled)) {
                is_annotated = !!oidcmp(oid, &peeled);
        } else {
                oidcpy(&peeled, oid);
@@ -256,7 +259,7 @@ static unsigned long finish_depth_computation(
        return seen_commits;
 }
 
-static void display_name(struct commit_name *n)
+static void append_name(struct commit_name *n, struct strbuf *dst)
 {
        if (n->prio == 2 && !n->tag) {
                n->tag = lookup_tag(&n->oid);
@@ -272,19 +275,18 @@ static void display_name(struct commit_name *n)
        }
 
        if (n->tag)
-               printf("%s", n->tag->tag);
+               strbuf_addstr(dst, n->tag->tag);
        else
-               printf("%s", n->path);
+               strbuf_addstr(dst, n->path);
 }
 
-static void show_suffix(int depth, const struct object_id *oid)
+static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst)
 {
-       printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
+       strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
 }
 
-static void describe(const char *arg, int last_one)
+static void describe_commit(struct object_id *oid, struct strbuf *dst)
 {
-       struct object_id oid;
        struct commit *cmit, *gave_up_on = NULL;
        struct commit_list *list;
        struct commit_name *n;
@@ -293,30 +295,25 @@ static void describe(const char *arg, int last_one)
        unsigned long seen_commits = 0;
        unsigned int unannotated_cnt = 0;
 
-       if (get_oid(arg, &oid))
-               die(_("Not a valid object name %s"), arg);
-       cmit = lookup_commit_reference(&oid);
-       if (!cmit)
-               die(_("%s is not a valid '%s' object"), arg, commit_type);
+       cmit = lookup_commit_reference(oid);
 
        n = find_commit_name(&cmit->object.oid);
        if (n && (tags || all || n->prio == 2)) {
                /*
                 * Exact match to an existing ref.
                 */
-               display_name(n);
+               append_name(n, dst);
                if (longformat)
-                       show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid);
+                       append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst);
                if (suffix)
-                       printf("%s", suffix);
-               printf("\n");
+                       strbuf_addstr(dst, suffix);
                return;
        }
 
        if (!max_candidates)
                die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid));
        if (debug)
-               fprintf(stderr, _("searching to describe %s\n"), arg);
+               fprintf(stderr, _("No exact match on refs or tags, searching to describe\n"));
 
        if (!have_util) {
                struct hashmap_iter iter;
@@ -381,22 +378,21 @@ static void describe(const char *arg, int last_one)
        }
 
        if (!match_cnt) {
-               struct object_id *oid = &cmit->object.oid;
+               struct object_id *cmit_oid = &cmit->object.oid;
                if (always) {
-                       printf("%s", find_unique_abbrev(oid->hash, abbrev));
+                       strbuf_addstr(dst, find_unique_abbrev(cmit_oid->hash, abbrev));
                        if (suffix)
-                               printf("%s", suffix);
-                       printf("\n");
+                               strbuf_addstr(dst, suffix);
                        return;
                }
                if (unannotated_cnt)
                        die(_("No annotated tags can describe '%s'.\n"
                            "However, there were unannotated tags: try --tags."),
-                           oid_to_hex(oid));
+                           oid_to_hex(cmit_oid));
                else
                        die(_("No tags can describe '%s'.\n"
                            "Try --always, or create some tags."),
-                           oid_to_hex(oid));
+                           oid_to_hex(cmit_oid));
        }
 
        QSORT(all_matches, match_cnt, compare_pt);
@@ -434,15 +430,86 @@ static void describe(const char *arg, int last_one)
                }
        }
 
-       display_name(all_matches[0].name);
+       append_name(all_matches[0].name, dst);
        if (abbrev)
-               show_suffix(all_matches[0].depth, &cmit->object.oid);
+               append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
        if (suffix)
-               printf("%s", suffix);
-       printf("\n");
+               strbuf_addstr(dst, suffix);
+}
+
+struct process_commit_data {
+       struct object_id current_commit;
+       struct object_id looking_for;
+       struct strbuf *dst;
+       struct rev_info *revs;
+};
+
+static void process_commit(struct commit *commit, void *data)
+{
+       struct process_commit_data *pcd = data;
+       pcd->current_commit = commit->object.oid;
+}
+
+static void process_object(struct object *obj, const char *path, void *data)
+{
+       struct process_commit_data *pcd = data;
+
+       if (!oidcmp(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
+               reset_revision_walk();
+               describe_commit(&pcd->current_commit, pcd->dst);
+               strbuf_addf(pcd->dst, ":%s", path);
+               free_commit_list(pcd->revs->commits);
+               pcd->revs->commits = NULL;
+       }
+}
+
+static void describe_blob(struct object_id oid, struct strbuf *dst)
+{
+       struct rev_info revs;
+       struct argv_array args = ARGV_ARRAY_INIT;
+       struct process_commit_data pcd = { null_oid, oid, dst, &revs};
+
+       argv_array_pushl(&args, "internal: The first arg is not parsed",
+               "--objects", "--in-commit-order", "--reverse", "HEAD",
+               NULL);
+
+       init_revisions(&revs, NULL);
+       if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1)
+               BUG("setup_revisions could not handle all args?");
+
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+
+       traverse_commit_list(&revs, process_commit, process_object, &pcd);
+       reset_revision_walk();
+}
+
+static void describe(const char *arg, int last_one)
+{
+       struct object_id oid;
+       struct commit *cmit;
+       struct strbuf sb = STRBUF_INIT;
+
+       if (debug)
+               fprintf(stderr, _("describe %s\n"), arg);
+
+       if (get_oid(arg, &oid))
+               die(_("Not a valid object name %s"), arg);
+       cmit = lookup_commit_reference_gently(&oid, 1);
+
+       if (cmit)
+               describe_commit(&oid, &sb);
+       else if (lookup_blob(&oid))
+               describe_blob(oid, &sb);
+       else
+               die(_("%s is neither a commit nor blob"), arg);
+
+       puts(sb.buf);
 
        if (!last_one)
                clear_commit_marks(cmit, -1);
+
+       strbuf_release(&sb);
 }
 
 int cmd_describe(int argc, const char **argv, const char *prefix)
@@ -543,7 +610,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                        }
                } else if (dirty) {
                        static struct lock_file index_lock;
-                       int fd;
+                       struct rev_info revs;
+                       struct argv_array args = ARGV_ARRAY_INIT;
+                       int fd, result;
 
                        read_cache_preload(NULL);
                        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED,
@@ -552,8 +621,13 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                        if (0 <= fd)
                                update_index_if_able(&the_index, &index_lock);
 
-                       if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1,
-                                           diff_index_args, prefix))
+                       init_revisions(&revs, prefix);
+                       argv_array_pushv(&args, diff_index_args);
+                       if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1)
+                               BUG("malformed internal diff-index command line");
+                       result = run_diff_index(&revs, 0);
+
+                       if (!diff_result_code(&revs.diffopt, result))
                                suffix = NULL;
                        else
                                suffix = dirty;
index d66499909e82e255a8d1356b606f9b901935317f..b775a756470ddc365907fae53dcc949bcaae7ca5 100644 (file)
@@ -110,6 +110,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
 
        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
        init_revisions(opt, prefix);
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
        opt->abbrev = 0;
        opt->diff = 1;
        opt->disable_stdin = 1;
index f5bbd4d757e12e6e34a86fac006298acb771909f..16bfb22f7381ee8e6967ab836686c5def7cff892 100644 (file)
@@ -44,7 +44,7 @@ static void stuff_change(struct diff_options *opt,
            !oidcmp(old_oid, new_oid) && (old_mode == new_mode))
                return;
 
-       if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
+       if (opt->flags.reverse_diff) {
                SWAP(old_mode, new_mode);
                SWAP(old_oid, new_oid);
                SWAP(old_path, new_path);
@@ -203,17 +203,16 @@ static int builtin_diff_combined(struct rev_info *revs,
 
 static void refresh_index_quietly(void)
 {
-       struct lock_file *lock_file;
+       struct lock_file lock_file = LOCK_INIT;
        int fd;
 
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-       fd = hold_locked_index(lock_file, 0);
+       fd = hold_locked_index(&lock_file, 0);
        if (fd < 0)
                return;
        discard_cache();
        read_cache();
        refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
-       update_index_if_able(&the_index, lock_file);
+       update_index_if_able(&the_index, &lock_file);
 }
 
 static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
@@ -350,8 +349,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        rev.diffopt.stat_graph_width = -1;
 
        /* Default to let external and textconv be used */
-       DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
-       DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
+       rev.diffopt.flags.allow_external = 1;
+       rev.diffopt.flags.allow_textconv = 1;
 
        if (nongit)
                die(_("Not a git repository"));
@@ -361,7 +360,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                diff_setup_done(&rev.diffopt);
        }
 
-       DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
+       rev.diffopt.flags.recursive = 1;
 
        setup_diff_pager(&rev.diffopt);
 
@@ -380,7 +379,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                                add_head_to_pending(&rev);
                                if (!rev.pending.nr) {
                                        struct tree *tree;
-                                       tree = lookup_tree(&empty_tree_oid);
+                                       tree = lookup_tree(the_hash_algo->empty_tree);
                                        add_pending_object(&rev, &tree->object, "HEAD");
                                }
                                break;
index b2d3ba7539d5b9cdc7ec6f910ee24510e709f76c..bcc79d1888f2217bcb380ffb1e7178c100a41e8e 100644 (file)
@@ -616,7 +616,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                        if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
                            write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
                                ret = error("could not write %s", buf.buf);
-                               rollback_lock_file(&lock);
                                goto finish;
                        }
                        changed_files(&wt_modified, buf.buf, workdir);
index 2fb60d6d48eae68affdcf64f6dbab0282408f393..796d0cd66c7750a20613dea77cd75b1b8e7665a4 100644 (file)
@@ -823,7 +823,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
                if (e->flags & UNINTERESTING)
                        continue;
 
-               if (dwim_ref(e->name, strlen(e->name), oid.hash, &full_name) != 1)
+               if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1)
                        continue;
 
                if (refspecs) {
@@ -895,7 +895,7 @@ static void export_marks(char *file)
 {
        unsigned int i;
        uint32_t mark;
-       struct object_decoration *deco = idnums.hash;
+       struct decoration_entry *deco = idnums.entries;
        FILE *f;
        int e = 0;
 
@@ -1066,7 +1066,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                die("revision walk setup failed");
        revs.diffopt.format_callback = show_filemodify;
        revs.diffopt.format_callback_data = &paths_of_changed_objects;
-       DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+       revs.diffopt.flags.recursive = 1;
        while ((commit = get_revision(&revs))) {
                if (has_unshown_parent(commit)) {
                        add_object_array(&commit->object, NULL, &commits);
index 225c734924f14854784cbc1e310f2fb817f1f38b..7bbcd26faf1fe650ccc7dbbd526db5114aac0c8f 100644 (file)
@@ -3,6 +3,7 @@
  */
 #include "cache.h"
 #include "config.h"
+#include "repository.h"
 #include "refs.h"
 #include "commit.h"
 #include "builtin.h"
@@ -457,8 +458,8 @@ static int s_update_ref(const char *action,
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, ref->name,
-                                  ref->new_oid.hash,
-                                  check_old ? ref->old_oid.hash : NULL,
+                                  &ref->new_oid,
+                                  check_old ? &ref->old_oid : NULL,
                                   0, msg, &err))
                goto fail;
 
@@ -727,7 +728,7 @@ static int update_local_ref(struct ref *ref,
        }
 }
 
-static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+static int iterate_ref_map(void *cb_data, struct object_id *oid)
 {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
@@ -737,7 +738,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
        if (!ref)
                return -1; /* end of the list */
        *rm = ref->next;
-       hashcpy(sha1, ref->old_oid.hash);
+       oidcpy(oid, &ref->old_oid);
        return 0;
 }
 
@@ -1094,9 +1095,6 @@ static int do_fetch(struct transport *transport,
                        tags = TAGS_UNSET;
        }
 
-       if (!transport->get_refs_list || !transport->fetch)
-               die(_("Don't know how to fetch from %s"), transport->url);
-
        /* if not appending, truncate FETCH_HEAD */
        if (!append && !dry_run) {
                retcode = truncate_fetch_head();
@@ -1397,7 +1395,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                struct argv_array options = ARGV_ARRAY_INIT;
 
                add_options_to_argv(&options);
-               result = fetch_populated_submodules(&options,
+               result = fetch_populated_submodules(the_repository,
+                                                   &options,
                                                    submodule_prefix,
                                                    recurse_submodules,
                                                    recurse_submodules_default,
index 27a2361e91dcd94cba4556b04b028286835f8823..8e8a15ea4ad6de2bb73f63d39a0895a263918774 100644 (file)
@@ -377,7 +377,8 @@ static void shortlog(const char *name,
                        string_list_append(&subjects,
                                           oid_to_hex(&commit->object.oid));
                else
-                       string_list_append(&subjects, strbuf_detach(&sb, NULL));
+                       string_list_append_nodup(&subjects,
+                                                strbuf_detach(&sb, NULL));
        }
 
        if (opts->credit_people)
@@ -603,7 +604,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 
        /* get current branch */
        current_branch = current_branch_to_free =
-               resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL);
+               resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
        if (!current_branch)
                die("No current branch");
        if (starts_with(current_branch, "refs/heads/"))
index 56afe405b8072b3099a23661ac586f673e54ff0e..04846d46f91d6cde14d379da8e0e3dd458c08dc5 100644 (file)
@@ -555,7 +555,7 @@ static int fsck_head_link(void)
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
-       head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL);
+       head_points_at = resolve_ref_unsafe("HEAD", 0, &head_oid, NULL);
        if (!head_points_at) {
                errors_found |= ERROR_REFS;
                return error("Invalid HEAD");
@@ -759,6 +759,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 
        if (keep_cache_objects) {
                verify_index_checksum = 1;
+               verify_ce_order = 1;
                read_cache();
                for (i = 0; i < active_nr; i++) {
                        unsigned int mode;
index 5a6cfe6b45b06b6b75ea399a83cb8dcf7e197141..3ca4ac80d8c7014e7e9aba228f542113a5103b48 100644 (file)
@@ -1015,6 +1015,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                       prefix, argv + i);
        pathspec.max_depth = opt.max_depth;
        pathspec.recursive = 1;
+       pathspec.recurse_submodules = !!recurse_submodules;
 
 #ifndef NO_PTHREADS
        if (list.nr || cached || show_in_pager)
index 8ec459f522522897c9ed4def4e72b4263d4c93e8..4c51aec81f374d52e7c6a1afa73ab9dea3cf40b8 100644 (file)
@@ -1660,10 +1660,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                from_stdin = 1;
                        } else if (!strcmp(arg, "--fix-thin")) {
                                fix_thin_pack = 1;
-                       } else if (!strcmp(arg, "--strict")) {
-                               strict = 1;
-                               do_fsck_object = 1;
-                       } else if (skip_prefix(arg, "--strict=", &arg)) {
+                       } else if (skip_to_optional_arg(arg, "--strict", &arg)) {
                                strict = 1;
                                do_fsck_object = 1;
                                fsck_set_msg_types(&fsck_options, arg);
@@ -1679,10 +1676,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                verify = 1;
                                show_stat = 1;
                                stat_only = 1;
-                       } else if (!strcmp(arg, "--keep")) {
-                               keep_msg = "";
-                       } else if (starts_with(arg, "--keep=")) {
-                               keep_msg = arg + 7;
+                       } else if (skip_to_optional_arg(arg, "--keep", &keep_msg)) {
+                               ; /* nothing to do */
                        } else if (starts_with(arg, "--threads=")) {
                                char *end;
                                nr_threads = strtoul(arg+10, &end, 0);
index d81a09051ead0bb14161c25ef30bf8b0348b3198..14fdf39165d20602ad8b682b55660ec2466c1d20 100644 (file)
@@ -121,20 +121,19 @@ static void cmd_log_init_defaults(struct rev_info *rev)
        if (fmt_pretty)
                get_commit_format(fmt_pretty, rev);
        if (default_follow)
-               DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES);
+               rev->diffopt.flags.default_follow_renames = 1;
        rev->verbose_header = 1;
-       DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+       rev->diffopt.flags.recursive = 1;
        rev->diffopt.stat_width = -1; /* use full terminal width */
        rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */
        rev->abbrev_commit = default_abbrev_commit;
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
        rev->show_signature = default_show_signature;
-       DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
+       rev->diffopt.flags.allow_textconv = 1;
 
        if (default_date_mode)
                parse_date_format(default_date_mode, &rev->date_mode);
-       rev->diffopt.touched_flags = 0;
 }
 
 static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
@@ -143,11 +142,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
        struct userformat_want w;
        int quiet = 0, source = 0, mailmap = 0;
        static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
+       static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
+       static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
+       struct decoration_filter decoration_filter = {&decorate_refs_include,
+                                                     &decorate_refs_exclude};
 
        const struct option builtin_log_options[] = {
                OPT__QUIET(&quiet, N_("suppress diff output")),
                OPT_BOOL(0, "source", &source, N_("show source")),
                OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+               OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
+                               N_("pattern"), N_("only decorate refs that match <pattern>")),
+               OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude,
+                               N_("pattern"), N_("do not decorate refs that match <pattern>")),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
                  PARSE_OPT_OPTARG, decorate_callback},
                OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
@@ -182,7 +189,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
                init_display_notes(&rev->notes_opt);
 
        if (rev->diffopt.pickaxe || rev->diffopt.filter ||
-           DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES))
+           rev->diffopt.flags.follow_renames)
                rev->always_show_header = 0;
 
        if (source)
@@ -206,7 +213,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 
        if (decoration_style) {
                rev->show_decorations = 1;
-               load_ref_decorations(decoration_style);
+               load_ref_decorations(&decoration_filter, decoration_style);
        }
 
        if (rev->line_level_traverse)
@@ -392,7 +399,7 @@ static int cmd_log_walk(struct rev_info *rev)
                fclose(rev->diffopt.file);
 
        if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
-           DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
+           rev->diffopt.flags.check_failed) {
                return 02;
        }
        return diff_result_code(&rev->diffopt, 0);
@@ -484,8 +491,8 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
        unsigned long size;
 
        fflush(rev->diffopt.file);
-       if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) ||
-           !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
+       if (!rev->diffopt.flags.textconv_set_via_cmdline ||
+           !rev->diffopt.flags.allow_textconv)
                return stream_blob_to_fd(1, oid, NULL, 0);
 
        if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH,
@@ -667,9 +674,9 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
 static void log_setup_revisions_tweak(struct rev_info *rev,
                                      struct setup_revision_opt *opt)
 {
-       if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) &&
+       if (rev->diffopt.flags.default_follow_renames &&
            rev->prune_data.nr == 1)
-               DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES);
+               rev->diffopt.flags.follow_renames = 1;
 
        /* Turn --cc/-c into -p --cc/-c when -p was not given */
        if (!rev->diffopt.output_format && rev->combine_merges)
@@ -975,7 +982,7 @@ static char *find_branch_name(struct rev_info *rev)
                return NULL;
        ref = rev->cmdline.rev[positive].name;
        tip_oid = &rev->cmdline.rev[positive].item->oid;
-       if (dwim_ref(ref, strlen(ref), branch_oid.hash, &full_ref) &&
+       if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) &&
            skip_prefix(full_ref, "refs/heads/", &v) &&
            !oidcmp(tip_oid, &branch_oid))
                branch = xstrdup(v);
@@ -1341,7 +1348,7 @@ static void prepare_bases(struct base_tree_info *bases,
                return;
 
        diff_setup(&diffopt);
-       DIFF_OPT_SET(&diffopt, RECURSIVE);
+       diffopt.flags.recursive = 1;
        diff_setup_done(&diffopt);
 
        oidcpy(&bases->base_commit, &base->object.oid);
@@ -1512,7 +1519,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        rev.verbose_header = 1;
        rev.diff = 1;
        rev.max_parents = 1;
-       DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
+       rev.diffopt.flags.recursive = 1;
        rev.subject_prefix = fmt_patch_subject_prefix;
        memset(&s_r_opt, 0, sizeof(s_r_opt));
        s_r_opt.def = "HEAD";
@@ -1613,8 +1620,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        rev.zero_commit = zero_commit;
 
-       if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
-               DIFF_OPT_SET(&rev.diffopt, BINARY);
+       if (!rev.diffopt.flags.text && !no_binary_diff)
+               rev.diffopt.flags.binary = 1;
 
        if (rev.show_notes)
                init_display_notes(&rev.notes_opt);
index 8c713c47acccf0a3a50ec8a0b822aafae8d6bada..2fc836e33086d5b70b86a3fc746f9e75706ef7ea 100644 (file)
@@ -31,6 +31,7 @@ static int show_resolve_undo;
 static int show_modified;
 static int show_killed;
 static int show_valid_bit;
+static int show_fsmonitor_bit;
 static int line_terminator = '\n';
 static int debug_mode;
 static int show_eol;
@@ -86,7 +87,8 @@ static const char *get_tag(const struct cache_entry *ce, const char *tag)
 {
        static char alttag[4];
 
-       if (tag && *tag && show_valid_bit && (ce->ce_flags & CE_VALID)) {
+       if (tag && *tag && ((show_valid_bit && (ce->ce_flags & CE_VALID)) ||
+               (show_fsmonitor_bit && (ce->ce_flags & CE_FSMONITOR_VALID)))) {
                memcpy(alttag, tag, 3);
 
                if (isalpha(tag[0])) {
@@ -515,6 +517,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                        N_("identify the file status with tags")),
                OPT_BOOL('v', NULL, &show_valid_bit,
                        N_("use lowercase letters for 'assume unchanged' files")),
+               OPT_BOOL('f', NULL, &show_fsmonitor_bit,
+                       N_("use lowercase letters for 'fsmonitor clean' files")),
                OPT_BOOL('c', "cached", &show_cached,
                        N_("show cached files in the output (default)")),
                OPT_BOOL('d', "deleted", &show_deleted,
@@ -584,7 +588,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
        for (i = 0; i < exclude_list.nr; i++) {
                add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
        }
-       if (show_tag || show_valid_bit) {
+       if (show_tag || show_valid_bit || show_fsmonitor_bit) {
                tag_cached = "H ";
                tag_unmerged = "M ";
                tag_removed = "R ";
index 24f6c7193546b91a8644a4417bfc6d6d94da6a62..3b7600150b66c4bf814e21b74b2d931f44c83805 100644 (file)
@@ -158,7 +158,7 @@ static int handle_fork_point(int argc, const char **argv)
        struct commit_list *bases;
        int i, ret = 0;
 
-       switch (dwim_ref(argv[0], strlen(argv[0]), oid.hash, &refname)) {
+       switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
        case 0:
                die("No such ref: '%s'", argv[0]);
        case 1:
index 684411694f64ca216fb03430df2d2a0ec9ea93ff..c84c6e05e922f09692b9b133cd391b850c7951df 100644 (file)
@@ -9,26 +9,24 @@
  */
 #include "git-compat-util.h"
 #include "builtin.h"
+#include "diff.h"
 
 static const char builtin_merge_ours_usage[] =
        "git merge-ours <base>... -- HEAD <remote>...";
 
-static const char *diff_index_args[] = {
-       "diff-index", "--quiet", "--cached", "HEAD", "--", NULL
-};
-#define NARGS (ARRAY_SIZE(diff_index_args) - 1)
-
 int cmd_merge_ours(int argc, const char **argv, const char *prefix)
 {
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(builtin_merge_ours_usage);
 
        /*
-        * We need to exit with 2 if the index does not match our HEAD tree,
-        * because the current index is what we will be committing as the
-        * merge result.
+        * The contents of the current index becomes the tree we
+        * commit.  The index must match HEAD, or this merge cannot go
+        * through.
         */
-       if (cmd_diff_index(NARGS, diff_index_args, prefix))
+       if (read_cache() < 0)
+               die_errno("read_cache failed");
+       if (index_differs_from("HEAD", NULL, 0))
                exit(2);
        exit(0);
 }
index fbbf2a9e5eb9be47d8574ca0bab175c5a6c0d15d..30264cfd7c1720aee814699eb3ca057e292b082d 100644 (file)
@@ -405,9 +405,8 @@ static void finish(struct commit *head_commit,
                        printf(_("No merge message -- not updating HEAD\n"));
                else {
                        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
-                       update_ref(reflog_message.buf, "HEAD",
-                               new_head->hash, head->hash, 0,
-                               UPDATE_REFS_DIE_ON_ERR);
+                       update_ref(reflog_message.buf, "HEAD", new_head, head,
+                                  0, UPDATE_REFS_DIE_ON_ERR);
                        /*
                         * We ignore errors in 'gc --auto', since the
                         * user should see them.
@@ -455,7 +454,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
-       if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) {
+       if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) {
                if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
                                    oid_to_hex(&branch_head), remote);
@@ -568,6 +567,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 
        if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
                show_diffstat = git_config_bool(k, v);
+       else if (!strcmp(k, "merge.verifysignatures"))
+               verify_signatures = git_config_bool(k, v);
        else if (!strcmp(k, "pull.twohead"))
                return git_config_string(&pull_twohead, k, v);
        else if (!strcmp(k, "pull.octopus"))
@@ -1144,7 +1145,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
+       branch = branch_to_free = resolve_refdup("HEAD", 0, &head_oid, NULL);
        if (branch)
                skip_prefix(branch, "refs/heads/", &branch);
        if (!branch || is_null_oid(&head_oid))
@@ -1262,8 +1263,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        die(_("Can merge only exactly one commit into empty head"));
                remote_head_oid = &remoteheads->item->object.oid;
                read_empty(remote_head_oid->hash, 0);
-               update_ref("initial pull", "HEAD", remote_head_oid->hash,
-                          NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+               update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0,
+                          UPDATE_REFS_DIE_ON_ERR);
                goto done;
        }
 
@@ -1358,8 +1359,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                free(list);
        }
 
-       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash,
-                  NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+       update_ref("updating ORIG_HEAD", "ORIG_HEAD",
+                  &head_commit->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 
        if (remoteheads && !common) {
                /* No common ancestors found. */
index ffdd5f01a190b99407d1019972e3198c42b7194c..cf3684d907a2fe3408581502c682d6290c790827 100644 (file)
@@ -291,7 +291,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        }
 
        if (gitmodules_modified)
-               stage_updated_gitmodules();
+               stage_updated_gitmodules(&the_index);
 
        if (active_cache_changed &&
            write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
index e435286dd8307ad6555ad5bd8a57fe799fa200f8..7c8176164561be1d57771ad2fdeb9260cc1d8c87 100644 (file)
@@ -12,7 +12,7 @@
 #include "builtin.h"
 #include "notes.h"
 #include "blob.h"
-#include "commit.h"
+#include "pretty.h"
 #include "refs.h"
 #include "exec_cmd.h"
 #include "run-command.h"
@@ -33,7 +33,7 @@ static const char * const git_notes_usage[] = {
        N_("git notes merge --commit [-v | -q]"),
        N_("git notes merge --abort [-v | -q]"),
        N_("git notes [--ref <notes-ref>] remove [<object>...]"),
-       N_("git notes [--ref <notes-ref>] prune [-n | -v]"),
+       N_("git notes [--ref <notes-ref>] prune [-n] [-v]"),
        N_("git notes [--ref <notes-ref>] get-ref"),
        NULL
 };
@@ -686,7 +686,7 @@ static int merge_abort(struct notes_merge_options *o)
 
        if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0))
                ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL"));
-       if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF))
+       if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NO_DEREF))
                ret += error(_("failed to delete ref NOTES_MERGE_REF"));
        if (notes_merge_abort(o))
                ret += error(_("failed to remove 'git notes merge' worktree"));
@@ -724,7 +724,7 @@ static int merge_commit(struct notes_merge_options *o)
        init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
 
        o->local_ref = local_ref_to_free =
-               resolve_refdup("NOTES_MERGE_REF", 0, oid.hash, NULL);
+               resolve_refdup("NOTES_MERGE_REF", 0, &oid, NULL);
        if (!o->local_ref)
                die(_("failed to resolve NOTES_MERGE_REF"));
 
@@ -736,8 +736,8 @@ static int merge_commit(struct notes_merge_options *o)
        format_commit_message(partial, "%s", &msg, &pretty_ctx);
        strbuf_trim(&msg);
        strbuf_insert(&msg, 0, "notes: ", 7);
-       update_ref(msg.buf, o->local_ref, oid.hash,
-                  is_null_oid(&parent_oid) ? NULL : parent_oid.hash,
+       update_ref(msg.buf, o->local_ref, &oid,
+                  is_null_oid(&parent_oid) ? NULL : &parent_oid,
                   0, UPDATE_REFS_DIE_ON_ERR);
 
        free_notes(t);
@@ -850,12 +850,12 @@ static int merge(int argc, const char **argv, const char *prefix)
 
        if (result >= 0) /* Merge resulted (trivially) in result_oid */
                /* Update default notes ref with new commit */
-               update_ref(msg.buf, default_notes_ref(), result_oid.hash, NULL,
-                          0, UPDATE_REFS_DIE_ON_ERR);
+               update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0,
+                          UPDATE_REFS_DIE_ON_ERR);
        else { /* Merge has unresolved conflicts */
                const struct worktree *wt;
                /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
-               update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_oid.hash, NULL,
+               update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL,
                           0, UPDATE_REFS_DIE_ON_ERR);
                /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
                wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
index 6e77dfd44439f4c5f928a73dce3233ff6e63c3b4..6b9cfc289d87b543b747c4024504703a2e0d6641 100644 (file)
@@ -15,6 +15,8 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
 #include "pack-objects.h"
 #include "progress.h"
 #include "refs.h"
@@ -79,6 +81,15 @@ static unsigned long cache_max_small_delta_size = 1000;
 
 static unsigned long window_memory_limit = 0;
 
+static struct list_objects_filter_options filter_options;
+
+enum missing_action {
+       MA_ERROR = 0,    /* fail if any missing objects are encountered */
+       MA_ALLOW_ANY,    /* silently allow ALL missing objects */
+};
+static enum missing_action arg_missing_action;
+static show_object_fn fn_show_object;
+
 /*
  * stats
  */
@@ -151,7 +162,7 @@ static unsigned long do_compress(void **pptr, unsigned long size)
 }
 
 static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f,
-                                          const unsigned char *sha1)
+                                          const struct object_id *oid)
 {
        git_zstream stream;
        unsigned char ibuf[1024 * 16];
@@ -165,7 +176,7 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi
                int zret = Z_OK;
                readlen = read_istream(st, ibuf, sizeof(ibuf));
                if (readlen == -1)
-                       die(_("unable to read %s"), sha1_to_hex(sha1));
+                       die(_("unable to read %s"), oid_to_hex(oid));
 
                stream.next_in = ibuf;
                stream.avail_in = readlen;
@@ -339,7 +350,7 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
                sha1write(f, header, hdrlen);
        }
        if (st) {
-               datalen = write_large_blob_data(st, f, entry->idx.oid.hash);
+               datalen = write_large_blob_data(st, f, &entry->idx.oid);
                close_istream(st);
        } else {
                sha1write(f, buf, datalen);
@@ -557,13 +568,13 @@ static enum write_one_status write_one(struct sha1file *f,
 static int mark_tagged(const char *path, const struct object_id *oid, int flag,
                       void *cb_data)
 {
-       unsigned char peeled[20];
+       struct object_id peeled;
        struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL);
 
        if (entry)
                entry->tagged = 1;
-       if (!peel_ref(path, peeled)) {
-               entry = packlist_find(&to_pack, peeled, NULL);
+       if (!peel_ref(path, &peeled)) {
+               entry = packlist_find(&to_pack, peeled.hash, NULL);
                if (entry)
                        entry->tagged = 1;
        }
@@ -792,7 +803,7 @@ static void write_pack_file(void)
        write_order = compute_write_order();
 
        do {
-               unsigned char sha1[20];
+               struct object_id oid;
                char *pack_tmp_name = NULL;
 
                if (pack_to_stdout)
@@ -823,13 +834,13 @@ static void write_pack_file(void)
                 * If so, rewrite it like in fast-import
                 */
                if (pack_to_stdout) {
-                       sha1close(f, sha1, CSUM_CLOSE);
+                       sha1close(f, oid.hash, CSUM_CLOSE);
                } else if (nr_written == nr_remaining) {
-                       sha1close(f, sha1, CSUM_FSYNC);
+                       sha1close(f, oid.hash, CSUM_FSYNC);
                } else {
-                       int fd = sha1close(f, sha1, 0);
-                       fixup_pack_header_footer(fd, sha1, pack_tmp_name,
-                                                nr_written, sha1, offset);
+                       int fd = sha1close(f, oid.hash, 0);
+                       fixup_pack_header_footer(fd, oid.hash, pack_tmp_name,
+                                                nr_written, oid.hash, offset);
                        close(fd);
                        if (write_bitmap_index) {
                                warning(_(no_split_warning));
@@ -863,16 +874,16 @@ static void write_pack_file(void)
                        strbuf_addf(&tmpname, "%s-", base_name);
 
                        if (write_bitmap_index) {
-                               bitmap_writer_set_checksum(sha1);
+                               bitmap_writer_set_checksum(oid.hash);
                                bitmap_writer_build_type_index(written_list, nr_written);
                        }
 
                        finish_tmp_packfile(&tmpname, pack_tmp_name,
                                            written_list, nr_written,
-                                           &pack_idx_opts, sha1);
+                                           &pack_idx_opts, oid.hash);
 
                        if (write_bitmap_index) {
-                               strbuf_addf(&tmpname, "%s.bitmap", sha1_to_hex(sha1));
+                               strbuf_addf(&tmpname, "%s.bitmap", oid_to_hex(&oid));
 
                                stop_progress(&progress_state);
 
@@ -887,7 +898,7 @@ static void write_pack_file(void)
 
                        strbuf_release(&tmpname);
                        free(pack_tmp_name);
-                       puts(sha1_to_hex(sha1));
+                       puts(oid_to_hex(&oid));
                }
 
                /* mark written objects as written to previous pack */
@@ -928,13 +939,13 @@ static int no_try_delta(const char *path)
  * found the item, since that saves us from having to look it up again a
  * few lines later when we want to add the new entry.
  */
-static int have_duplicate_entry(const unsigned char *sha1,
+static int have_duplicate_entry(const struct object_id *oid,
                                int exclude,
                                uint32_t *index_pos)
 {
        struct object_entry *entry;
 
-       entry = packlist_find(&to_pack, sha1, index_pos);
+       entry = packlist_find(&to_pack, oid->hash, index_pos);
        if (!entry)
                return 0;
 
@@ -990,7 +1001,7 @@ static int want_found_object(int exclude, struct packed_git *p)
  * function finds if there is any pack that has the object and returns the pack
  * and its offset in these variables.
  */
-static int want_object_in_pack(const unsigned char *sha1,
+static int want_object_in_pack(const struct object_id *oid,
                               int exclude,
                               struct packed_git **found_pack,
                               off_t *found_offset)
@@ -998,7 +1009,7 @@ static int want_object_in_pack(const unsigned char *sha1,
        struct mru_entry *entry;
        int want;
 
-       if (!exclude && local && has_loose_object_nonlocal(sha1))
+       if (!exclude && local && has_loose_object_nonlocal(oid->hash))
                return 0;
 
        /*
@@ -1019,7 +1030,7 @@ static int want_object_in_pack(const unsigned char *sha1,
                if (p == *found_pack)
                        offset = *found_offset;
                else
-                       offset = find_pack_entry_one(sha1, p);
+                       offset = find_pack_entry_one(oid->hash, p);
 
                if (offset) {
                        if (!*found_pack) {
@@ -1039,7 +1050,7 @@ static int want_object_in_pack(const unsigned char *sha1,
        return 1;
 }
 
-static void create_object_entry(const unsigned char *sha1,
+static void create_object_entry(const struct object_id *oid,
                                enum object_type type,
                                uint32_t hash,
                                int exclude,
@@ -1050,7 +1061,7 @@ static void create_object_entry(const unsigned char *sha1,
 {
        struct object_entry *entry;
 
-       entry = packlist_alloc(&to_pack, sha1, index_pos);
+       entry = packlist_alloc(&to_pack, oid->hash, index_pos);
        entry->hash = hash;
        if (type)
                entry->type = type;
@@ -1070,17 +1081,17 @@ static const char no_closure_warning[] = N_(
 "disabling bitmap writing, as some objects are not being packed"
 );
 
-static int add_object_entry(const unsigned char *sha1, enum object_type type,
+static int add_object_entry(const struct object_id *oid, enum object_type type,
                            const char *name, int exclude)
 {
        struct packed_git *found_pack = NULL;
        off_t found_offset = 0;
        uint32_t index_pos;
 
-       if (have_duplicate_entry(sha1, exclude, &index_pos))
+       if (have_duplicate_entry(oid, exclude, &index_pos))
                return 0;
 
-       if (!want_object_in_pack(sha1, exclude, &found_pack, &found_offset)) {
+       if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) {
                /* The pack is missing an object, so it will not have closure */
                if (write_bitmap_index) {
                        warning(_(no_closure_warning));
@@ -1089,7 +1100,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
                return 0;
        }
 
-       create_object_entry(sha1, type, pack_name_hash(name),
+       create_object_entry(oid, type, pack_name_hash(name),
                            exclude, name && no_try_delta(name),
                            index_pos, found_pack, found_offset);
 
@@ -1097,27 +1108,27 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
        return 1;
 }
 
-static int add_object_entry_from_bitmap(const unsigned char *sha1,
+static int add_object_entry_from_bitmap(const struct object_id *oid,
                                        enum object_type type,
                                        int flags, uint32_t name_hash,
                                        struct packed_git *pack, off_t offset)
 {
        uint32_t index_pos;
 
-       if (have_duplicate_entry(sha1, 0, &index_pos))
+       if (have_duplicate_entry(oid, 0, &index_pos))
                return 0;
 
-       if (!want_object_in_pack(sha1, 0, &pack, &offset))
+       if (!want_object_in_pack(oid, 0, &pack, &offset))
                return 0;
 
-       create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset);
+       create_object_entry(oid, type, name_hash, 0, 0, index_pos, pack, offset);
 
        display_progress(progress_state, nr_result);
        return 1;
 }
 
 struct pbase_tree_cache {
-       unsigned char sha1[20];
+       struct object_id oid;
        int ref;
        int temporary;
        void *tree_data;
@@ -1125,9 +1136,9 @@ struct pbase_tree_cache {
 };
 
 static struct pbase_tree_cache *(pbase_tree_cache[256]);
-static int pbase_tree_cache_ix(const unsigned char *sha1)
+static int pbase_tree_cache_ix(const struct object_id *oid)
 {
-       return sha1[0] % ARRAY_SIZE(pbase_tree_cache);
+       return oid->hash[0] % ARRAY_SIZE(pbase_tree_cache);
 }
 static int pbase_tree_cache_ix_incr(int ix)
 {
@@ -1144,14 +1155,14 @@ static struct pbase_tree {
        struct pbase_tree_cache pcache;
 } *pbase_tree;
 
-static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
+static struct pbase_tree_cache *pbase_tree_get(const struct object_id *oid)
 {
        struct pbase_tree_cache *ent, *nent;
        void *data;
        unsigned long size;
        enum object_type type;
        int neigh;
-       int my_ix = pbase_tree_cache_ix(sha1);
+       int my_ix = pbase_tree_cache_ix(oid);
        int available_ix = -1;
 
        /* pbase-tree-cache acts as a limited hashtable.
@@ -1160,7 +1171,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
         */
        for (neigh = 0; neigh < 8; neigh++) {
                ent = pbase_tree_cache[my_ix];
-               if (ent && !hashcmp(ent->sha1, sha1)) {
+               if (ent && !oidcmp(&ent->oid, oid)) {
                        ent->ref++;
                        return ent;
                }
@@ -1176,7 +1187,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
        /* Did not find one.  Either we got a bogus request or
         * we need to read and perhaps cache.
         */
-       data = read_sha1_file(sha1, &type, &size);
+       data = read_sha1_file(oid->hash, &type, &size);
        if (!data)
                return NULL;
        if (type != OBJ_TREE) {
@@ -1202,7 +1213,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
                free(ent->tree_data);
                nent = ent;
        }
-       hashcpy(nent->sha1, sha1);
+       oidcpy(&nent->oid, oid);
        nent->tree_data = data;
        nent->tree_size = size;
        nent->ref = 1;
@@ -1247,7 +1258,7 @@ static void add_pbase_object(struct tree_desc *tree,
                if (cmp < 0)
                        return;
                if (name[cmplen] != '/') {
-                       add_object_entry(entry.oid->hash,
+                       add_object_entry(entry.oid,
                                         object_type(entry.mode),
                                         fullname, 1);
                        return;
@@ -1258,7 +1269,7 @@ static void add_pbase_object(struct tree_desc *tree,
                        const char *down = name+cmplen+1;
                        int downlen = name_cmp_len(down);
 
-                       tree = pbase_tree_get(entry.oid->hash);
+                       tree = pbase_tree_get(entry.oid);
                        if (!tree)
                                return;
                        init_tree_desc(&sub, tree->tree_data, tree->tree_size);
@@ -1317,7 +1328,7 @@ static void add_preferred_base_object(const char *name)
        cmplen = name_cmp_len(name);
        for (it = pbase_tree; it; it = it->next) {
                if (cmplen == 0) {
-                       add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1);
+                       add_object_entry(&it->pcache.oid, OBJ_TREE, NULL, 1);
                }
                else {
                        struct tree_desc tree;
@@ -1327,22 +1338,22 @@ static void add_preferred_base_object(const char *name)
        }
 }
 
-static void add_preferred_base(unsigned char *sha1)
+static void add_preferred_base(struct object_id *oid)
 {
        struct pbase_tree *it;
        void *data;
        unsigned long size;
-       unsigned char tree_sha1[20];
+       struct object_id tree_oid;
 
        if (window <= num_preferred_base++)
                return;
 
-       data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
+       data = read_object_with_reference(oid->hash, tree_type, &size, tree_oid.hash);
        if (!data)
                return;
 
        for (it = pbase_tree; it; it = it->next) {
-               if (!hashcmp(it->pcache.sha1, tree_sha1)) {
+               if (!oidcmp(&it->pcache.oid, &tree_oid)) {
                        free(data);
                        return;
                }
@@ -1352,7 +1363,7 @@ static void add_preferred_base(unsigned char *sha1)
        it->next = pbase_tree;
        pbase_tree = it;
 
-       hashcpy(it->pcache.sha1, tree_sha1);
+       oidcpy(&it->pcache.oid, &tree_oid);
        it->pcache.tree_data = data;
        it->pcache.tree_size = size;
 }
@@ -2357,7 +2368,7 @@ static void add_tag_chain(const struct object_id *oid)
                        die("unable to pack objects reachable from tag %s",
                            oid_to_hex(oid));
 
-               add_object_entry(tag->object.oid.hash, OBJ_TAG, NULL, 0);
+               add_object_entry(&tag->object.oid, OBJ_TAG, NULL, 0);
 
                if (tag->tagged->type != OBJ_TAG)
                        return;
@@ -2371,7 +2382,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag,
        struct object_id peeled;
 
        if (starts_with(path, "refs/tags/") && /* is a tag? */
-           !peel_ref(path, peeled.hash)    && /* peelable? */
+           !peel_ref(path, &peeled)    && /* peelable? */
            packlist_find(&to_pack, peeled.hash, NULL))      /* object packed? */
                add_tag_chain(oid);
        return 0;
@@ -2505,8 +2516,9 @@ static int git_pack_config(const char *k, const char *v, void *cb)
 
 static void read_object_list_from_stdin(void)
 {
-       char line[40 + 1 + PATH_MAX + 2];
-       unsigned char sha1[20];
+       char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2];
+       struct object_id oid;
+       const char *p;
 
        for (;;) {
                if (!fgets(line, sizeof(line), stdin)) {
@@ -2520,17 +2532,17 @@ static void read_object_list_from_stdin(void)
                        continue;
                }
                if (line[0] == '-') {
-                       if (get_sha1_hex(line+1, sha1))
-                               die("expected edge sha1, got garbage:\n %s",
+                       if (get_oid_hex(line+1, &oid))
+                               die("expected edge object ID, got garbage:\n %s",
                                    line);
-                       add_preferred_base(sha1);
+                       add_preferred_base(&oid);
                        continue;
                }
-               if (get_sha1_hex(line, sha1))
-                       die("expected sha1, got garbage:\n %s", line);
+               if (parse_oid_hex(line, &oid, &p))
+                       die("expected object ID, got garbage:\n %s", line);
 
-               add_preferred_base_object(line+41);
-               add_object_entry(sha1, 0, line+41, 0);
+               add_preferred_base_object(p + 1);
+               add_object_entry(&oid, 0, p + 1, 0);
        }
 }
 
@@ -2538,7 +2550,7 @@ static void read_object_list_from_stdin(void)
 
 static void show_commit(struct commit *commit, void *data)
 {
-       add_object_entry(commit->object.oid.hash, OBJ_COMMIT, NULL, 0);
+       add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0);
        commit->object.flags |= OBJECT_ADDED;
 
        if (write_bitmap_index)
@@ -2548,13 +2560,49 @@ static void show_commit(struct commit *commit, void *data)
 static void show_object(struct object *obj, const char *name, void *data)
 {
        add_preferred_base_object(name);
-       add_object_entry(obj->oid.hash, obj->type, name, 0);
+       add_object_entry(&obj->oid, obj->type, name, 0);
        obj->flags |= OBJECT_ADDED;
 }
 
+static void show_object__ma_allow_any(struct object *obj, const char *name, void *data)
+{
+       assert(arg_missing_action == MA_ALLOW_ANY);
+
+       /*
+        * Quietly ignore ALL missing objects.  This avoids problems with
+        * staging them now and getting an odd error later.
+        */
+       if (!has_object_file(&obj->oid))
+               return;
+
+       show_object(obj, name, data);
+}
+
+static int option_parse_missing_action(const struct option *opt,
+                                      const char *arg, int unset)
+{
+       assert(arg);
+       assert(!unset);
+
+       if (!strcmp(arg, "error")) {
+               arg_missing_action = MA_ERROR;
+               fn_show_object = show_object;
+               return 0;
+       }
+
+       if (!strcmp(arg, "allow-any")) {
+               arg_missing_action = MA_ALLOW_ANY;
+               fn_show_object = show_object__ma_allow_any;
+               return 0;
+       }
+
+       die(_("invalid value for --missing"));
+       return 0;
+}
+
 static void show_edge(struct commit *commit)
 {
-       add_preferred_base(commit->object.oid.hash);
+       add_preferred_base(&commit->object.oid);
 }
 
 struct in_pack_object {
@@ -2601,7 +2649,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
        memset(&in_pack, 0, sizeof(in_pack));
 
        for (p = packed_git; p; p = p->next) {
-               const unsigned char *sha1;
+               struct object_id oid;
                struct object *o;
 
                if (!p->pack_local || p->pack_keep)
@@ -2614,8 +2662,8 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
                           in_pack.alloc);
 
                for (i = 0; i < p->num_objects; i++) {
-                       sha1 = nth_packed_object_sha1(p, i);
-                       o = lookup_unknown_object(sha1);
+                       nth_packed_object_oid(&oid, p, i);
+                       o = lookup_unknown_object(oid.hash);
                        if (!(o->flags & OBJECT_ADDED))
                                mark_in_pack_object(o, p, &in_pack);
                        o->flags |= OBJECT_ADDED;
@@ -2626,7 +2674,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
                QSORT(in_pack.array, in_pack.nr, ofscmp);
                for (i = 0; i < in_pack.nr; i++) {
                        struct object *o = in_pack.array[i].object;
-                       add_object_entry(o->oid.hash, o->type, "", 0);
+                       add_object_entry(&o->oid, o->type, "", 0);
                }
        }
        free(in_pack.array);
@@ -2642,7 +2690,7 @@ static int add_loose_object(const struct object_id *oid, const char *path,
                return 0;
        }
 
-       add_object_entry(oid->hash, type, "", 0);
+       add_object_entry(oid, type, "", 0);
        return 0;
 }
 
@@ -2658,7 +2706,7 @@ static void add_unreachable_loose_objects(void)
                                      NULL, NULL, NULL);
 }
 
-static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
+static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)
 {
        static struct packed_git *last_found = (void *)1;
        struct packed_git *p;
@@ -2667,7 +2715,7 @@ static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
 
        while (p) {
                if ((!p->pack_local || p->pack_keep) &&
-                       find_pack_entry_one(sha1, p)) {
+                       find_pack_entry_one(oid->hash, p)) {
                        last_found = p;
                        return 1;
                }
@@ -2718,7 +2766,7 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
                for (i = 0; i < p->num_objects; i++) {
                        nth_packed_object_oid(&oid, p, i);
                        if (!packlist_find(&to_pack, oid.hash, NULL) &&
-                           !has_sha1_pack_kept_or_nonlocal(oid.hash) &&
+                           !has_sha1_pack_kept_or_nonlocal(&oid) &&
                            !loosened_object_can_be_discarded(&oid, p->mtime))
                                if (force_object_loose(oid.hash, p->mtime))
                                        die("unable to force loose object");
@@ -2816,7 +2864,12 @@ static void get_object_list(int ac, const char **av)
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        mark_edges_uninteresting(&revs, show_edge);
-       traverse_commit_list(&revs, show_commit, show_object, NULL);
+
+       if (!fn_show_object)
+               fn_show_object = show_object;
+       traverse_commit_list_filtered(&filter_options, &revs,
+                                     show_commit, fn_show_object, NULL,
+                                     NULL);
 
        if (unpack_unreachable_expiration) {
                revs.ignore_missing_links = 1;
@@ -2952,6 +3005,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                         N_("use a bitmap index if available to speed up counting objects")),
                OPT_BOOL(0, "write-bitmap-index", &write_bitmap_index,
                         N_("write a bitmap index together with the pack index")),
+               OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+               { OPTION_CALLBACK, 0, "missing", NULL, N_("action"),
+                 N_("handling for missing objects"), PARSE_OPT_NONEG,
+                 option_parse_missing_action },
                OPT_END(),
        };
 
@@ -3028,6 +3085,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (!rev_list_all || !rev_list_reflog || !rev_list_index)
                unpack_unreachable_expiration = 0;
 
+       if (filter_options.choice) {
+               if (!pack_to_stdout)
+                       die("cannot use --filter without --stdout.");
+               use_bitmap_index = 0;
+       }
+
        /*
         * "soft" reasons not to use bitmaps - for on-disk repack by default we want
         *
index cddabf26a95cc22dfcad9843554dc8af26c858bc..d2fdae680a1ebe75d55d4fb66a3bdd0b54829666 100644 (file)
@@ -8,7 +8,7 @@
 #include "progress.h"
 
 static const char * const prune_usage[] = {
-       N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
+       N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
        NULL
 };
 static int show_only;
index 1b4d9cd97452a0945b54b233eadfaa784b91238b..511dbbe0f6e25d8f0e8c6ce511cc0ff734adc9bc 100644 (file)
@@ -86,6 +86,7 @@ static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static enum rebase_type opt_rebase = -1;
 static char *opt_diffstat;
 static char *opt_log;
+static char *opt_signoff;
 static char *opt_squash;
 static char *opt_commit;
 static char *opt_edit;
@@ -144,6 +145,9 @@ static struct option pull_options[] = {
        OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
                N_("add (at most <n>) entries from shortlog to merge commit message"),
                PARSE_OPT_OPTARG),
+       OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL,
+               N_("add Signed-off-by:"),
+               PARSE_OPT_OPTARG),
        OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
                N_("create a single commit instead of doing a merge"),
                PARSE_OPT_NOARG),
@@ -553,10 +557,10 @@ static int pull_into_void(const struct object_id *merge_head,
         * index/worktree changes that the user already made on the unborn
         * branch.
         */
-       if (checkout_fast_forward(&empty_tree_oid, merge_head, 0))
+       if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0))
                return 1;
 
-       if (update_ref("initial pull", "HEAD", merge_head->hash, curr_head->hash, 0, UPDATE_REFS_DIE_ON_ERR))
+       if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
                return 1;
 
        return 0;
@@ -606,6 +610,8 @@ static int run_merge(void)
                argv_array_push(&args, opt_diffstat);
        if (opt_log)
                argv_array_push(&args, opt_log);
+       if (opt_signoff)
+               argv_array_push(&args, opt_signoff);
        if (opt_squash)
                argv_array_push(&args, opt_squash);
        if (opt_commit)
index 2ac81042292ef1852ec9a31dea34ec91a1b796e3..1c28427d82ee73631fb02b5f126c40ebd2cb1774 100644 (file)
@@ -32,6 +32,8 @@ static const char **refspec;
 static int refspec_nr;
 static int refspec_alloc;
 
+static struct string_list push_options_config = STRING_LIST_INIT_DUP;
+
 static void add_refspec(const char *ref)
 {
        refspec_nr++;
@@ -503,6 +505,15 @@ static int git_push_config(const char *k, const char *v, void *cb)
                int val = git_config_bool(k, v) ?
                        RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF;
                recurse_submodules = val;
+       } else if (!strcmp(k, "push.pushoption")) {
+               if (!v)
+                       return config_error_nonbool(k);
+               else
+                       if (!*v)
+                               string_list_clear(&push_options_config, 0);
+                       else
+                               string_list_append(&push_options_config, v);
+               return 0;
        }
 
        return git_default_config(k, v, NULL);
@@ -515,7 +526,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
        int push_cert = -1;
        int rc;
        const char *repo = NULL;        /* default repository */
-       struct string_list push_options = STRING_LIST_INIT_DUP;
+       struct string_list push_options_cmdline = STRING_LIST_INIT_DUP;
+       struct string_list *push_options;
        const struct string_list_item *item;
 
        struct option options[] = {
@@ -551,7 +563,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                  0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
                  PARSE_OPT_OPTARG, option_parse_push_signed },
                OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
-               OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
+               OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")),
                OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
                                TRANSPORT_FAMILY_IPV4),
                OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -562,6 +574,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
        packet_trace_identity("push");
        git_config(git_push_config, &flags);
        argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+       push_options = (push_options_cmdline.nr
+               ? &push_options_cmdline
+               : &push_options_config);
        set_push_cert_flags(&flags, push_cert);
 
        if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
@@ -584,12 +599,13 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                set_refspecs(argv + 1, argc - 1, repo);
        }
 
-       for_each_string_list_item(item, &push_options)
+       for_each_string_list_item(item, push_options)
                if (strchr(item->string, '\n'))
                        die(_("push options must not have new line characters"));
 
-       rc = do_push(repo, flags, &push_options);
-       string_list_clear(&push_options, 0);
+       rc = do_push(repo, flags, push_options);
+       string_list_clear(&push_options_cmdline, 0);
+       string_list_clear(&push_options_config, 0);
        if (rc == -1)
                usage_with_options(push_usage, options);
        else
index f8519363a393862b6857acab037e74367c7f2134..7daee544b7b4cf3495355e1c6e597272d7b66440 100644 (file)
@@ -12,10 +12,12 @@ static const char * const builtin_rebase_helper_usage[] = {
 int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 {
        struct replay_opts opts = REPLAY_OPTS_INIT;
-       int keep_empty = 0;
+       unsigned flags = 0, keep_empty = 0;
+       int abbreviate_commands = 0;
        enum {
-               CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
-               CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH
+               CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
+               CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
+               ADD_EXEC
        } command = 0;
        struct option options[] = {
                OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -27,19 +29,22 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE(0, "make-script", &command,
                        N_("make rebase script"), MAKE_SCRIPT),
                OPT_CMDMODE(0, "shorten-ids", &command,
-                       N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S),
+                       N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
                OPT_CMDMODE(0, "expand-ids", &command,
-                       N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
+                       N_("expand commit ids in the todo list"), EXPAND_OIDS),
                OPT_CMDMODE(0, "check-todo-list", &command,
                        N_("check the todo list"), CHECK_TODO_LIST),
                OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
                        N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
                OPT_CMDMODE(0, "rearrange-squash", &command,
                        N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
+               OPT_CMDMODE(0, "add-exec-commands", &command,
+                       N_("insert exec commands in todo list"), ADD_EXEC),
                OPT_END()
        };
 
        git_config(git_default_config, NULL);
+       git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
        opts.action = REPLAY_INTERACTIVE_REBASE;
        opts.allow_ff = 1;
@@ -48,21 +53,25 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, NULL, options,
                        builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
 
+       flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+       flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+       flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+
        if (command == CONTINUE && argc == 1)
                return !!sequencer_continue(&opts);
        if (command == ABORT && argc == 1)
                return !!sequencer_remove_state(&opts);
        if (command == MAKE_SCRIPT && argc > 1)
-               return !!sequencer_make_script(keep_empty, stdout, argc, argv);
-       if (command == SHORTEN_SHA1S && argc == 1)
-               return !!transform_todo_ids(1);
-       if (command == EXPAND_SHA1S && argc == 1)
-               return !!transform_todo_ids(0);
+               return !!sequencer_make_script(stdout, argc, argv, flags);
+       if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1)
+               return !!transform_todos(flags);
        if (command == CHECK_TODO_LIST && argc == 1)
                return !!check_todo_list();
        if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
                return !!skip_unnecessary_picks();
        if (command == REARRANGE_SQUASH && argc == 1)
                return !!rearrange_squash();
+       if (command == ADD_EXEC && argc == 2)
+               return !!sequencer_add_exec_commands(argv[1]);
        usage_with_options(builtin_rebase_helper_usage, options);
 }
index cc4876740569c6e495b7cb32161d019619171d79..b7ce7c7f5275febbf6db6b71e3b610b09a4fe191 100644 (file)
@@ -24,6 +24,7 @@
 #include "tmp-objdir.h"
 #include "oidset.h"
 #include "packfile.h"
+#include "protocol.h"
 
 static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@ -870,7 +871,7 @@ static void refuse_unconfigured_deny_delete_current(void)
        rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg));
 }
 
-static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]);
+static int command_singleton_iterator(void *cb_data, struct object_id *oid);
 static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
 {
        static struct lock_file shallow_lock;
@@ -1139,7 +1140,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
                }
                if (ref_transaction_delete(transaction,
                                           namespaced_name,
-                                          old_oid ? old_oid->hash : NULL,
+                                          old_oid,
                                           0, "push", &err)) {
                        rp_error("%s", err.buf);
                        strbuf_release(&err);
@@ -1156,7 +1157,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 
                if (ref_transaction_update(transaction,
                                           namespaced_name,
-                                          new_oid->hash, old_oid->hash,
+                                          new_oid, old_oid,
                                           0, "push",
                                           &err)) {
                        rp_error("%s", err.buf);
@@ -1270,7 +1271,7 @@ static void check_aliased_updates(struct command *commands)
        string_list_clear(&ref_list, 0);
 }
 
-static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
+static int command_singleton_iterator(void *cb_data, struct object_id *oid)
 {
        struct command **cmd_list = cb_data;
        struct command *cmd = *cmd_list;
@@ -1278,7 +1279,7 @@ static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
        if (!cmd || is_null_oid(&cmd->new_oid))
                return -1; /* end of list */
        *cmd_list = NULL; /* this returns only one */
-       hashcpy(sha1, cmd->new_oid.hash);
+       oidcpy(oid, &cmd->new_oid);
        return 0;
 }
 
@@ -1309,7 +1310,7 @@ struct iterate_data {
        struct shallow_info *si;
 };
 
-static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
+static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
 {
        struct iterate_data *data = cb_data;
        struct command **cmd_list = &data->cmds;
@@ -1320,7 +1321,7 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
                        /* to be checked in update_shallow_ref() */
                        continue;
                if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) {
-                       hashcpy(sha1, cmd->new_oid.hash);
+                       oidcpy(oid, &cmd->new_oid);
                        *cmd_list = cmd->next;
                        return 0;
                }
@@ -1961,6 +1962,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
 
+       switch (determine_protocol_version_server()) {
+       case protocol_v1:
+               /*
+                * v1 is just the original protocol with a version string,
+                * so just fall through after writing the version string.
+                */
+               if (advertise_refs || !stateless_rpc)
+                       packet_write_fmt(1, "version 1\n");
+
+               /* fallthrough */
+       case protocol_v0:
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
+
        if (advertise_refs || !stateless_rpc) {
                write_head_info();
        }
index 2067cca5b1ec4ed141a3e404cb7738c9b0ebc5d9..2233725315ba32832ec5c866b7dbdf72103ac74d 100644 (file)
@@ -42,7 +42,7 @@ struct expire_reflog_policy_cb {
 };
 
 struct collected_reflog {
-       unsigned char sha1[20];
+       struct object_id oid;
        char reflog[FLEX_ARRAY];
 };
 
@@ -385,7 +385,7 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus
        struct collect_reflog_cb *cb = cb_data;
 
        FLEX_ALLOC_STR(e, reflog, ref);
-       hashcpy(e->sha1, oid->hash);
+       oidcpy(&e->oid, oid);
        ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
        cb->e[cb->nr++] = e;
        return 0;
@@ -416,16 +416,6 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
        return ent;
 }
 
-static int parse_expire_cfg_value(const char *var, const char *value, timestamp_t *expire)
-{
-       if (!value)
-               return config_error_nonbool(var);
-       if (parse_expiry_date(value, expire))
-               return error(_("'%s' for '%s' is not a valid timestamp"),
-                            value, var);
-       return 0;
-}
-
 /* expiry timer slot */
 #define EXPIRE_TOTAL   01
 #define EXPIRE_UNREACH 02
@@ -443,11 +433,11 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
 
        if (!strcmp(key, "reflogexpire")) {
                slot = EXPIRE_TOTAL;
-               if (parse_expire_cfg_value(var, value, &expire))
+               if (git_config_expiry_date(&expire, var, value))
                        return -1;
        } else if (!strcmp(key, "reflogexpireunreachable")) {
                slot = EXPIRE_UNREACH;
-               if (parse_expire_cfg_value(var, value, &expire))
+               if (git_config_expiry_date(&expire, var, value))
                        return -1;
        } else
                return git_default_config(var, value, cb);
@@ -589,7 +579,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                for (i = 0; i < collected.nr; i++) {
                        struct collected_reflog *e = collected.e[i];
                        set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);
-                       status |= reflog_expire(e->reflog, e->sha1, flags,
+                       status |= reflog_expire(e->reflog, &e->oid, flags,
                                                reflog_expiry_prepare,
                                                should_expire_reflog_ent,
                                                reflog_expiry_cleanup,
@@ -601,13 +591,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 
        for (; i < argc; i++) {
                char *ref;
-               unsigned char sha1[20];
-               if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
+               struct object_id oid;
+               if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) {
                        status |= error("%s points nowhere!", argv[i]);
                        continue;
                }
                set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
-               status |= reflog_expire(ref, sha1, flags,
+               status |= reflog_expire(ref, &oid, flags,
                                        reflog_expiry_prepare,
                                        should_expire_reflog_ent,
                                        reflog_expiry_cleanup,
@@ -659,7 +649,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
 
        for ( ; i < argc; i++) {
                const char *spec = strstr(argv[i], "@{");
-               unsigned char sha1[20];
+               struct object_id oid;
                char *ep, *ref;
                int recno;
 
@@ -668,7 +658,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
                        continue;
                }
 
-               if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
+               if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) {
                        status |= error("no reflog for '%s'", argv[i]);
                        continue;
                }
@@ -683,7 +673,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
                        cb.cmd.expire_total = 0;
                }
 
-               status |= reflog_expire(ref, sha1, flags,
+               status |= reflog_expire(ref, &oid, flags,
                                        reflog_expiry_prepare,
                                        should_expire_reflog_ent,
                                        reflog_expiry_cleanup,
index bc896236952079fc3ae06e23101991169dea64fc..d95bf904c3b3fb40438335198a2729e45a9897cb 100644 (file)
@@ -690,10 +690,10 @@ static int mv(int argc, const char **argv)
                int flag = 0;
                struct object_id oid;
 
-               read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag);
+               read_ref_full(item->string, RESOLVE_REF_READING, &oid, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
-               if (delete_ref(NULL, item->string, NULL, REF_NODEREF))
+               if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF))
                        die(_("deleting '%s' failed"), item->string);
        }
        for (i = 0; i < remote_branches.nr; i++) {
@@ -788,7 +788,7 @@ static int rm(int argc, const char **argv)
        strbuf_release(&buf);
 
        if (!result)
-               result = delete_refs("remote: remove", &branches, REF_NODEREF);
+               result = delete_refs("remote: remove", &branches, REF_NO_DEREF);
        string_list_clear(&branches, 0);
 
        if (skipped.nr) {
@@ -1255,7 +1255,7 @@ static int set_head(int argc, const char **argv)
                        head_name = xstrdup(states.heads.items[0].string);
                free_remote_ref_states(&states);
        } else if (opt_d && !opt_a && argc == 1) {
-               if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF))
+               if (delete_ref(NULL, buf.buf, NULL, REF_NO_DEREF))
                        result |= error(_("Could not delete %s"), buf.buf);
        } else
                usage_with_options(builtin_remote_sethead_usage, options);
index 3e71a771523d8566590bfbd5de71b08acf79e3a4..10078ae37136f154486d8163b6b905ad163c7047 100644 (file)
@@ -113,7 +113,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
                strbuf_addstr(&ref, oid_to_hex(&oid));
                full_hex = ref.buf + base_len;
 
-               if (read_ref(ref.buf, oid.hash)) {
+               if (read_ref(ref.buf, &oid)) {
                        error("replace ref '%s' not found.", full_hex);
                        had_error = 1;
                        continue;
@@ -128,7 +128,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
 static int delete_replace_ref(const char *name, const char *ref,
                              const struct object_id *oid)
 {
-       if (delete_ref(NULL, ref, oid->hash, 0))
+       if (delete_ref(NULL, ref, oid, 0))
                return 1;
        printf("Deleted replace ref '%s'\n", name);
        return 0;
@@ -144,7 +144,7 @@ static void check_ref_valid(struct object_id *object,
        if (check_refname_format(ref->buf, 0))
                die("'%s' is not a valid ref name.", ref->buf);
 
-       if (read_ref(ref->buf, prev->hash))
+       if (read_ref(ref->buf, prev))
                oidclr(prev);
        else if (!force)
                die("replace ref '%s' already exists", ref->buf);
@@ -175,7 +175,7 @@ static int replace_object_oid(const char *object_ref,
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, ref.buf, repl->hash, prev.hash,
+           ref_transaction_update(transaction, ref.buf, repl, &prev,
                                   0, NULL, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
index 9cd89b23059f768c4651725f2c26c5b08c35f650..e15f595799c40933e31f909715e35b4d0a665fc0 100644 (file)
@@ -12,7 +12,7 @@
 #include "lockfile.h"
 #include "tag.h"
 #include "object.h"
-#include "commit.h"
+#include "pretty.h"
 #include "run-command.h"
 #include "refs.h"
 #include "diff.h"
@@ -166,7 +166,7 @@ static int read_from_tree(const struct pathspec *pathspec,
        opt.output_format = DIFF_FORMAT_CALLBACK;
        opt.format_callback = update_index_from_diff;
        opt.format_callback_data = &intent_to_add;
-       opt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
+       opt.flags.override_submodule_config = 1;
 
        if (do_diff_cache(tree_oid, &opt))
                return 1;
@@ -266,12 +266,12 @@ static int reset_refs(const char *rev, const struct object_id *oid)
        if (!get_oid("HEAD", &oid_orig)) {
                orig = &oid_orig;
                set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
-               update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
+               update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
                           UPDATE_REFS_MSG_ON_ERR);
        } else if (old_orig)
-               delete_ref(NULL, "ORIG_HEAD", old_orig->hash, 0);
+               delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
        set_reflog_message(&msg, "updating HEAD", rev);
-       update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0,
+       update_ref_status = update_ref(msg.buf, "HEAD", oid, orig, 0,
                                       UPDATE_REFS_MSG_ON_ERR);
        strbuf_release(&msg);
        return update_ref_status;
index fb1c36af6abef0b6169348381e712883bba7e1b3..d5345b6a2e237b550e5cee69e6d46ff8271ade22 100644 (file)
@@ -4,6 +4,8 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
 #include "pack.h"
 #include "pack-bitmap.h"
 #include "builtin.h"
@@ -12,6 +14,7 @@
 #include "bisect.h"
 #include "progress.h"
 #include "reflog-walk.h"
+#include "oidset.h"
 
 static const char rev_list_usage[] =
 "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -55,6 +58,20 @@ static const char rev_list_usage[] =
 static struct progress *progress;
 static unsigned progress_counter;
 
+static struct list_objects_filter_options filter_options;
+static struct oidset omitted_objects;
+static int arg_print_omitted; /* print objects omitted by filter */
+
+static struct oidset missing_objects;
+enum missing_action {
+       MA_ERROR = 0,    /* fail if any missing objects are encountered */
+       MA_ALLOW_ANY,    /* silently allow ALL missing objects */
+       MA_PRINT,        /* print ALL missing objects in special section */
+};
+static enum missing_action arg_missing_action;
+
+#define DEFAULT_OIDSET_SIZE     (16*1024)
+
 static void finish_commit(struct commit *commit, void *data);
 static void show_commit(struct commit *commit, void *data)
 {
@@ -178,11 +195,31 @@ static void finish_commit(struct commit *commit, void *data)
        free_commit_buffer(commit);
 }
 
+static inline void finish_object__ma(struct object *obj)
+{
+       switch (arg_missing_action) {
+       case MA_ERROR:
+               die("missing blob object '%s'", oid_to_hex(&obj->oid));
+               return;
+
+       case MA_ALLOW_ANY:
+               return;
+
+       case MA_PRINT:
+               oidset_insert(&missing_objects, &obj->oid);
+               return;
+
+       default:
+               BUG("unhandled missing_action");
+               return;
+       }
+}
+
 static void finish_object(struct object *obj, const char *name, void *cb_data)
 {
        struct rev_list_info *info = cb_data;
        if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid))
-               die("missing blob object '%s'", oid_to_hex(&obj->oid));
+               finish_object__ma(obj);
        if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
                parse_object(&obj->oid);
 }
@@ -258,17 +295,37 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
 }
 
 static int show_object_fast(
-       const unsigned char *sha1,
+       const struct object_id *oid,
        enum object_type type,
        int exclude,
        uint32_t name_hash,
        struct packed_git *found_pack,
        off_t found_offset)
 {
-       fprintf(stdout, "%s\n", sha1_to_hex(sha1));
+       fprintf(stdout, "%s\n", oid_to_hex(oid));
        return 1;
 }
 
+static inline int parse_missing_action_value(const char *value)
+{
+       if (!strcmp(value, "error")) {
+               arg_missing_action = MA_ERROR;
+               return 1;
+       }
+
+       if (!strcmp(value, "allow-any")) {
+               arg_missing_action = MA_ALLOW_ANY;
+               return 1;
+       }
+
+       if (!strcmp(value, "print")) {
+               arg_missing_action = MA_PRINT;
+               return 1;
+       }
+
+       return 0;
+}
+
 int cmd_rev_list(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -294,7 +351,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.bisect)
                bisect_list = 1;
 
-       if (DIFF_OPT_TST(&revs.diffopt, QUICK))
+       if (revs.diffopt.flags.quick)
                info.flags |= REV_LIST_QUIET;
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
@@ -335,6 +392,30 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        show_progress = arg;
                        continue;
                }
+
+               if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) {
+                       parse_list_objects_filter(&filter_options, arg);
+                       if (filter_options.choice && !revs.blob_objects)
+                               die(_("object filtering requires --objects"));
+                       if (filter_options.choice == LOFC_SPARSE_OID &&
+                           !filter_options.sparse_oid_value)
+                               die(_("invalid sparse value '%s'"),
+                                   filter_options.filter_spec);
+                       continue;
+               }
+               if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
+                       list_objects_filter_release(&filter_options);
+                       continue;
+               }
+               if (!strcmp(arg, "--filter-print-omitted")) {
+                       arg_print_omitted = 1;
+                       continue;
+               }
+
+               if (skip_prefix(arg, "--missing=", &arg) &&
+                   parse_missing_action_value(arg))
+                       continue;
+
                usage(rev_list_usage);
 
        }
@@ -360,6 +441,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.show_notes)
                die(_("rev-list does not support display of notes"));
 
+       if (filter_options.choice && use_bitmap_index)
+               die(_("cannot combine --use-bitmap-index with object filtering"));
+
        save_commit_buffer = (revs.verbose_header ||
                              revs.grep_filter.pattern_list ||
                              revs.grep_filter.header_list);
@@ -403,7 +487,31 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        return show_bisect_vars(&info, reaches, all);
        }
 
-       traverse_commit_list(&revs, show_commit, show_object, &info);
+       if (arg_print_omitted)
+               oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
+       if (arg_missing_action == MA_PRINT)
+               oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE);
+
+       traverse_commit_list_filtered(
+               &filter_options, &revs, show_commit, show_object, &info,
+               (arg_print_omitted ? &omitted_objects : NULL));
+
+       if (arg_print_omitted) {
+               struct oidset_iter iter;
+               struct object_id *oid;
+               oidset_iter_init(&omitted_objects, &iter);
+               while ((oid = oidset_iter_next(&iter)))
+                       printf("~%s\n", oid_to_hex(oid));
+               oidset_clear(&omitted_objects);
+       }
+       if (arg_missing_action == MA_PRINT) {
+               struct oidset_iter iter;
+               struct object_id *oid;
+               oidset_iter_init(&missing_objects, &iter);
+               while ((oid = oidset_iter_next(&iter)))
+                       printf("?%s\n", oid_to_hex(oid));
+               oidset_clear(&missing_objects);
+       }
 
        stop_progress(&progress);
 
index a8d7e6f7aecc2e4179547bde3fd6d07231764b9f..74aa644cbb37b5741c521eba53d0a040aaf1582e 100644 (file)
@@ -133,7 +133,7 @@ static void show_rev(int type, const struct object_id *oid, const char *name)
                        struct object_id discard;
                        char *full;
 
-                       switch (dwim_ref(name, strlen(name), discard.hash, &full)) {
+                       switch (dwim_ref(name, strlen(name), &discard, &full)) {
                        case 0:
                                /*
                                 * Not found -- not a ref.  We could
index d91451fea122cec3923543ef78acf0c5ef4c5fa2..4a2fcca27b3f722ca520c2411b80e6984ecf780a 100644 (file)
@@ -382,7 +382,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                }
                strbuf_release(&buf);
                if (gitmodules_modified)
-                       stage_updated_gitmodules();
+                       stage_updated_gitmodules(&the_index);
        }
 
        if (active_cache_changed) {
index 6fa1f62a88ac2704abc6f274ed7a6170bd3a2f4c..e8a4aa40cb4b6cf8787af3dd35d833a92a85bba3 100644 (file)
@@ -1,6 +1,6 @@
 #include "cache.h"
 #include "config.h"
-#include "commit.h"
+#include "pretty.h"
 #include "refs.h"
 #include "builtin.h"
 #include "color.h"
@@ -705,8 +705,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        static const char *fake_av[2];
 
                        fake_av[0] = resolve_refdup("HEAD",
-                                                   RESOLVE_REF_READING,
-                                                   oid.hash, NULL);
+                                                   RESOLVE_REF_READING, &oid,
+                                                   NULL);
                        fake_av[1] = NULL;
                        av = fake_av;
                        ac = 1;
@@ -720,7 +720,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        die(Q_("only %d entry can be shown at one time.",
                               "only %d entries can be shown at one time.",
                               MAX_REVS), MAX_REVS);
-               if (!dwim_ref(*av, strlen(*av), oid.hash, &ref))
+               if (!dwim_ref(*av, strlen(*av), &oid, &ref))
                        die(_("no such ref %s"), *av);
 
                /* Has the base been specified? */
@@ -731,7 +731,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                                /* Ah, that is a date spec... */
                                timestamp_t at;
                                at = approxidate(reflog_base);
-                               read_ref_at(ref, flags, at, -1, oid.hash, NULL,
+                               read_ref_at(ref, flags, at, -1, &oid, NULL,
                                            NULL, NULL, &base);
                        }
                }
@@ -743,7 +743,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        timestamp_t timestamp;
                        int tz;
 
-                       if (read_ref_at(ref, flags, 0, base+i, oid.hash, &logmsg,
+                       if (read_ref_at(ref, flags, 0, base + i, &oid, &logmsg,
                                        &timestamp, &tz, NULL)) {
                                reflog = i;
                                break;
@@ -775,7 +775,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        }
 
        head = resolve_refdup("HEAD", RESOLVE_REF_READING,
-                             head_oid.hash, NULL);
+                             &head_oid, NULL);
 
        if (with_current_branch && head) {
                int has_head = 0;
index 013d241abc09be76c318dbc25d5fb3b40ade7b3a..41e5e71cad660d26ddc90ffeaa383fd7bf10f79f 100644 (file)
@@ -38,7 +38,7 @@ static void show_one(const char *refname, const struct object_id *oid)
        if (!deref_tags)
                return;
 
-       if (!peel_ref(refname, peeled.hash)) {
+       if (!peel_ref(refname, &peeled)) {
                hex = find_unique_abbrev(peeled.hash, abbrev);
                printf("%s %s^{}\n", hex, refname);
        }
@@ -197,7 +197,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                        struct object_id oid;
 
                        if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
-                           !read_ref(*pattern, oid.hash)) {
+                           !read_ref(*pattern, &oid)) {
                                show_one(*pattern, &oid);
                        }
                        else if (!quiet)
index 06ed02f99402be6a7edb36ff0eb08b903c513c6c..a5c4a8a6941d46c78e4dc58d0df47532f0bb2b45 100644 (file)
 #include "remote.h"
 #include "refs.h"
 #include "connect.h"
+#include "revision.h"
+#include "diffcore.h"
+#include "diff.h"
+
+#define OPT_QUIET (1 << 0)
+#define OPT_CACHED (1 << 1)
+#define OPT_RECURSIVE (1 << 2)
+
+typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
+                                 void *cb_data);
 
 static char *get_default_remote(void)
 {
@@ -219,6 +229,64 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
        return 0;
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+       const char *super_prefix = get_super_prefix();
+
+       if (prefix && super_prefix) {
+               BUG("cannot have prefix '%s' and superprefix '%s'",
+                   prefix, super_prefix);
+       } else if (prefix) {
+               struct strbuf sb = STRBUF_INIT;
+               char *displaypath = xstrdup(relative_path(path, prefix, &sb));
+               strbuf_release(&sb);
+               return displaypath;
+       } else if (super_prefix) {
+               return xstrfmt("%s%s", super_prefix, path);
+       } else {
+               return xstrdup(path);
+       }
+}
+
+static char *compute_rev_name(const char *sub_path, const char* object_id)
+{
+       struct strbuf sb = STRBUF_INIT;
+       const char ***d;
+
+       static const char *describe_bare[] = { NULL };
+
+       static const char *describe_tags[] = { "--tags", NULL };
+
+       static const char *describe_contains[] = { "--contains", NULL };
+
+       static const char *describe_all_always[] = { "--all", "--always", NULL };
+
+       static const char **describe_argv[] = { describe_bare, describe_tags,
+                                               describe_contains,
+                                               describe_all_always, NULL };
+
+       for (d = describe_argv; *d; d++) {
+               struct child_process cp = CHILD_PROCESS_INIT;
+               prepare_submodule_repo_env(&cp.env_array);
+               cp.dir = sub_path;
+               cp.git_cmd = 1;
+               cp.no_stderr = 1;
+
+               argv_array_push(&cp.args, "describe");
+               argv_array_pushv(&cp.args, *d);
+               argv_array_push(&cp.args, object_id);
+
+               if (!capture_command(&cp, &sb, 0)) {
+                       strbuf_strip_suffix(&sb, "\n");
+                       return strbuf_detach(&sb, NULL);
+               }
+       }
+
+       strbuf_release(&sb);
+       return NULL;
+}
+
 struct module_list {
        const struct cache_entry **entries;
        int alloc, nr;
@@ -328,21 +396,29 @@ static int module_list(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static void init_submodule(const char *path, const char *prefix, int quiet)
+static void for_each_listed_submodule(const struct module_list *list,
+                                     each_submodule_fn fn, void *cb_data)
+{
+       int i;
+       for (i = 0; i < list->nr; i++)
+               fn(list->entries[i], cb_data);
+}
+
+struct init_cb {
+       const char *prefix;
+       unsigned int flags;
+};
+
+#define INIT_CB_INIT { NULL, 0 }
+
+static void init_submodule(const char *path, const char *prefix,
+                          unsigned int flags)
 {
        const struct submodule *sub;
        struct strbuf sb = STRBUF_INIT;
        char *upd = NULL, *url = NULL, *displaypath;
 
-       if (prefix && get_super_prefix())
-               die("BUG: cannot have prefix and superprefix");
-       else if (prefix)
-               displaypath = xstrdup(relative_path(path, prefix, &sb));
-       else if (get_super_prefix()) {
-               strbuf_addf(&sb, "%s%s", get_super_prefix(), path);
-               displaypath = strbuf_detach(&sb, NULL);
-       } else
-               displaypath = xstrdup(path);
+       displaypath = get_submodule_displaypath(path, prefix);
 
        sub = submodule_from_path(&null_oid, path);
 
@@ -357,9 +433,9 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
         * Set active flag for the submodule being initialized
         */
        if (!is_submodule_active(the_repository, path)) {
-               strbuf_reset(&sb);
                strbuf_addf(&sb, "submodule.%s.active", sub->name);
                git_config_set_gently(sb.buf, "true");
+               strbuf_reset(&sb);
        }
 
        /*
@@ -367,7 +443,6 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
         * To look up the url in .git/config, we must not fall back to
         * .gitmodules, so look it up directly.
         */
-       strbuf_reset(&sb);
        strbuf_addf(&sb, "submodule.%s.url", sub->name);
        if (git_config_get_string(sb.buf, &url)) {
                if (!sub->url)
@@ -399,14 +474,14 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
                if (git_config_set_gently(sb.buf, url))
                        die(_("Failed to register url for submodule path '%s'"),
                            displaypath);
-               if (!quiet)
+               if (!(flags & OPT_QUIET))
                        fprintf(stderr,
                                _("Submodule '%s' (%s) registered for path '%s'\n"),
                                sub->name, url, displaypath);
        }
+       strbuf_reset(&sb);
 
        /* Copy "update" setting when it is not set yet */
-       strbuf_reset(&sb);
        strbuf_addf(&sb, "submodule.%s.update", sub->name);
        if (git_config_get_string(sb.buf, &upd) &&
            sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
@@ -426,12 +501,18 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
        free(upd);
 }
 
+static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
+{
+       struct init_cb *info = cb_data;
+       init_submodule(list_item->name, info->prefix, info->flags);
+}
+
 static int module_init(int argc, const char **argv, const char *prefix)
 {
+       struct init_cb info = INIT_CB_INIT;
        struct pathspec pathspec;
        struct module_list list = MODULE_LIST_INIT;
        int quiet = 0;
-       int i;
 
        struct option module_init_options[] = {
                OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
@@ -456,8 +537,165 @@ static int module_init(int argc, const char **argv, const char *prefix)
        if (!argc && git_config_get_value_multi("submodule.active"))
                module_list_active(&list);
 
-       for (i = 0; i < list.nr; i++)
-               init_submodule(list.entries[i]->name, prefix, quiet);
+       info.prefix = prefix;
+       if (quiet)
+               info.flags |= OPT_QUIET;
+
+       for_each_listed_submodule(&list, init_submodule_cb, &info);
+
+       return 0;
+}
+
+struct status_cb {
+       const char *prefix;
+       unsigned int flags;
+};
+
+#define STATUS_CB_INIT { NULL, 0 }
+
+static void print_status(unsigned int flags, char state, const char *path,
+                        const struct object_id *oid, const char *displaypath)
+{
+       if (flags & OPT_QUIET)
+               return;
+
+       printf("%c%s %s", state, oid_to_hex(oid), displaypath);
+
+       if (state == ' ' || state == '+')
+               printf(" (%s)", compute_rev_name(path, oid_to_hex(oid)));
+
+       printf("\n");
+}
+
+static int handle_submodule_head_ref(const char *refname,
+                                    const struct object_id *oid, int flags,
+                                    void *cb_data)
+{
+       struct object_id *output = cb_data;
+       if (oid)
+               oidcpy(output, oid);
+
+       return 0;
+}
+
+static void status_submodule(const char *path, const struct object_id *ce_oid,
+                            unsigned int ce_flags, const char *prefix,
+                            unsigned int flags)
+{
+       char *displaypath;
+       struct argv_array diff_files_args = ARGV_ARRAY_INIT;
+       struct rev_info rev;
+       int diff_files_result;
+
+       if (!submodule_from_path(&null_oid, path))
+               die(_("no submodule mapping found in .gitmodules for path '%s'"),
+                     path);
+
+       displaypath = get_submodule_displaypath(path, prefix);
+
+       if ((CE_STAGEMASK & ce_flags) >> CE_STAGESHIFT) {
+               print_status(flags, 'U', path, &null_oid, displaypath);
+               goto cleanup;
+       }
+
+       if (!is_submodule_active(the_repository, path)) {
+               print_status(flags, '-', path, ce_oid, displaypath);
+               goto cleanup;
+       }
+
+       argv_array_pushl(&diff_files_args, "diff-files",
+                        "--ignore-submodules=dirty", "--quiet", "--",
+                        path, NULL);
+
+       git_config(git_diff_basic_config, NULL);
+       init_revisions(&rev, prefix);
+       rev.abbrev = 0;
+       diff_files_args.argc = setup_revisions(diff_files_args.argc,
+                                              diff_files_args.argv,
+                                              &rev, NULL);
+       diff_files_result = run_diff_files(&rev, 0);
+
+       if (!diff_result_code(&rev.diffopt, diff_files_result)) {
+               print_status(flags, ' ', path, ce_oid,
+                            displaypath);
+       } else if (!(flags & OPT_CACHED)) {
+               struct object_id oid;
+
+               if (refs_head_ref(get_submodule_ref_store(path),
+                                 handle_submodule_head_ref, &oid))
+                       die(_("could not resolve HEAD ref inside the "
+                             "submodule '%s'"), path);
+
+               print_status(flags, '+', path, &oid, displaypath);
+       } else {
+               print_status(flags, '+', path, ce_oid, displaypath);
+       }
+
+       if (flags & OPT_RECURSIVE) {
+               struct child_process cpr = CHILD_PROCESS_INIT;
+
+               cpr.git_cmd = 1;
+               cpr.dir = path;
+               prepare_submodule_repo_env(&cpr.env_array);
+
+               argv_array_push(&cpr.args, "--super-prefix");
+               argv_array_pushf(&cpr.args, "%s/", displaypath);
+               argv_array_pushl(&cpr.args, "submodule--helper", "status",
+                                "--recursive", NULL);
+
+               if (flags & OPT_CACHED)
+                       argv_array_push(&cpr.args, "--cached");
+
+               if (flags & OPT_QUIET)
+                       argv_array_push(&cpr.args, "--quiet");
+
+               if (run_command(&cpr))
+                       die(_("failed to recurse into submodule '%s'"), path);
+       }
+
+cleanup:
+       argv_array_clear(&diff_files_args);
+       free(displaypath);
+}
+
+static void status_submodule_cb(const struct cache_entry *list_item,
+                               void *cb_data)
+{
+       struct status_cb *info = cb_data;
+       status_submodule(list_item->name, &list_item->oid, list_item->ce_flags,
+                        info->prefix, info->flags);
+}
+
+static int module_status(int argc, const char **argv, const char *prefix)
+{
+       struct status_cb info = STATUS_CB_INIT;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       int quiet = 0;
+
+       struct option module_status_options[] = {
+               OPT__QUIET(&quiet, N_("Suppress submodule status output")),
+               OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED),
+               OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule status [--quiet] [--cached] [--recursive] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_status_options,
+                            git_submodule_helper_usage, 0);
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+               return 1;
+
+       info.prefix = prefix;
+       if (quiet)
+               info.flags |= OPT_QUIET;
+
+       for_each_listed_submodule(&list, status_submodule_cb, &info);
 
        return 0;
 }
@@ -1144,7 +1382,7 @@ static int push_check(int argc, const char **argv, const char *prefix)
        argv++;
        argc--;
        /* Get the submodule's head ref and determine if it is detached */
-       head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
+       head = resolve_refdup("HEAD", 0, &head_oid, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
@@ -1259,6 +1497,7 @@ static struct cmd_struct commands[] = {
        {"resolve-relative-url", resolve_relative_url, 0},
        {"resolve-relative-url-test", resolve_relative_url_test, 0},
        {"init", module_init, SUPPORT_SUPER_PREFIX},
+       {"status", module_status, SUPPORT_SUPER_PREFIX},
        {"remote-branch", resolve_remote_submodule_branch, 0},
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
index 17aabaa679d599090ef5eb01d0c3650108c4f79c..80237f0df10f442181814900a93d797f965ec3de 100644 (file)
@@ -58,7 +58,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                        die("Cannot delete %s, not a symbolic ref", argv[0]);
                if (!strcmp(argv[0], "HEAD"))
                        die("deleting '%s' is not allowed", argv[0]);
-               return delete_ref(NULL, argv[0], NULL, REF_NODEREF);
+               return delete_ref(NULL, argv[0], NULL, REF_NO_DEREF);
        }
 
        switch (argc) {
index b38329b593b595edad3efde82aaf1ecb90a73141..a7e6a5b0f234a95fb45a71d7e9aa7f0baa2b47f8 100644 (file)
@@ -82,7 +82,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
        for (p = argv; *p; p++) {
                strbuf_reset(&ref);
                strbuf_addf(&ref, "refs/tags/%s", *p);
-               if (read_ref(ref.buf, oid.hash)) {
+               if (read_ref(ref.buf, &oid)) {
                        error(_("tag '%s' not found."), *p);
                        had_error = 1;
                        continue;
@@ -97,7 +97,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
 static int delete_tag(const char *name, const char *ref,
                      const struct object_id *oid, const void *cb_data)
 {
-       if (delete_ref(NULL, ref, oid->hash, 0))
+       if (delete_ref(NULL, ref, oid, 0))
                return 1;
        printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(oid->hash, DEFAULT_ABBREV));
        return 0;
@@ -518,7 +518,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (strbuf_check_tag_ref(&ref, tag))
                die(_("'%s' is not a valid tag name."), tag);
 
-       if (read_ref(ref.buf, prev.hash))
+       if (read_ref(ref.buf, &prev))
                oidclr(&prev);
        else if (!force)
                die(_("tag '%s' already exists"), tag);
@@ -544,7 +544,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, ref.buf, object.hash, prev.hash,
+           ref_transaction_update(transaction, ref.buf, &object, &prev,
                                   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
                                   reflog_msg.buf, &err) ||
            ref_transaction_commit(transaction, &err))
index bf7420b808fd1e0314d981bf65073df62f7578a4..58d1c2d2827d61899d73f1ea7632c5ee219f3ace 100644 (file)
@@ -16,6 +16,7 @@
 #include "pathspec.h"
 #include "dir.h"
 #include "split-index.h"
+#include "fsmonitor.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -32,6 +33,7 @@ static int force_remove;
 static int verbose;
 static int mark_valid_only;
 static int mark_skip_worktree_only;
+static int mark_fsmonitor_only;
 #define MARK_FLAG 1
 #define UNMARK_FLAG 2
 static struct strbuf mtime_dir = STRBUF_INIT;
@@ -228,6 +230,7 @@ static int mark_ce_flags(const char *path, int flag, int mark)
        int namelen = strlen(path);
        int pos = cache_name_pos(path, namelen);
        if (0 <= pos) {
+               mark_fsmonitor_invalid(&the_index, active_cache[pos]);
                if (mark)
                        active_cache[pos]->ce_flags |= flag;
                else
@@ -328,7 +331,7 @@ static int process_directory(const char *path, int len, struct stat *st)
                if (S_ISGITLINK(ce->ce_mode)) {
 
                        /* Do nothing to the index if there is no HEAD! */
-                       if (resolve_gitlink_ref(path, "HEAD", oid.hash) < 0)
+                       if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
                                return 0;
 
                        return add_one_path(ce, path, len, st);
@@ -354,7 +357,7 @@ static int process_directory(const char *path, int len, struct stat *st)
        }
 
        /* No match - should we add it as a gitlink? */
-       if (!resolve_gitlink_ref(path, "HEAD", oid.hash))
+       if (!resolve_gitlink_ref(path, "HEAD", &oid))
                return add_one_path(NULL, path, len, st);
 
        /* Error out. */
@@ -460,6 +463,11 @@ static void update_one(const char *path)
                        die("Unable to mark file %s", path);
                return;
        }
+       if (mark_fsmonitor_only) {
+               if (mark_ce_flags(path, CE_FSMONITOR_VALID, mark_fsmonitor_only == MARK_FLAG))
+                       die("Unable to mark file %s", path);
+               return;
+       }
 
        if (force_remove) {
                if (remove_file_from_cache(path))
@@ -679,9 +687,9 @@ static int unresolve_one(const char *path)
 
 static void read_head_pointers(void)
 {
-       if (read_ref("HEAD", head_oid.hash))
+       if (read_ref("HEAD", &head_oid))
                die("No HEAD -- no initial commit yet?");
-       if (read_ref("MERGE_HEAD", merge_head_oid.hash)) {
+       if (read_ref("MERGE_HEAD", &merge_head_oid)) {
                fprintf(stderr, "Not in the middle of a merge.\n");
                exit(0);
        }
@@ -721,7 +729,7 @@ static int do_reupdate(int ac, const char **av,
                       PATHSPEC_PREFER_CWD,
                       prefix, av + 1);
 
-       if (read_ref("HEAD", head_oid.hash))
+       if (read_ref("HEAD", &head_oid))
                /* If there is no HEAD, that means it is an initial
                 * commit.  Update everything in the index.
                 */
@@ -917,6 +925,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        struct refresh_params refresh_args = {0, &has_errors};
        int lock_error = 0;
        int split_index = -1;
+       int force_write = 0;
+       int fsmonitor = -1;
        struct lock_file lock_file = LOCK_INIT;
        struct parse_opt_ctx_t ctx;
        strbuf_getline_fn getline_fn;
@@ -1008,6 +1018,16 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                            N_("test if the filesystem supports untracked cache"), UC_TEST),
                OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
                            N_("enable untracked cache without testing the filesystem"), UC_FORCE),
+               OPT_SET_INT(0, "force-write-index", &force_write,
+                       N_("write out the index even if is not flagged as changed"), 1),
+               OPT_BOOL(0, "fsmonitor", &fsmonitor,
+                       N_("enable or disable file system monitor")),
+               {OPTION_SET_INT, 0, "fsmonitor-valid", &mark_fsmonitor_only, NULL,
+                       N_("mark files as fsmonitor valid"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+               {OPTION_SET_INT, 0, "no-fsmonitor-valid", &mark_fsmonitor_only, NULL,
+                       N_("clear fsmonitor valid bit"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
                OPT_END()
        };
 
@@ -1146,7 +1166,23 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                die("BUG: bad untracked_cache value: %d", untracked_cache);
        }
 
-       if (active_cache_changed) {
+       if (fsmonitor > 0) {
+               if (git_config_get_fsmonitor() == 0)
+                       warning(_("core.fsmonitor is unset; "
+                               "set it if you really want to "
+                               "enable fsmonitor"));
+               add_fsmonitor(&the_index);
+               report(_("fsmonitor enabled"));
+       } else if (!fsmonitor) {
+               if (git_config_get_fsmonitor() == 1)
+                       warning(_("core.fsmonitor is set; "
+                               "remove it if you really want to "
+                               "disable fsmonitor"));
+               remove_fsmonitor(&the_index);
+               report(_("fsmonitor disabled"));
+       }
+
+       if (active_cache_changed || force_write) {
                if (newfd < 0) {
                        if (refresh_args.flags & REFRESH_QUIET)
                                exit(128);
index 6b90c5deadb62600aa08166f500bde24cb800fbf..4b4714b3fd82ca9510b9b241867e497cdae92e76 100644 (file)
@@ -200,7 +200,7 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,
                die("update %s: extra input: %s", refname, next);
 
        if (ref_transaction_update(transaction, refname,
-                                  new_oid.hash, have_old ? old_oid.hash : NULL,
+                                  &new_oid, have_old ? &old_oid : NULL,
                                   update_flags | create_reflog_flag,
                                   msg, &err))
                die("%s", err.buf);
@@ -232,7 +232,7 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,
        if (*next != line_termination)
                die("create %s: extra input: %s", refname, next);
 
-       if (ref_transaction_create(transaction, refname, new_oid.hash,
+       if (ref_transaction_create(transaction, refname, &new_oid,
                                   update_flags | create_reflog_flag,
                                   msg, &err))
                die("%s", err.buf);
@@ -269,7 +269,7 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,
                die("delete %s: extra input: %s", refname, next);
 
        if (ref_transaction_delete(transaction, refname,
-                                  have_old ? old_oid.hash : NULL,
+                                  have_old ? &old_oid : NULL,
                                   update_flags, msg, &err))
                die("%s", err.buf);
 
@@ -298,7 +298,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,
        if (*next != line_termination)
                die("verify %s: extra input: %s", refname, next);
 
-       if (ref_transaction_verify(transaction, refname, old_oid.hash,
+       if (ref_transaction_verify(transaction, refname, &old_oid,
                                   update_flags, &err))
                die("%s", err.buf);
 
@@ -312,7 +312,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,
 static const char *parse_cmd_option(struct strbuf *input, const char *next)
 {
        if (!strncmp(next, "no-deref", 8) && next[8] == line_termination)
-               update_flags |= REF_NODEREF;
+               update_flags |= REF_NO_DEREF;
        else
                die("option unknown: %s", next);
        return next + 8;
@@ -427,17 +427,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        }
 
        if (no_deref)
-               flags = REF_NODEREF;
+               flags = REF_NO_DEREF;
        if (delete)
                /*
                 * For purposes of backwards compatibility, we treat
                 * NULL_SHA1 as "don't care" here:
                 */
                return delete_ref(msg, refname,
-                                 (oldval && !is_null_oid(&oldoid)) ? oldoid.hash : NULL,
+                                 (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL,
                                  flags);
        else
-               return update_ref(msg, refname, oid.hash, oldval ? oldoid.hash : NULL,
+               return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL,
                                  flags | create_reflog_flag,
                                  UPDATE_REFS_DIE_ON_ERR);
 }
index 7b9307aa588a70a47de4c53a380e165f90924ab9..7cef5b120b7786d107282d03052c5e06d3a2c85a 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "checkout.h"
 #include "config.h"
 #include "builtin.h"
 #include "dir.h"
@@ -32,8 +33,19 @@ struct add_opts {
 
 static int show_only;
 static int verbose;
+static int guess_remote;
 static timestamp_t expire;
 
+static int git_worktree_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "worktree.guessremote")) {
+               guess_remote = git_config_bool(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
 static int prune_worktree(const char *id, struct strbuf *reason)
 {
        struct stat st;
@@ -218,20 +230,21 @@ static int add_worktree(const char *path, const char *refname,
        int counter = 0, len, ret;
        struct strbuf symref = STRBUF_INIT;
        struct commit *commit = NULL;
+       int is_branch = 0;
 
        if (file_exists(path) && !is_empty_dir(path))
                die(_("'%s' already exists"), path);
 
        /* is 'refname' a branch or commit? */
        if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
-                ref_exists(symref.buf)) { /* it's a branch */
+           ref_exists(symref.buf)) {
+               is_branch = 1;
                if (!opts->force)
                        die_if_checked_out(symref.buf, 0);
-       } else { /* must be a commit */
-               commit = lookup_commit_reference_by_name(refname);
-               if (!commit)
-                       die(_("invalid reference: %s"), refname);
        }
+       commit = lookup_commit_reference_by_name(refname);
+       if (!commit)
+               die(_("invalid reference: %s"), refname);
 
        name = worktree_basename(path, &len);
        git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name);
@@ -296,7 +309,7 @@ static int add_worktree(const char *path, const char *refname,
        argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
        cp.git_cmd = 1;
 
-       if (commit)
+       if (!is_branch)
                argv_array_pushl(&cp.args, "update-ref", "HEAD",
                                 oid_to_hex(&commit->object.oid), NULL);
        else
@@ -327,6 +340,15 @@ static int add_worktree(const char *path, const char *refname,
                strbuf_addf(&sb, "%s/locked", sb_repo.buf);
                unlink_or_warn(sb.buf);
        }
+
+       /*
+        * Hook failure does not warrant worktree deletion, so run hook after
+        * is_junk is cleared, but do return appropriate code when hook fails.
+        */
+       if (!ret && opts->checkout)
+               ret = run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid),
+                                 oid_to_hex(&commit->object.oid), "1", NULL);
+
        argv_array_clear(&child_env);
        strbuf_release(&sb);
        strbuf_release(&symref);
@@ -341,6 +363,7 @@ static int add(int ac, const char **av, const char *prefix)
        const char *new_branch_force = NULL;
        char *path;
        const char *branch;
+       const char *opt_track = NULL;
        struct option options[] = {
                OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
                OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -350,6 +373,11 @@ static int add(int ac, const char **av, const char *prefix)
                OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
                OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
                OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
+               OPT_PASSTHRU(0, "track", &opt_track, NULL,
+                            N_("set up tracking mode (see git-branch(1))"),
+                            PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
+               OPT_BOOL(0, "guess-remote", &guess_remote,
+                        N_("try to match the new branch name with a remote-tracking branch")),
                OPT_END()
        };
 
@@ -384,6 +412,28 @@ static int add(int ac, const char **av, const char *prefix)
                int n;
                const char *s = worktree_basename(path, &n);
                opts.new_branch = xstrndup(s, n);
+               if (guess_remote) {
+                       struct object_id oid;
+                       const char *remote =
+                               unique_tracking_name(opts.new_branch, &oid);
+                       if (remote)
+                               branch = remote;
+               }
+       }
+
+       if (ac == 2 && !opts.new_branch && !opts.detach) {
+               struct object_id oid;
+               struct commit *commit;
+               const char *remote;
+
+               commit = lookup_commit_reference_by_name(branch);
+               if (!commit) {
+                       remote = unique_tracking_name(branch, &oid);
+                       if (remote) {
+                               opts.new_branch = branch;
+                               branch = remote;
+                       }
+               }
        }
 
        if (opts.new_branch) {
@@ -394,9 +444,13 @@ static int add(int ac, const char **av, const char *prefix)
                        argv_array_push(&cp.args, "--force");
                argv_array_push(&cp.args, opts.new_branch);
                argv_array_push(&cp.args, branch);
+               if (opt_track)
+                       argv_array_push(&cp.args, opt_track);
                if (run_command(&cp))
                        return -1;
                branch = opts.new_branch;
+       } else if (opt_track) {
+               die(_("--[no-]track can only be used if a new branch is created"));
        }
 
        UNLEAK(path);
@@ -410,7 +464,7 @@ static void show_worktree_porcelain(struct worktree *wt)
        if (wt->is_bare)
                printf("bare\n");
        else {
-               printf("HEAD %s\n", sha1_to_hex(wt->head_sha1));
+               printf("HEAD %s\n", oid_to_hex(&wt->head_oid));
                if (wt->is_detached)
                        printf("detached\n");
                else if (wt->head_ref)
@@ -430,7 +484,7 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
                strbuf_addstr(&sb, "(bare)");
        else {
                strbuf_addf(&sb, "%-*s ", abbrev_len,
-                               find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV));
+                               find_unique_abbrev(wt->head_oid.hash, DEFAULT_ABBREV));
                if (wt->is_detached)
                        strbuf_addstr(&sb, "(detached HEAD)");
                else if (wt->head_ref) {
@@ -455,7 +509,7 @@ static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
 
                if (path_len > *maxlen)
                        *maxlen = path_len;
-               sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev));
+               sha1_len = strlen(find_unique_abbrev(wt[i]->head_oid.hash, *abbrev));
                if (sha1_len > *abbrev)
                        *abbrev = sha1_len;
        }
@@ -557,7 +611,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_default_config, NULL);
+       git_config(git_worktree_config, NULL);
 
        if (ac < 2)
                usage_with_options(worktree_usage, options);
index c092d5d68f32652c229fe232af72d02111f51de7..93290962c95e6c27732f121f980d03289ac1ea6d 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -338,9 +338,9 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)
 
                if (e->item->flags & UNINTERESTING)
                        continue;
-               if (dwim_ref(e->name, strlen(e->name), oid.hash, &ref) != 1)
+               if (dwim_ref(e->name, strlen(e->name), &oid, &ref) != 1)
                        goto skip_write_ref;
-               if (read_ref_full(e->name, RESOLVE_REF_READING, oid.hash, &flag))
+               if (read_ref_full(e->name, RESOLVE_REF_READING, &oid, &flag))
                        flag = 0;
                display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
 
index d3f7401278bd5130558dde18da1235c188bb7303..e03e72c34a5c1fc618994ee63f38875d28d91886 100644 (file)
@@ -602,11 +602,11 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
 
 int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix)
 {
-       int entries, was_valid, newfd;
+       int entries, was_valid;
        struct lock_file lock_file = LOCK_INIT;
        int ret = 0;
 
-       newfd = hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);
+       hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);
 
        entries = read_index_from(index_state, index_path);
        if (entries < 0) {
@@ -625,10 +625,7 @@ int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, co
                        ret = WRITE_TREE_UNMERGED_INDEX;
                        goto out;
                }
-               if (0 <= newfd) {
-                       if (!write_locked_index(index_state, &lock_file, COMMIT_LOCK))
-                               newfd = -1;
-               }
+               write_locked_index(index_state, &lock_file, COMMIT_LOCK);
                /* Not being able to write is fine -- we are only interested
                 * in updating the cache-tree part, and if the next caller
                 * ends up using the old index with unupdated cache-tree part
@@ -650,8 +647,7 @@ int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, co
                hashcpy(sha1, index_state->cache_tree->oid.hash);
 
 out:
-       if (0 <= newfd)
-               rollback_lock_file(&lock_file);
+       rollback_lock_file(&lock_file);
        return ret;
 }
 
diff --git a/cache.h b/cache.h
index 6440e2bf21f5800db98f82fcedff46478188674e..a2ec8c0b55422f2b95a30d140cb2bfdf15f0721d 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -14,6 +14,7 @@
 #include "hash.h"
 #include "path.h"
 #include "sha1-array.h"
+#include "repository.h"
 
 #ifndef platform_SHA_CTX
 /*
@@ -77,6 +78,8 @@ struct object_id {
        unsigned char hash[GIT_MAX_RAWSZ];
 };
 
+#define the_hash_algo the_repository->hash_algo
+
 #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
 #define DTYPE(de)      ((de)->d_type)
 #else
@@ -204,6 +207,7 @@ struct cache_entry {
 #define CE_ADDED             (1 << 19)
 
 #define CE_HASHED            (1 << 20)
+#define CE_FSMONITOR_VALID   (1 << 21)
 #define CE_WT_REMOVE         (1 << 22) /* remove in work directory */
 #define CE_CONFLICTED        (1 << 23)
 
@@ -327,6 +331,7 @@ static inline unsigned int canon_mode(unsigned int mode)
 #define CACHE_TREE_CHANGED     (1 << 5)
 #define SPLIT_INDEX_ORDERED    (1 << 6)
 #define UNTRACKED_CHANGED      (1 << 7)
+#define FSMONITOR_CHANGED      (1 << 8)
 
 struct split_index;
 struct untracked_cache;
@@ -345,6 +350,8 @@ struct index_state {
        struct hashmap dir_hash;
        unsigned char sha1[20];
        struct untracked_cache *untracked;
+       uint64_t fsmonitor_last_update;
+       struct ewah_bitmap *fsmonitor_dirty;
 };
 
 extern struct index_state the_index;
@@ -446,6 +453,16 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
 #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
 
+/*
+ * Environment variable used in handshaking the wire protocol.
+ * Contains a colon ':' separated list of keys with optional values
+ * 'key[=value]'.  Presence of unknown keys and values must be
+ * ignored.
+ */
+#define GIT_PROTOCOL_ENVIRONMENT "GIT_PROTOCOL"
+/* HTTP header used to handshake the wire protocol */
+#define GIT_PROTOCOL_HEADER "Git-Protocol"
+
 /*
  * This environment variable is expected to contain a boolean indicating
  * whether we should or should not treat:
@@ -602,9 +619,28 @@ extern int do_read_index(struct index_state *istate, const char *path,
 extern int read_index_from(struct index_state *, const char *path);
 extern int is_index_unborn(struct index_state *);
 extern int read_index_unmerged(struct index_state *);
+
+/* For use with `write_locked_index()`. */
 #define COMMIT_LOCK            (1 << 0)
-#define CLOSE_LOCK             (1 << 1)
+
+/*
+ * Write the index while holding an already-taken lock. Close the lock,
+ * and if `COMMIT_LOCK` is given, commit it.
+ *
+ * Unless a split index is in use, write the index into the lockfile.
+ *
+ * With a split index, write the shared index to a temporary file,
+ * adjust its permissions and rename it into place, then write the
+ * split index to the lockfile. If the temporary file for the shared
+ * index cannot be created, fall back to the behavior described in
+ * the previous paragraph.
+ *
+ * With `COMMIT_LOCK`, the lock is always committed or rolled back.
+ * Without it, the lock is closed, but neither committed nor rolled
+ * back.
+ */
 extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
+
 extern int discard_index(struct index_state *);
 extern void move_index_extensions(struct index_state *dst, struct index_state *src);
 extern int unmerged_index(const struct index_state *);
@@ -681,11 +717,14 @@ extern void *read_blob_data_from_index(const struct index_state *, const char *,
 #define CE_MATCH_IGNORE_MISSING                0x08
 /* enable stat refresh */
 #define CE_MATCH_REFRESH               0x10
-extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
-extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
+/* don't refresh_fsmonitor state or do stat comparison even if CE_FSMONITOR_VALID is true */
+#define CE_MATCH_IGNORE_FSMONITOR 0X20
+extern int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
+extern int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 
 #define HASH_WRITE_OBJECT 1
 #define HASH_FORMAT_CHECK 2
+#define HASH_RENORMALIZE  4
 extern int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
 extern int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags);
 
@@ -716,12 +755,17 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *, unsigned int);
 
+/*
+ * Opportunistically update the index but do not complain if we can't.
+ * The lockfile is always committed or rolled back.
+ */
 extern void update_index_if_able(struct index_state *, struct lock_file *);
 
 extern int hold_locked_index(struct lock_file *, int);
 extern void set_alternate_index_output(const char *);
 
 extern int verify_index_checksum;
+extern int verify_ce_order;
 
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
@@ -775,6 +819,7 @@ extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
 extern int protect_hfs;
 extern int protect_ntfs;
+extern const char *core_fsmonitor;
 
 /*
  * Include broken refs in all ref iterations, which will
@@ -865,6 +910,7 @@ struct repository_format {
        int version;
        int precious_objects;
        int is_bare;
+       int hash_algo;
        char *work_tree;
        struct string_list unknown_extensions;
 };
@@ -997,22 +1043,22 @@ extern const struct object_id empty_blob_oid;
 
 static inline int is_empty_blob_sha1(const unsigned char *sha1)
 {
-       return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
+       return !hashcmp(sha1, the_hash_algo->empty_blob->hash);
 }
 
 static inline int is_empty_blob_oid(const struct object_id *oid)
 {
-       return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN);
+       return !oidcmp(oid, the_hash_algo->empty_blob);
 }
 
 static inline int is_empty_tree_sha1(const unsigned char *sha1)
 {
-       return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN);
+       return !hashcmp(sha1, the_hash_algo->empty_tree->hash);
 }
 
 static inline int is_empty_tree_oid(const struct object_id *oid)
 {
-       return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
+       return !oidcmp(oid, the_hash_algo->empty_tree);
 }
 
 /* set default permissions by passing mode arguments to open(2) */
@@ -1317,6 +1363,13 @@ extern int set_disambiguate_hint_config(const char *var, const char *value);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern int get_oid_hex(const char *hex, struct object_id *sha1);
 
+/*
+ * Read `len` pairs of hexadecimal digits from `hex` and write the
+ * values to `binary` as `len` bytes. Return 0 on success, or -1 if
+ * the input does not consist of hex digits).
+ */
+extern int hex_to_bytes(unsigned char *binary, const char *hex, size_t len);
+
 /*
  * Convert a binary sha1 to its hex equivalent. The `_r` variant is reentrant,
  * and writes the NUL-terminated output to the buffer `out`, which must be at
@@ -1438,6 +1491,7 @@ extern const char *ident_default_name(void);
 extern const char *ident_default_email(void);
 extern const char *git_editor(void);
 extern const char *git_pager(int stdout_is_tty);
+extern int is_terminal_dumb(void);
 extern int git_ident_config(const char *, const char *, void *);
 extern void reset_ident_date(void);
 
@@ -1919,4 +1973,10 @@ void sleep_millisec(int millisec);
  */
 void safe_create_dir(const char *dir, int share);
 
+/*
+ * Should we print an ellipsis after an abbreviated SHA-1 value
+ * when doing diff-raw output or indicating a detached HEAD?
+ */
+extern int print_sha1_ellipsis(void);
+
 #endif /* CACHE_H */
diff --git a/checkout.c b/checkout.c
new file mode 100644 (file)
index 0000000..ac42630
--- /dev/null
@@ -0,0 +1,43 @@
+#include "cache.h"
+#include "remote.h"
+#include "checkout.h"
+
+struct tracking_name_data {
+       /* const */ char *src_ref;
+       char *dst_ref;
+       struct object_id *dst_oid;
+       int unique;
+};
+
+static int check_tracking_name(struct remote *remote, void *cb_data)
+{
+       struct tracking_name_data *cb = cb_data;
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.src = cb->src_ref;
+       if (remote_find_tracking(remote, &query) ||
+           get_oid(query.dst, cb->dst_oid)) {
+               free(query.dst);
+               return 0;
+       }
+       if (cb->dst_ref) {
+               free(query.dst);
+               cb->unique = 0;
+               return 0;
+       }
+       cb->dst_ref = query.dst;
+       return 0;
+}
+
+const char *unique_tracking_name(const char *name, struct object_id *oid)
+{
+       struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+       cb_data.src_ref = xstrfmt("refs/heads/%s", name);
+       cb_data.dst_oid = oid;
+       for_each_remote(check_tracking_name, &cb_data);
+       free(cb_data.src_ref);
+       if (cb_data.unique)
+               return cb_data.dst_ref;
+       free(cb_data.dst_ref);
+       return NULL;
+}
diff --git a/checkout.h b/checkout.h
new file mode 100644 (file)
index 0000000..9980711
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef CHECKOUT_H
+#define CHECKOUT_H
+
+#include "cache.h"
+
+/*
+ * Check if the branch name uniquely matches a branch name on a remote
+ * tracking branch.  Return the name of the remote if such a branch
+ * exists, NULL otherwise.
+ */
+extern const char *unique_tracking_name(const char *name, struct object_id *oid);
+
+#endif /* CHECKOUT_H */
index 5bd06fe900d44916fd11875fa5641e9b97e90540..75a9fd2475168fce9e229ce44db2808227700e17 100755 (executable)
@@ -8,10 +8,8 @@
 P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION
 LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION
 
-case "${TRAVIS_OS_NAME:-linux}" in
-linux)
-       export GIT_TEST_HTTPD=YesPlease
-
+case "$jobname" in
+linux-clang|linux-gcc)
        mkdir --parents "$P4_PATH"
        pushd "$P4_PATH"
                wget --quiet "$P4WHENCE/bin.linux26x86_64/p4d"
@@ -26,7 +24,7 @@ linux)
                cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs .
        popd
        ;;
-osx)
+osx-clang|osx-gcc)
        brew update --quiet
        # Uncomment this if you want to run perf tests:
        # brew install gnu-time
index ac05f1f4694347bbd96fe1911aa33450d43ae24f..331d3eb3a6914cd811448e002e8b10ed503e7263 100755 (executable)
@@ -23,14 +23,42 @@ skip_branch_tip_with_tag () {
 
 # Set 'exit on error' for all CI scripts to let the caller know that
 # something went wrong
-set -e
+set -ex
 
 skip_branch_tip_with_tag
 
-case "${TRAVIS_OS_NAME:-linux}" in
-linux)
+if test -z "$jobname"
+then
+       jobname="$TRAVIS_OS_NAME-$CC"
+fi
+
+export DEVELOPER=1
+export DEFAULT_TEST_TARGET=prove
+export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
+export GIT_TEST_OPTS="--verbose-log"
+export GIT_TEST_CLONE_2GB=YesPlease
+
+case "$jobname" in
+linux-clang|linux-gcc)
+       export GIT_TEST_HTTPD=YesPlease
+
+       # The Linux build installs the defined dependency versions below.
+       # The OS X build installs the latest available versions. Keep that
+       # in mind when you encounter a broken OS X build!
+       export LINUX_P4_VERSION="16.2"
+       export LINUX_GIT_LFS_VERSION="1.5.2"
+
        P4_PATH="$(pwd)/custom/p4"
        GIT_LFS_PATH="$(pwd)/custom/git-lfs"
        export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH"
        ;;
+osx-clang|osx-gcc)
+       # t9810 occasionally fails on Travis CI OS X
+       # t9816 occasionally fails with "TAP out of sequence errors" on
+       # Travis CI OS X
+       export GIT_SKIP_TESTS="t9810 t9816"
+       ;;
+GETTEXT_POISON)
+       export GETTEXT_POISON=YesPlease
+       ;;
 esac
diff --git a/color.c b/color.c
index 9a9261ac164f503e2a9240806f4f44bedf9b8192..d48dd947c987cdca13f103fd8b4a2e56eceab238 100644 (file)
--- a/color.c
+++ b/color.c
@@ -329,8 +329,7 @@ static int check_auto_color(void)
        if (color_stdout_is_tty < 0)
                color_stdout_is_tty = isatty(1);
        if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) {
-               char *term = getenv("TERM");
-               if (term && strcmp(term, "dumb"))
+               if (!is_terminal_dumb())
                        return 1;
        }
        return 0;
index 9e163d5adabd93fd48a6b563c5f31c32740e01c7..2505de119a2be37e9dfb313a2e44db68595ad13f 100644 (file)
@@ -898,7 +898,7 @@ static void show_combined_header(struct combine_diff_path *elem,
                                 int show_file_header)
 {
        struct diff_options *opt = &rev->diffopt;
-       int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? GIT_SHA1_HEXSZ : DEFAULT_ABBREV;
+       int abbrev = opt->flags.full_index ? GIT_SHA1_HEXSZ : DEFAULT_ABBREV;
        const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
        const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
        const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
@@ -987,7 +987,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
        userdiff = userdiff_find_by_path(elem->path);
        if (!userdiff)
                userdiff = userdiff_find_by_name("default");
-       if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV))
+       if (opt->flags.allow_textconv)
                textconv = userdiff_get_textconv(userdiff);
 
        /* Read the result of merge first */
@@ -1014,7 +1014,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        elem->mode = canon_mode(st.st_mode);
                } else if (S_ISDIR(st.st_mode)) {
                        struct object_id oid;
-                       if (resolve_gitlink_ref(elem->path, "HEAD", oid.hash) < 0)
+                       if (resolve_gitlink_ref(elem->path, "HEAD", &oid) < 0)
                                result = grab_blob(&elem->oid, elem->mode,
                                                   &result_size, NULL, NULL);
                        else
@@ -1413,8 +1413,8 @@ void diff_tree_combined(const struct object_id *oid,
 
        diffopts = *opt;
        copy_pathspec(&diffopts.pathspec, &opt->pathspec);
-       DIFF_OPT_SET(&diffopts, RECURSIVE);
-       DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
+       diffopts.flags.recursive = 1;
+       diffopts.flags.allow_external = 0;
 
        /* find set of paths that everybody touches
         *
@@ -1435,7 +1435,7 @@ void diff_tree_combined(const struct object_id *oid,
         * NOTE please keep this semantically in sync with diffcore_std()
         */
        need_generic_pathscan = opt->skip_stat_unmatch  ||
-                       DIFF_OPT_TST(opt, FOLLOW_RENAMES)       ||
+                       opt->flags.follow_renames       ||
                        opt->break_opt != -1    ||
                        opt->detect_rename      ||
                        opt->pickaxe            ||
index 99a3fea68d3f63064351ccabeaf7f11cdf8e0d04..8c68ca1a5a18721538bb44fe37cdf94223f2a5b5 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -7,6 +7,7 @@
 #include "decorate.h"
 #include "gpg-interface.h"
 #include "string-list.h"
+#include "pretty.h"
 
 struct commit_list {
        struct commit *item;
@@ -121,93 +122,13 @@ struct commit_list *copy_commit_list(struct commit_list *list);
 
 void free_commit_list(struct commit_list *list);
 
-/* Commit formats */
-enum cmit_fmt {
-       CMIT_FMT_RAW,
-       CMIT_FMT_MEDIUM,
-       CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
-       CMIT_FMT_SHORT,
-       CMIT_FMT_FULL,
-       CMIT_FMT_FULLER,
-       CMIT_FMT_ONELINE,
-       CMIT_FMT_EMAIL,
-       CMIT_FMT_MBOXRD,
-       CMIT_FMT_USERFORMAT,
-
-       CMIT_FMT_UNSPECIFIED
-};
-
-static inline int cmit_fmt_is_mail(enum cmit_fmt fmt)
-{
-       return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD);
-}
-
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 
-struct pretty_print_context {
-       /*
-        * Callers should tweak these to change the behavior of pp_* functions.
-        */
-       enum cmit_fmt fmt;
-       int abbrev;
-       const char *after_subject;
-       int preserve_subject;
-       struct date_mode date_mode;
-       unsigned date_mode_explicit:1;
-       int print_email_subject;
-       int expand_tabs_in_log;
-       int need_8bit_cte;
-       char *notes_message;
-       struct reflog_walk_info *reflog_info;
-       struct rev_info *rev;
-       const char *output_encoding;
-       struct string_list *mailmap;
-       int color;
-       struct ident_split *from_ident;
-
-       /*
-        * Fields below here are manipulated internally by pp_* functions and
-        * should not be counted on by callers.
-        */
-       struct string_list in_body_headers;
-       int graph_width;
-};
-
-struct userformat_want {
-       unsigned notes:1;
-};
-
 extern int has_non_ascii(const char *text);
 extern const char *logmsg_reencode(const struct commit *commit,
                                   char **commit_encoding,
                                   const char *output_encoding);
-extern void get_commit_format(const char *arg, struct rev_info *);
-extern const char *format_subject(struct strbuf *sb, const char *msg,
-                                 const char *line_separator);
-extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);
-extern int commit_format_is_empty(enum cmit_fmt);
 extern const char *skip_blank_lines(const char *msg);
-extern void format_commit_message(const struct commit *commit,
-                                 const char *format, struct strbuf *sb,
-                                 const struct pretty_print_context *context);
-extern void pretty_print_commit(struct pretty_print_context *pp,
-                               const struct commit *commit,
-                               struct strbuf *sb);
-extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
-                          struct strbuf *sb);
-void pp_user_info(struct pretty_print_context *pp,
-                 const char *what, struct strbuf *sb,
-                 const char *line, const char *encoding);
-void pp_title_line(struct pretty_print_context *pp,
-                  const char **msg_p,
-                  struct strbuf *sb,
-                  const char *encoding,
-                  int need_8bit_cte);
-void pp_remainder(struct pretty_print_context *pp,
-                 const char **msg_p,
-                 struct strbuf *sb,
-                 int indent);
-
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
index 7d063e9e40a213ce378d90377a9ac2b1f04ddbe3..5078ce5eccfaf17620c21e0a7f7133b3badef3d3 100644 (file)
@@ -158,7 +158,9 @@ static inline uint64_t git_bswap64(uint64_t x)
 
 #define get_be16(p)    ntohs(*(unsigned short *)(p))
 #define get_be32(p)    ntohl(*(unsigned int *)(p))
+#define get_be64(p)    ntohll(*(uint64_t *)(p))
 #define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
+#define put_be64(p, v) do { *(uint64_t *)(p) = htonll(v); } while (0)
 
 #else
 
@@ -178,6 +180,13 @@ static inline uint32_t get_be32(const void *ptr)
                (uint32_t)p[3] <<  0;
 }
 
+static inline uint64_t get_be64(const void *ptr)
+{
+       const unsigned char *p = ptr;
+       return  (uint64_t)get_be32(&p[0]) << 32 |
+               (uint64_t)get_be32(&p[4]) <<  0;
+}
+
 static inline void put_be32(void *ptr, uint32_t value)
 {
        unsigned char *p = ptr;
@@ -187,4 +196,17 @@ static inline void put_be32(void *ptr, uint32_t value)
        p[3] = value >>  0;
 }
 
+static inline void put_be64(void *ptr, uint64_t value)
+{
+       unsigned char *p = ptr;
+       p[0] = value >> 56;
+       p[1] = value >> 48;
+       p[2] = value >> 40;
+       p[3] = value >> 32;
+       p[4] = value >> 24;
+       p[5] = value >> 16;
+       p[6] = value >>  8;
+       p[7] = value >>  0;
+}
+
 #endif
index 41862d4a32d27806a14ca7337f4aa98f04278eaf..e617c2018d22b6d389548a3573d2deb2ec608b99 100644 (file)
--- a/config.c
+++ b/config.c
@@ -990,6 +990,16 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
        return 0;
 }
 
+int git_config_expiry_date(timestamp_t *timestamp, const char *var, const char *value)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       if (parse_expiry_date(value, timestamp))
+               return error(_("'%s' for '%s' is not a valid timestamp"),
+                            value, var);
+       return 0;
+}
+
 static int git_default_core_config(const char *var, const char *value)
 {
        /* This needs a better name */
@@ -2156,6 +2166,20 @@ int git_config_get_max_percent_split_change(void)
        return -1; /* default value */
 }
 
+int git_config_get_fsmonitor(void)
+{
+       if (git_config_get_pathname("core.fsmonitor", &core_fsmonitor))
+               core_fsmonitor = getenv("GIT_FSMONITOR_TEST");
+
+       if (core_fsmonitor && !*core_fsmonitor)
+               core_fsmonitor = NULL;
+
+       if (core_fsmonitor)
+               return 1;
+
+       return 0;
+}
+
 NORETURN
 void git_die_config_linenr(const char *key, const char *filename, int linenr)
 {
@@ -2751,7 +2775,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
 {
        int ret = 0, remove = 0;
        char *filename_buf = NULL;
-       struct lock_file *lock;
+       struct lock_file lock = LOCK_INIT;
        int out_fd;
        char buf[1024];
        FILE *config_file = NULL;
@@ -2766,8 +2790,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
        if (!config_filename)
                config_filename = filename_buf = git_pathdup("config");
 
-       lock = xcalloc(1, sizeof(struct lock_file));
-       out_fd = hold_lock_file_for_update(lock, config_filename, 0);
+       out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (out_fd < 0) {
                ret = error("could not lock config file %s", config_filename);
                goto out;
@@ -2786,9 +2809,9 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                goto out;
        }
 
-       if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
+       if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
                ret = error_errno("chmod on %s failed",
-                                 get_lock_file_path(lock));
+                                 get_lock_file_path(&lock));
                goto out;
        }
 
@@ -2812,7 +2835,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                         */
                        if (copystr.len > 0) {
                                if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) {
-                                       ret = write_error(get_lock_file_path(lock));
+                                       ret = write_error(get_lock_file_path(&lock));
                                        goto out;
                                }
                                strbuf_reset(&copystr);
@@ -2828,7 +2851,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                                store.baselen = strlen(new_name);
                                if (!copy) {
                                        if (write_section(out_fd, new_name) < 0) {
-                                               ret = write_error(get_lock_file_path(lock));
+                                               ret = write_error(get_lock_file_path(&lock));
                                                goto out;
                                        }
                                        /*
@@ -2862,7 +2885,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                }
 
                if (write_in_full(out_fd, output, length) < 0) {
-                       ret = write_error(get_lock_file_path(lock));
+                       ret = write_error(get_lock_file_path(&lock));
                        goto out;
                }
        }
@@ -2874,7 +2897,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
         */
        if (copystr.len > 0) {
                if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) {
-                       ret = write_error(get_lock_file_path(lock));
+                       ret = write_error(get_lock_file_path(&lock));
                        goto out;
                }
                strbuf_reset(&copystr);
@@ -2883,13 +2906,13 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
        fclose(config_file);
        config_file = NULL;
 commit_and_out:
-       if (commit_lock_file(lock) < 0)
+       if (commit_lock_file(&lock) < 0)
                ret = error_errno("could not write config file %s",
                                  config_filename);
 out:
        if (config_file)
                fclose(config_file);
-       rollback_lock_file(lock);
+       rollback_lock_file(&lock);
 out_no_rollback:
        free(filename_buf);
        return ret;
index a49d26441622508fd3c6560ac9784e84541c10dc..ef70a9cac1e6dc67df24d157a4e5de38edd8c984 100644 (file)
--- a/config.h
+++ b/config.h
@@ -58,6 +58,7 @@ extern int git_config_bool_or_int(const char *, const char *, int *);
 extern int git_config_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_expiry_date(timestamp_t *, const char *, const char *);
 extern int git_config_set_in_file_gently(const char *, const char *, const char *);
 extern void git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set_gently(const char *, const char *);
@@ -212,6 +213,7 @@ extern int git_config_get_pathname(const char *key, const char **dest);
 extern int git_config_get_untracked_cache(void);
 extern int git_config_get_split_index(void);
 extern int git_config_get_max_percent_split_change(void);
+extern int git_config_get_fsmonitor(void);
 
 /* This dies if the configured or default date is in the future */
 extern int git_config_get_expiry(const char *key, const char **output);
index df56c0cbff4dfb1263917f6147bd7e1912418dd6..c3a014c5babf72ee4c0d135fec264afb37b040de 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -11,6 +11,8 @@
 #include "string-list.h"
 #include "sha1-array.h"
 #include "transport.h"
+#include "strbuf.h"
+#include "protocol.h"
 
 static char *server_capabilities;
 static const char *parse_feature_value(const char *, const char *, int *);
@@ -107,6 +109,118 @@ static void annotate_refs_with_symref_info(struct ref *ref)
        string_list_clear(&symref, 0);
 }
 
+/*
+ * Read one line of a server's ref advertisement into packet_buffer.
+ */
+static int read_remote_ref(int in, char **src_buf, size_t *src_len,
+                          int *responded)
+{
+       int len = packet_read(in, src_buf, src_len,
+                             packet_buffer, sizeof(packet_buffer),
+                             PACKET_READ_GENTLE_ON_EOF |
+                             PACKET_READ_CHOMP_NEWLINE);
+       const char *arg;
+       if (len < 0)
+               die_initial_contact(*responded);
+       if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg))
+               die("remote error: %s", arg);
+
+       *responded = 1;
+
+       return len;
+}
+
+#define EXPECTING_PROTOCOL_VERSION 0
+#define EXPECTING_FIRST_REF 1
+#define EXPECTING_REF 2
+#define EXPECTING_SHALLOW 3
+
+/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */
+static int process_protocol_version(void)
+{
+       switch (determine_protocol_version_client(packet_buffer)) {
+       case protocol_v1:
+               return 1;
+       case protocol_v0:
+               return 0;
+       default:
+               die("server is speaking an unknown protocol");
+       }
+}
+
+static void process_capabilities(int *len)
+{
+       int nul_location = strlen(packet_buffer);
+       if (nul_location == *len)
+               return;
+       server_capabilities = xstrdup(packet_buffer + nul_location + 1);
+       *len = nul_location;
+}
+
+static int process_dummy_ref(void)
+{
+       struct object_id oid;
+       const char *name;
+
+       if (parse_oid_hex(packet_buffer, &oid, &name))
+               return 0;
+       if (*name != ' ')
+               return 0;
+       name++;
+
+       return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
+}
+
+static void check_no_capabilities(int len)
+{
+       if (strlen(packet_buffer) != len)
+               warning("Ignoring capabilities after first line '%s'",
+                       packet_buffer + strlen(packet_buffer));
+}
+
+static int process_ref(int len, struct ref ***list, unsigned int flags,
+                      struct oid_array *extra_have)
+{
+       struct object_id old_oid;
+       const char *name;
+
+       if (parse_oid_hex(packet_buffer, &old_oid, &name))
+               return 0;
+       if (*name != ' ')
+               return 0;
+       name++;
+
+       if (extra_have && !strcmp(name, ".have")) {
+               oid_array_append(extra_have, &old_oid);
+       } else if (!strcmp(name, "capabilities^{}")) {
+               die("protocol error: unexpected capabilities^{}");
+       } else if (check_ref(name, flags)) {
+               struct ref *ref = alloc_ref(name);
+               oidcpy(&ref->old_oid, &old_oid);
+               **list = ref;
+               *list = &ref->next;
+       }
+       check_no_capabilities(len);
+       return 1;
+}
+
+static int process_shallow(int len, struct oid_array *shallow_points)
+{
+       const char *arg;
+       struct object_id old_oid;
+
+       if (!skip_prefix(packet_buffer, "shallow ", &arg))
+               return 0;
+
+       if (get_oid_hex(arg, &old_oid))
+               die("protocol error: expected shallow sha-1, got '%s'", arg);
+       if (!shallow_points)
+               die("repository on the other end cannot be shallow");
+       oid_array_append(shallow_points, &old_oid);
+       check_no_capabilities(len);
+       return 1;
+}
+
 /*
  * Read all the refs from the other end
  */
@@ -123,76 +237,41 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
         * willing to talk to us.  A hang-up before seeing any
         * response does not necessarily mean an ACL problem, though.
         */
-       int saw_response;
-       int got_dummy_ref_with_capabilities_declaration = 0;
+       int responded = 0;
+       int len;
+       int state = EXPECTING_PROTOCOL_VERSION;
 
        *list = NULL;
-       for (saw_response = 0; ; saw_response = 1) {
-               struct ref *ref;
-               struct object_id old_oid;
-               char *name;
-               int len, name_len;
-               char *buffer = packet_buffer;
-               const char *arg;
-
-               len = packet_read(in, &src_buf, &src_len,
-                                 packet_buffer, sizeof(packet_buffer),
-                                 PACKET_READ_GENTLE_ON_EOF |
-                                 PACKET_READ_CHOMP_NEWLINE);
-               if (len < 0)
-                       die_initial_contact(saw_response);
-
-               if (!len)
-                       break;
-
-               if (len > 4 && skip_prefix(buffer, "ERR ", &arg))
-                       die("remote error: %s", arg);
-
-               if (len == GIT_SHA1_HEXSZ + strlen("shallow ") &&
-                       skip_prefix(buffer, "shallow ", &arg)) {
-                       if (get_oid_hex(arg, &old_oid))
-                               die("protocol error: expected shallow sha-1, got '%s'", arg);
-                       if (!shallow_points)
-                               die("repository on the other end cannot be shallow");
-                       oid_array_append(shallow_points, &old_oid);
-                       continue;
-               }
-
-               if (len < GIT_SHA1_HEXSZ + 2 || get_oid_hex(buffer, &old_oid) ||
-                       buffer[GIT_SHA1_HEXSZ] != ' ')
-                       die("protocol error: expected sha/ref, got '%s'", buffer);
-               name = buffer + GIT_SHA1_HEXSZ + 1;
-
-               name_len = strlen(name);
-               if (len != name_len + GIT_SHA1_HEXSZ + 1) {
-                       free(server_capabilities);
-                       server_capabilities = xstrdup(name + name_len + 1);
-               }
 
-               if (extra_have && !strcmp(name, ".have")) {
-                       oid_array_append(extra_have, &old_oid);
-                       continue;
-               }
-
-               if (!strcmp(name, "capabilities^{}")) {
-                       if (saw_response)
-                               die("protocol error: unexpected capabilities^{}");
-                       if (got_dummy_ref_with_capabilities_declaration)
-                               die("protocol error: multiple capabilities^{}");
-                       got_dummy_ref_with_capabilities_declaration = 1;
-                       continue;
+       while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) {
+               switch (state) {
+               case EXPECTING_PROTOCOL_VERSION:
+                       if (process_protocol_version()) {
+                               state = EXPECTING_FIRST_REF;
+                               break;
+                       }
+                       state = EXPECTING_FIRST_REF;
+                       /* fallthrough */
+               case EXPECTING_FIRST_REF:
+                       process_capabilities(&len);
+                       if (process_dummy_ref()) {
+                               state = EXPECTING_SHALLOW;
+                               break;
+                       }
+                       state = EXPECTING_REF;
+                       /* fallthrough */
+               case EXPECTING_REF:
+                       if (process_ref(len, &list, flags, extra_have))
+                               break;
+                       state = EXPECTING_SHALLOW;
+                       /* fallthrough */
+               case EXPECTING_SHALLOW:
+                       if (process_shallow(len, shallow_points))
+                               break;
+                       die("protocol error: unexpected '%s'", packet_buffer);
+               default:
+                       die("unexpected state %d", state);
                }
-
-               if (!check_ref(name, flags))
-                       continue;
-
-               if (got_dummy_ref_with_capabilities_declaration)
-                       die("protocol error: unexpected ref after capabilities^{}");
-
-               ref = alloc_ref(buffer + GIT_SHA1_HEXSZ + 1);
-               oidcpy(&ref->old_oid, &old_oid);
-               *list = ref;
-               list = &ref->next;
        }
 
        annotate_refs_with_symref_info(*orig_list);
@@ -503,12 +582,25 @@ static int git_tcp_connect_sock(char *host, int flags)
 #endif /* NO_IPV6 */
 
 
-static void git_tcp_connect(int fd[2], char *host, int flags)
+/*
+ * Dummy child_process returned by git_connect() if the transport protocol
+ * does not need fork(2).
+ */
+static struct child_process no_fork = CHILD_PROCESS_INIT;
+
+int git_connection_is_socket(struct child_process *conn)
+{
+       return conn == &no_fork;
+}
+
+static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)
 {
        int sockfd = git_tcp_connect_sock(host, flags);
 
        fd[0] = sockfd;
        fd[1] = dup(sockfd);
+
+       return &no_fork;
 }
 
 
@@ -682,8 +774,6 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
        return protocol;
 }
 
-static struct child_process no_fork = CHILD_PROCESS_INIT;
-
 static const char *get_ssh_command(void)
 {
        const char *ssh;
@@ -697,37 +787,47 @@ static const char *get_ssh_command(void)
        return NULL;
 }
 
-static int override_ssh_variant(int *port_option, int *needs_batch)
+enum ssh_variant {
+       VARIANT_AUTO,
+       VARIANT_SIMPLE,
+       VARIANT_SSH,
+       VARIANT_PLINK,
+       VARIANT_PUTTY,
+       VARIANT_TORTOISEPLINK,
+};
+
+static void override_ssh_variant(enum ssh_variant *ssh_variant)
 {
-       char *variant;
+       const char *variant = getenv("GIT_SSH_VARIANT");
 
-       variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT"));
-       if (!variant &&
-           git_config_get_string("ssh.variant", &variant))
-               return 0;
+       if (!variant && git_config_get_string_const("ssh.variant", &variant))
+               return;
 
-       if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) {
-               *port_option = 'P';
-               *needs_batch = 0;
-       } else if (!strcmp(variant, "tortoiseplink")) {
-               *port_option = 'P';
-               *needs_batch = 1;
-       } else {
-               *port_option = 'p';
-               *needs_batch = 0;
-       }
-       free(variant);
-       return 1;
+       if (!strcmp(variant, "auto"))
+               *ssh_variant = VARIANT_AUTO;
+       else if (!strcmp(variant, "plink"))
+               *ssh_variant = VARIANT_PLINK;
+       else if (!strcmp(variant, "putty"))
+               *ssh_variant = VARIANT_PUTTY;
+       else if (!strcmp(variant, "tortoiseplink"))
+               *ssh_variant = VARIANT_TORTOISEPLINK;
+       else if (!strcmp(variant, "simple"))
+               *ssh_variant = VARIANT_SIMPLE;
+       else
+               *ssh_variant = VARIANT_SSH;
 }
 
-static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
-                              int *port_option, int *needs_batch)
+static enum ssh_variant determine_ssh_variant(const char *ssh_command,
+                                             int is_cmdline)
 {
+       enum ssh_variant ssh_variant = VARIANT_AUTO;
        const char *variant;
        char *p = NULL;
 
-       if (override_ssh_variant(port_option, needs_batch))
-               return;
+       override_ssh_variant(&ssh_variant);
+
+       if (ssh_variant != VARIANT_AUTO)
+               return ssh_variant;
 
        if (!is_cmdline) {
                p = xstrdup(ssh_command);
@@ -746,27 +846,200 @@ static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
                        free(ssh_argv);
                } else {
                        free(p);
-                       return;
+                       return ssh_variant;
                }
        }
 
-       if (!strcasecmp(variant, "plink") ||
-           !strcasecmp(variant, "plink.exe"))
-               *port_option = 'P';
+       if (!strcasecmp(variant, "ssh") ||
+           !strcasecmp(variant, "ssh.exe"))
+               ssh_variant = VARIANT_SSH;
+       else if (!strcasecmp(variant, "plink") ||
+                !strcasecmp(variant, "plink.exe"))
+               ssh_variant = VARIANT_PLINK;
        else if (!strcasecmp(variant, "tortoiseplink") ||
-                !strcasecmp(variant, "tortoiseplink.exe")) {
-               *port_option = 'P';
-               *needs_batch = 1;
-       }
+                !strcasecmp(variant, "tortoiseplink.exe"))
+               ssh_variant = VARIANT_TORTOISEPLINK;
+
        free(p);
+       return ssh_variant;
+}
+
+/*
+ * Open a connection using Git's native protocol.
+ *
+ * The caller is responsible for freeing hostandport, but this function may
+ * modify it (for example, to truncate it to remove the port part).
+ */
+static struct child_process *git_connect_git(int fd[2], char *hostandport,
+                                            const char *path, const char *prog,
+                                            int flags)
+{
+       struct child_process *conn;
+       struct strbuf request = STRBUF_INIT;
+       /*
+        * Set up virtual host information based on where we will
+        * connect, unless the user has overridden us in
+        * the environment.
+        */
+       char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
+       if (target_host)
+               target_host = xstrdup(target_host);
+       else
+               target_host = xstrdup(hostandport);
+
+       transport_check_allowed("git");
+
+       /*
+        * These underlying connection commands die() if they
+        * cannot connect.
+        */
+       if (git_use_proxy(hostandport))
+               conn = git_proxy_connect(fd, hostandport);
+       else
+               conn = git_tcp_connect(fd, hostandport, flags);
+       /*
+        * Separate original protocol components prog and path
+        * from extended host header with a NUL byte.
+        *
+        * Note: Do not add any other headers here!  Doing so
+        * will cause older git-daemon servers to crash.
+        */
+       strbuf_addf(&request,
+                   "%s %s%chost=%s%c",
+                   prog, path, 0,
+                   target_host, 0);
+
+       /* If using a new version put that stuff here after a second null byte */
+       if (get_protocol_version_config() > 0) {
+               strbuf_addch(&request, '\0');
+               strbuf_addf(&request, "version=%d%c",
+                           get_protocol_version_config(), '\0');
+       }
+
+       packet_write(fd[1], request.buf, request.len);
+
+       free(target_host);
+       strbuf_release(&request);
+       return conn;
+}
+
+/*
+ * Append the appropriate environment variables to `env` and options to
+ * `args` for running ssh in Git's SSH-tunneled transport.
+ */
+static void push_ssh_options(struct argv_array *args, struct argv_array *env,
+                            enum ssh_variant variant, const char *port,
+                            int flags)
+{
+       if (variant == VARIANT_SSH &&
+           get_protocol_version_config() > 0) {
+               argv_array_push(args, "-o");
+               argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                get_protocol_version_config());
+       }
+
+       if (flags & CONNECT_IPV4) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -4");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-4");
+               }
+       } else if (flags & CONNECT_IPV6) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -6");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-6");
+               }
+       }
+
+       if (variant == VARIANT_TORTOISEPLINK)
+               argv_array_push(args, "-batch");
+
+       if (port) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support setting port");
+               case VARIANT_SSH:
+                       argv_array_push(args, "-p");
+                       break;
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-P");
+               }
+
+               argv_array_push(args, port);
+       }
+}
+
+/* Prepare a child_process for use by Git's SSH-tunneled transport. */
+static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
+                         const char *port, int flags)
+{
+       const char *ssh;
+       enum ssh_variant variant;
+
+       if (looks_like_command_line_option(ssh_host))
+               die("strange hostname '%s' blocked", ssh_host);
+
+       ssh = get_ssh_command();
+       if (ssh) {
+               variant = determine_ssh_variant(ssh, 1);
+       } else {
+               /*
+                * GIT_SSH is the no-shell version of
+                * GIT_SSH_COMMAND (and must remain so for
+                * historical compatibility).
+                */
+               conn->use_shell = 0;
+
+               ssh = getenv("GIT_SSH");
+               if (!ssh)
+                       ssh = "ssh";
+               variant = determine_ssh_variant(ssh, 0);
+       }
+
+       if (variant == VARIANT_AUTO) {
+               struct child_process detect = CHILD_PROCESS_INIT;
+
+               detect.use_shell = conn->use_shell;
+               detect.no_stdin = detect.no_stdout = detect.no_stderr = 1;
+
+               argv_array_push(&detect.args, ssh);
+               argv_array_push(&detect.args, "-G");
+               push_ssh_options(&detect.args, &detect.env_array,
+                                VARIANT_SSH, port, flags);
+               argv_array_push(&detect.args, ssh_host);
+
+               variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
+       }
+
+       argv_array_push(&conn->args, ssh);
+       push_ssh_options(&conn->args, &conn->env_array, variant, port, flags);
+       argv_array_push(&conn->args, ssh_host);
 }
 
 /*
- * This returns a dummy child_process if the transport protocol does not
- * need fork(2), or a struct child_process object if it does.  Once done,
- * finish the connection with finish_connect() with the value returned from
- * this function (it is safe to call finish_connect() with NULL to support
- * the former case).
+ * This returns the dummy child_process `no_fork` if the transport protocol
+ * does not need fork(2), or a struct child_process object if it does.  Once
+ * done, finish the connection with finish_connect() with the value returned
+ * from this function (it is safe to call finish_connect() with NULL to
+ * support the former case).
  *
  * If it returns, the connect is successful; it just dies on errors (this
  * will hopefully be changed in a libification effort, to return NULL when
@@ -776,7 +1049,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                                  const char *prog, int flags)
 {
        char *hostandport, *path;
-       struct child_process *conn = &no_fork;
+       struct child_process *conn;
        enum protocol protocol;
 
        /* Without this we cannot rely on waitpid() to tell
@@ -792,40 +1065,10 @@ struct child_process *git_connect(int fd[2], const char *url,
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
-               /*
-                * Set up virtual host information based on where we will
-                * connect, unless the user has overridden us in
-                * the environment.
-                */
-               char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
-               if (target_host)
-                       target_host = xstrdup(target_host);
-               else
-                       target_host = xstrdup(hostandport);
-
-               transport_check_allowed("git");
-
-               /* These underlying connection commands die() if they
-                * cannot connect.
-                */
-               if (git_use_proxy(hostandport))
-                       conn = git_proxy_connect(fd, hostandport);
-               else
-                       git_tcp_connect(fd, hostandport, flags);
-               /*
-                * Separate original protocol components prog and path
-                * from extended host header with a NUL byte.
-                *
-                * Note: Do not add any other headers here!  Doing so
-                * will cause older git-daemon servers to crash.
-                */
-               packet_write_fmt(fd[1],
-                            "%s %s%chost=%s%c",
-                            prog, path, 0,
-                            target_host, 0);
-               free(target_host);
+               conn = git_connect_git(fd, hostandport, path, prog, flags);
        } else {
                struct strbuf cmd = STRBUF_INIT;
+               const char *const *var;
 
                conn = xmalloc(sizeof(*conn));
                child_process_init(conn);
@@ -838,13 +1081,12 @@ struct child_process *git_connect(int fd[2], const char *url,
                sq_quote_buf(&cmd, path);
 
                /* remove repo-local variables from the environment */
-               conn->env = local_repo_env;
+               for (var = local_repo_env; *var; var++)
+                       argv_array_push(&conn->env_array, *var);
+
                conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
-                       const char *ssh;
-                       int needs_batch = 0;
-                       int port_option = 'p';
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -866,46 +1108,13 @@ struct child_process *git_connect(int fd[2], const char *url,
                                strbuf_release(&cmd);
                                return NULL;
                        }
-
-                       if (looks_like_command_line_option(ssh_host))
-                               die("strange hostname '%s' blocked", ssh_host);
-
-                       ssh = get_ssh_command();
-                       if (ssh)
-                               handle_ssh_variant(ssh, 1, &port_option,
-                                                  &needs_batch);
-                       else {
-                               /*
-                                * GIT_SSH is the no-shell version of
-                                * GIT_SSH_COMMAND (and must remain so for
-                                * historical compatibility).
-                                */
-                               conn->use_shell = 0;
-
-                               ssh = getenv("GIT_SSH");
-                               if (!ssh)
-                                       ssh = "ssh";
-                               else
-                                       handle_ssh_variant(ssh, 0,
-                                                          &port_option,
-                                                          &needs_batch);
-                       }
-
-                       argv_array_push(&conn->args, ssh);
-                       if (flags & CONNECT_IPV4)
-                               argv_array_push(&conn->args, "-4");
-                       else if (flags & CONNECT_IPV6)
-                               argv_array_push(&conn->args, "-6");
-                       if (needs_batch)
-                               argv_array_push(&conn->args, "-batch");
-                       if (port) {
-                               argv_array_pushf(&conn->args,
-                                                "-%c", port_option);
-                               argv_array_push(&conn->args, port);
-                       }
-                       argv_array_push(&conn->args, ssh_host);
+                       fill_ssh_args(conn, ssh_host, port, flags);
                } else {
                        transport_check_allowed("file");
+                       if (get_protocol_version_config() > 0) {
+                               argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                                get_protocol_version_config());
+                       }
                }
                argv_array_push(&conn->args, cmd.buf);
 
@@ -921,11 +1130,6 @@ struct child_process *git_connect(int fd[2], const char *url,
        return conn;
 }
 
-int git_connection_is_socket(struct child_process *conn)
-{
-       return conn == &no_fork;
-}
-
 int finish_connect(struct child_process *conn)
 {
        int code;
index f416b05051f3b794c9be74e9baaf3b84e18ff6a2..4a47f332706a332174610ae275b3bcb5afd30f2f 100644 (file)
  *
  * Returns 0 if everything is connected, non-zero otherwise.
  */
-int check_connected(sha1_iterate_fn fn, void *cb_data,
+int check_connected(oid_iterate_fn fn, void *cb_data,
                    struct check_connected_options *opt)
 {
        struct child_process rev_list = CHILD_PROCESS_INIT;
        struct check_connected_options defaults = CHECK_CONNECTED_INIT;
-       char commit[41];
-       unsigned char sha1[20];
+       char commit[GIT_MAX_HEXSZ + 1];
+       struct object_id oid;
        int err = 0;
        struct packed_git *new_pack = NULL;
        struct transport *transport;
@@ -32,7 +32,7 @@ int check_connected(sha1_iterate_fn fn, void *cb_data,
                opt = &defaults;
        transport = opt->transport;
 
-       if (fn(cb_data, sha1)) {
+       if (fn(cb_data, &oid)) {
                if (opt->err_fd)
                        close(opt->err_fd);
                return err;
@@ -77,7 +77,7 @@ int check_connected(sha1_iterate_fn fn, void *cb_data,
 
        sigchain_push(SIGPIPE, SIG_IGN);
 
-       commit[40] = '\n';
+       commit[GIT_SHA1_HEXSZ] = '\n';
        do {
                /*
                 * If index-pack already checked that:
@@ -87,17 +87,17 @@ int check_connected(sha1_iterate_fn fn, void *cb_data,
                 * are sure the ref is good and not sending it to
                 * rev-list for verification.
                 */
-               if (new_pack && find_pack_entry_one(sha1, new_pack))
+               if (new_pack && find_pack_entry_one(oid.hash, new_pack))
                        continue;
 
-               memcpy(commit, sha1_to_hex(sha1), 40);
-               if (write_in_full(rev_list.in, commit, 41) < 0) {
+               memcpy(commit, oid_to_hex(&oid), GIT_SHA1_HEXSZ);
+               if (write_in_full(rev_list.in, commit, GIT_SHA1_HEXSZ + 1) < 0) {
                        if (errno != EPIPE && errno != EINVAL)
                                error_errno(_("failed write to rev-list"));
                        err = -1;
                        break;
                }
-       } while (!fn(cb_data, sha1));
+       } while (!fn(cb_data, &oid));
 
        if (close(rev_list.in))
                err = error_errno(_("failed to close rev-list's stdin"));
index 4ca325f79dc5ee45823834f71f66a6d3b278c3e5..a53f03a61aca4871be5ab75db9bf63ee895668e1 100644 (file)
@@ -8,7 +8,7 @@ struct transport;
  * When called after returning the name for the last object, return -1
  * to signal EOF, otherwise return 0.
  */
-typedef int (*sha1_iterate_fn)(void *, unsigned char [20]);
+typedef int (*oid_iterate_fn)(void *, struct object_id *oid);
 
 /*
  * Named-arguments struct for check_connected. All arguments are
@@ -51,7 +51,7 @@ struct check_connected_options {
  *
  * If "opt" is NULL, behaves as if CHECK_CONNECTED_INIT was passed.
  */
-int check_connected(sha1_iterate_fn fn, void *cb_data,
+int check_connected(oid_iterate_fn fn, void *cb_data,
                    struct check_connected_options *opt);
 
 #endif /* CONNECTED_H */
index 17929b0809803690d88044bbfe71496bfc2da87e..3683c772c5586ccc1f6dfc8c047919d8d96aa891 100644 (file)
@@ -1400,7 +1400,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
                        --patch-with-stat --name-only --name-status --color
                        --no-color --color-words --no-renames --check
                        --full-index --binary --abbrev --diff-filter=
-                       --find-copies-harder
+                       --find-copies-harder --ignore-cr-at-eol
                        --text --ignore-space-at-eol --ignore-space-change
                        --ignore-all-space --ignore-blank-lines --exit-code
                        --quiet --ext-diff --no-ext-diff
@@ -1922,6 +1922,7 @@ _git_pull ()
        --*)
                __gitcomp "
                        --rebase --no-rebase
+                       --autostash --no-autostash
                        $__git_merge_options
                        $__git_fetch_options
                "
@@ -2350,6 +2351,7 @@ _git_config ()
                advice.rmHints
                advice.statusHints
                advice.statusUoption
+               advice.ignoredHook
                alias.
                am.keepcr
                am.threeWay
@@ -2640,6 +2642,7 @@ _git_config ()
                sendemail.suppressfrom
                sendemail.thread
                sendemail.to
+               sendemail.tocmd
                sendemail.validate
                sendemail.smtpbatchsize
                sendemail.smtprelogindelay
index c6cbef38c2a580555dfcdc67e04f6ac507f9066c..983e419d2b7eda8f191a878a395179dcc24eb949 100644 (file)
@@ -278,11 +278,12 @@ __git_ps1_colorize_gitstring ()
        r="$c_clear$r"
 }
 
+# Helper function to read the first line of a file into a variable.
+# __git_eread requires 2 arguments, the file path and the name of the
+# variable, in that order.
 __git_eread ()
 {
-       local f="$1"
-       shift
-       test -r "$f" && read "$@" <"$f"
+       test -r "$1" && IFS=$'\r\n' read "$2" <"$1"
 }
 
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
index 225e3f09547d07563a8da34b21677dce514659fa..4484bda41065af6f6e23c667a7bab59a8a1a61cc 100644 (file)
@@ -63,6 +63,9 @@ git jump grep foo_bar
 # same as above, but case-insensitive; you can give
 # arbitrary grep options
 git jump grep -i foo_bar
+
+# use the silver searcher for git jump grep
+git config jump.grepCmd "ag --column"
 --------------------------------------------------
 
 
@@ -92,3 +95,10 @@ how to activate it.
 
 The shell snippets to generate the quickfix lines will almost certainly
 choke on filenames with exotic characters (like newlines).
+
+Contributing
+------------
+
+Bug fixes, bug reports, and feature requests should be discussed on the
+Git mailing list <git@vger.kernel.org>, and cc'd to the git-jump
+maintainer, Jeff King <peff@peff.net>.
index 427f206a45b326f2ee80af0fae7080d5f794c14a..80ab0590bc92221e2b2f8f35b76f5a0cbb970f8b 100755 (executable)
@@ -11,7 +11,8 @@ diff: elements are diff hunks. Arguments are given to diff.
 
 merge: elements are merge conflicts. Arguments are ignored.
 
-grep: elements are grep hits. Arguments are given to grep.
+grep: elements are grep hits. Arguments are given to git grep or, if
+      configured, to the command in `jump.grepCmd`.
 
 ws: elements are whitespace errors. Arguments are given to diff --check.
 EOF
@@ -50,7 +51,9 @@ mode_merge() {
 # but let's clean up extra whitespace, so they look better if the
 # editor shows them to us in the status bar.
 mode_grep() {
-       git grep -n "$@" |
+       cmd=$(git config jump.grepCmd)
+       test -n "$cmd" || cmd="git grep -n"
+       $cmd "$@" |
        perl -pe '
        s/[ \t]+/ /g;
        s/^ *//;
index d13c4dfa7d746a827879bb0445aff33de4541e6a..917d9e2d3222c12f2d0dc043a1434acae2b398ae 100644 (file)
@@ -2,6 +2,7 @@ package Git::Mediawiki;
 
 use 5.008;
 use strict;
+use POSIX;
 use Git;
 
 BEGIN {
@@ -52,7 +53,7 @@ sub smudge_filename {
        $filename =~ s/ /_/g;
        # Decode forbidden characters encoded in clean_filename
        $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf('%c', hex($1))/ge;
-       return $filename;
+       return substr($filename, 0, NAME_MAX-length('.mw'));
 }
 
 sub connect_maybe {
index e7f857c1a2882c7c701af3e2ce6c3223bfd88c7c..af9cbc9d0f7bc296ec728c3b965c26a057c8cf72 100755 (executable)
 my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.${remotename}.categories"));
 chomp(@tracked_categories);
 
+# Just like @tracked_categories, but for MediaWiki namespaces.
+my @tracked_namespaces = split(/[ \n]/, run_git("config --get-all remote.${remotename}.namespaces"));
+for (@tracked_namespaces) { s/_/ /g; }
+chomp(@tracked_namespaces);
+
 # Import media files on pull
 my $import_media = run_git("config --get --bool remote.${remotename}.mediaimport");
 chomp($import_media);
@@ -256,6 +261,32 @@ sub get_mw_tracked_categories {
        return;
 }
 
+sub get_mw_tracked_namespaces {
+    my $pages = shift;
+    foreach my $local_namespace (sort @tracked_namespaces) {
+        my $namespace_id;
+        if ($local_namespace eq "(Main)") {
+            $namespace_id = 0;
+        } else {
+            $namespace_id = get_mw_namespace_id($local_namespace);
+        }
+        # virtual namespaces don't support allpages
+        next if !defined($namespace_id) || $namespace_id < 0;
+        my $mw_pages = $mediawiki->list( {
+            action => 'query',
+            list => 'allpages',
+            apnamespace => $namespace_id,
+            aplimit => 'max' } )
+            || die $mediawiki->{error}->{code} . ': '
+                . $mediawiki->{error}->{details} . "\n";
+        print {*STDERR} "$#{$mw_pages} found in namespace $local_namespace ($namespace_id)\n";
+        foreach my $page (@{$mw_pages}) {
+            $pages->{$page->{title}} = $page;
+        }
+    }
+    return;
+}
+
 sub get_mw_all_pages {
        my $pages = shift;
        # No user-provided list, get the list of pages from the API.
@@ -319,6 +350,10 @@ sub get_mw_pages {
                $user_defined = 1;
                get_mw_tracked_categories(\%pages);
        }
+       if (@tracked_namespaces) {
+               $user_defined = 1;
+               get_mw_tracked_namespaces(\%pages);
+       }
        if (!$user_defined) {
                get_mw_all_pages(\%pages);
        }
@@ -1308,7 +1343,8 @@ sub get_mw_namespace_id {
        my $id;
 
        if (!defined $ns) {
-               print {*STDERR} "No such namespace ${name} on MediaWiki.\n";
+               my @namespaces = map { s/ /_/g; $_; } sort keys %namespace_id;
+               print {*STDERR} "No such namespace ${name} on MediaWiki, known namespaces: @namespaces\n";
                $ns = {is_namespace => 0};
                $namespace_id{$name} = $ns;
        }
index 20d7ab67bdf889feb255e01a9b18cf4efa6107a3..1a41a48e15efd7a6c3030e7a0d1097cbc08099c1 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -220,18 +220,27 @@ static void check_safe_crlf(const char *path, enum crlf_action crlf_action,
        }
 }
 
-static int has_cr_in_index(const struct index_state *istate, const char *path)
+static int has_crlf_in_index(const struct index_state *istate, const char *path)
 {
        unsigned long sz;
        void *data;
-       int has_cr;
+       const char *crp;
+       int has_crlf = 0;
 
        data = read_blob_data_from_index(istate, path, &sz);
        if (!data)
                return 0;
-       has_cr = memchr(data, '\r', sz) != NULL;
+
+       crp = memchr(data, '\r', sz);
+       if (crp) {
+               unsigned int ret_stats;
+               ret_stats = gather_convert_stats(data, sz);
+               if (!(ret_stats & CONVERT_STAT_BITS_BIN) &&
+                   (ret_stats & CONVERT_STAT_BITS_TXT_CRLF))
+                       has_crlf = 1;
+       }
        free(data);
-       return has_cr;
+       return has_crlf;
 }
 
 static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats,
@@ -290,7 +299,7 @@ static int crlf_to_git(const struct index_state *istate,
                 * cherry-pick.
                 */
                if ((checksafe != SAFE_CRLF_RENORMALIZE) &&
-                   has_cr_in_index(istate, path))
+                   has_crlf_in_index(istate, path))
                        convert_crlf_into_lf = 0;
        }
        if ((checksafe == SAFE_CRLF_WARN ||
index 30747075f0c45942220f63202f8c984e3e0956a7..e37e343d0aec37fd74e7bdf694b99f59d022690a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -282,7 +282,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
        return NULL;            /* Fallthrough. Deny by default */
 }
 
-typedef int (*daemon_service_fn)(void);
+typedef int (*daemon_service_fn)(const struct argv_array *env);
 struct daemon_service {
        const char *name;
        const char *config_name;
@@ -363,7 +363,7 @@ static int run_access_hook(struct daemon_service *service, const char *dir,
 }
 
 static int run_service(const char *dir, struct daemon_service *service,
-                      struct hostinfo *hi)
+                      struct hostinfo *hi, const struct argv_array *env)
 {
        const char *path;
        int enabled = service->enabled;
@@ -422,7 +422,7 @@ static int run_service(const char *dir, struct daemon_service *service,
         */
        signal(SIGTERM, SIG_IGN);
 
-       return service->fn();
+       return service->fn(env);
 }
 
 static void copy_to_log(int fd)
@@ -462,25 +462,34 @@ static int run_service_command(struct child_process *cld)
        return finish_command(cld);
 }
 
-static int upload_pack(void)
+static int upload_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL);
        argv_array_pushf(&cld.args, "--timeout=%u", timeout);
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int upload_archive(void)
+static int upload_archive(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "upload-archive");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int receive_pack(void)
+static int receive_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "receive-pack");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
@@ -573,8 +582,11 @@ static void canonicalize_client(struct strbuf *out, const char *in)
 
 /*
  * Read the host as supplied by the client connection.
+ *
+ * Returns a pointer to the character after the NUL byte terminating the host
+ * arguemnt, or 'extra_args' if there is no host arguemnt.
  */
-static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
+static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
 {
        char *val;
        int vallen;
@@ -602,6 +614,43 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
                if (extra_args < end && *extra_args)
                        die("Invalid request");
        }
+
+       return extra_args;
+}
+
+static void parse_extra_args(struct hostinfo *hi, struct argv_array *env,
+                            char *extra_args, int buflen)
+{
+       const char *end = extra_args + buflen;
+       struct strbuf git_protocol = STRBUF_INIT;
+
+       /* First look for the host argument */
+       extra_args = parse_host_arg(hi, extra_args, buflen);
+
+       /* Look for additional arguments places after a second NUL byte */
+       for (; extra_args < end; extra_args += strlen(extra_args) + 1) {
+               const char *arg = extra_args;
+
+               /*
+                * Parse the extra arguments, adding most to 'git_protocol'
+                * which will be used to set the 'GIT_PROTOCOL' envvar in the
+                * service that will be run.
+                *
+                * If there ends up being a particular arg in the future that
+                * git-daemon needs to parse specificly (like the 'host' arg)
+                * then it can be parsed here and not added to 'git_protocol'.
+                */
+               if (*arg) {
+                       if (git_protocol.len > 0)
+                               strbuf_addch(&git_protocol, ':');
+                       strbuf_addstr(&git_protocol, arg);
+               }
+       }
+
+       if (git_protocol.len > 0)
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
+                                git_protocol.buf);
+       strbuf_release(&git_protocol);
 }
 
 /*
@@ -695,6 +744,7 @@ static int execute(void)
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
        struct hostinfo hi;
+       struct argv_array env = ARGV_ARRAY_INIT;
 
        hostinfo_init(&hi);
 
@@ -716,8 +766,9 @@ static int execute(void)
                pktlen--;
        }
 
+       /* parse additional args hidden behind a NUL byte */
        if (len != pktlen)
-               parse_host_arg(&hi, line + len + 1, pktlen - len - 1);
+               parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1);
 
        for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
                struct daemon_service *s = &(daemon_service[i]);
@@ -730,13 +781,15 @@ static int execute(void)
                         * Note: The directory here is probably context sensitive,
                         * and might depend on the actual service being performed.
                         */
-                       int rc = run_service(arg, s, &hi);
+                       int rc = run_service(arg, s, &hi, &env);
                        hostinfo_clear(&hi);
+                       argv_array_clear(&env);
                        return rc;
                }
        }
 
        hostinfo_clear(&hi);
+       argv_array_clear(&env);
        logerror("Protocol error: '%s'", line);
        return -1;
 }
index 270eb2519764f1378fe53352021cd7afb3ea306a..de31331fa425429cf3c05078761b9bcf52fb0a45 100644 (file)
@@ -14,20 +14,20 @@ static unsigned int hash_obj(const struct object *obj, unsigned int n)
 static void *insert_decoration(struct decoration *n, const struct object *base, void *decoration)
 {
        int size = n->size;
-       struct object_decoration *hash = n->hash;
+       struct decoration_entry *entries = n->entries;
        unsigned int j = hash_obj(base, size);
 
-       while (hash[j].base) {
-               if (hash[j].base == base) {
-                       void *old = hash[j].decoration;
-                       hash[j].decoration = decoration;
+       while (entries[j].base) {
+               if (entries[j].base == base) {
+                       void *old = entries[j].decoration;
+                       entries[j].decoration = decoration;
                        return old;
                }
                if (++j >= size)
                        j = 0;
        }
-       hash[j].base = base;
-       hash[j].decoration = decoration;
+       entries[j].base = base;
+       entries[j].decoration = decoration;
        n->nr++;
        return NULL;
 }
@@ -36,24 +36,23 @@ static void grow_decoration(struct decoration *n)
 {
        int i;
        int old_size = n->size;
-       struct object_decoration *old_hash = n->hash;
+       struct decoration_entry *old_entries = n->entries;
 
        n->size = (old_size + 1000) * 3 / 2;
-       n->hash = xcalloc(n->size, sizeof(struct object_decoration));
+       n->entries = xcalloc(n->size, sizeof(struct decoration_entry));
        n->nr = 0;
 
        for (i = 0; i < old_size; i++) {
-               const struct object *base = old_hash[i].base;
-               void *decoration = old_hash[i].decoration;
+               const struct object *base = old_entries[i].base;
+               void *decoration = old_entries[i].decoration;
 
                if (!decoration)
                        continue;
                insert_decoration(n, base, decoration);
        }
-       free(old_hash);
+       free(old_entries);
 }
 
-/* Add a decoration pointer, return any old one */
 void *add_decoration(struct decoration *n, const struct object *obj,
                void *decoration)
 {
@@ -64,7 +63,6 @@ void *add_decoration(struct decoration *n, const struct object *obj,
        return insert_decoration(n, obj, decoration);
 }
 
-/* Lookup a decoration pointer */
 void *lookup_decoration(struct decoration *n, const struct object *obj)
 {
        unsigned int j;
@@ -74,7 +72,7 @@ void *lookup_decoration(struct decoration *n, const struct object *obj)
                return NULL;
        j = hash_obj(obj, n->size);
        for (;;) {
-               struct object_decoration *ref = n->hash + j;
+               struct decoration_entry *ref = n->entries + j;
                if (ref->base == obj)
                        return ref->decoration;
                if (!ref->base)
index e7328044ff84a4acaaa7f5f4bc5f85375dc7a07a..9014c1e996c60d9b3e1bd27ffa01b9c2d0c8ae80 100644 (file)
@@ -1,18 +1,61 @@
 #ifndef DECORATE_H
 #define DECORATE_H
 
-struct object_decoration {
+/*
+ * A data structure that associates Git objects to void pointers. See
+ * t/helper/test-example-decorate.c for a demonstration of how to use these
+ * functions.
+ */
+
+/*
+ * An entry in the data structure.
+ */
+struct decoration_entry {
        const struct object *base;
        void *decoration;
 };
 
+/*
+ * The data structure.
+ *
+ * This data structure must be zero-initialized.
+ */
 struct decoration {
+       /*
+        * Not used by the decoration mechanism. Clients may use this for
+        * whatever they want.
+        */
        const char *name;
-       unsigned int size, nr;
-       struct object_decoration *hash;
+
+       /*
+        * The capacity of "entries".
+        */
+       unsigned int size;
+
+       /*
+        * The number of real Git objects (that is, entries with non-NULL
+        * "base").
+        */
+       unsigned int nr;
+
+       /*
+        * The entries. This is an array of size "size", containing nr entries
+        * with non-NULL "base" and (size - nr) entries with NULL "base".
+        */
+       struct decoration_entry *entries;
 };
 
+/*
+ * Add an association from the given object to the given pointer (which may be
+ * NULL), returning the previously associated pointer. If there is no previous
+ * association, this function returns NULL.
+ */
 extern void *add_decoration(struct decoration *n, const struct object *obj, void *decoration);
+
+/*
+ * Return the pointer associated to the given object. If there is no
+ * association, this function returns NULL.
+ */
 extern void *lookup_decoration(struct decoration *n, const struct object *obj);
 
 #endif
index 4e0980caa80fb980938f68c8d350ec00cc24398b..8104603a3b36f2fd73761db34efde44d5938f68f 100644 (file)
@@ -12,6 +12,7 @@
 #include "refs.h"
 #include "submodule.h"
 #include "dir.h"
+#include "fsmonitor.h"
 
 /*
  * diff-files
@@ -36,7 +37,7 @@ static int check_removed(const struct cache_entry *ce, struct stat *st)
        if (has_symlink_leading_path(ce->name, ce_namelen(ce)))
                return 1;
        if (S_ISDIR(st->st_mode)) {
-               unsigned char sub[20];
+               struct object_id sub;
 
                /*
                 * If ce is already a gitlink, we can have a plain
@@ -50,7 +51,7 @@ static int check_removed(const struct cache_entry *ce, struct stat *st)
                 * a directory --- the blob was removed!
                 */
                if (!S_ISGITLINK(ce->ce_mode) &&
-                   resolve_gitlink_ref(ce->name, "HEAD", sub))
+                   resolve_gitlink_ref(ce->name, "HEAD", &sub))
                        return 1;
        }
        return 0;
@@ -71,14 +72,15 @@ static int match_stat_with_submodule(struct diff_options *diffopt,
 {
        int changed = ce_match_stat(ce, st, ce_option);
        if (S_ISGITLINK(ce->ce_mode)) {
-               unsigned orig_flags = diffopt->flags;
-               if (!DIFF_OPT_TST(diffopt, OVERRIDE_SUBMODULE_CONFIG))
+               struct diff_flags orig_flags = diffopt->flags;
+               if (!diffopt->flags.override_submodule_config)
                        set_diffopt_flags_from_submodule_config(diffopt, ce->name);
-               if (DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES))
+               if (diffopt->flags.ignore_submodules)
                        changed = 0;
-               else if (!DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES)
-                   && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES)))
-                       *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES));
+               else if (!diffopt->flags.ignore_dirty_submodules &&
+                        (!changed || diffopt->flags.dirty_submodules))
+                       *dirty_submodule = is_submodule_modified(ce->name,
+                                                                diffopt->flags.ignore_untracked_in_submodules);
                diffopt->flags = orig_flags;
        }
        return changed;
@@ -216,7 +218,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        } else if (revs->diffopt.ita_invisible_in_index &&
                                   ce_intent_to_add(ce)) {
                                diff_addremove(&revs->diffopt, '+', ce->ce_mode,
-                                              &empty_tree_oid, 0,
+                                              the_hash_algo->empty_tree, 0,
                                               ce->name, 0);
                                continue;
                        }
@@ -228,7 +230,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 
                if (!changed && !dirty_submodule) {
                        ce_mark_uptodate(ce);
-                       if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
+                       mark_fsmonitor_valid(ce);
+                       if (!revs->diffopt.flags.find_copies_harder)
                                continue;
                }
                oldmode = ce->ce_mode;
@@ -362,7 +365,7 @@ static int show_modified(struct rev_info *revs,
 
        oldmode = old->ce_mode;
        if (mode == oldmode && !oidcmp(oid, &old->oid) && !dirty_submodule &&
-           !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
+           !revs->diffopt.flags.find_copies_harder)
                return 0;
 
        diff_change(&revs->diffopt, oldmode, mode,
@@ -493,7 +496,7 @@ static int diff_cache(struct rev_info *revs,
        opts.head_idx = 1;
        opts.index_only = cached;
        opts.diff_index_cached = (cached &&
-                                 !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER));
+                                 !revs->diffopt.flags.find_copies_harder);
        opts.merge = 1;
        opts.fn = oneway_diff;
        opts.unpack_data = revs;
@@ -534,7 +537,7 @@ int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt)
        return 0;
 }
 
-int index_differs_from(const char *def, int diff_flags,
+int index_differs_from(const char *def, const struct diff_flags *flags,
                       int ita_invisible_in_index)
 {
        struct rev_info rev;
@@ -544,11 +547,12 @@ int index_differs_from(const char *def, int diff_flags,
        memset(&opt, 0, sizeof(opt));
        opt.def = def;
        setup_revisions(0, NULL, &rev, &opt);
-       DIFF_OPT_SET(&rev.diffopt, QUICK);
-       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
-       rev.diffopt.flags |= diff_flags;
+       rev.diffopt.flags.quick = 1;
+       rev.diffopt.flags.exit_with_status = 1;
+       if (flags)
+               diff_flags_or(&rev.diffopt.flags, flags);
        rev.diffopt.ita_invisible_in_index = ita_invisible_in_index;
        run_diff_index(&rev, 1);
        object_array_clear(&rev.pending);
-       return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0);
+       return (rev.diffopt.flags.has_changes != 0);
 }
index 80ff17d460ed67fa4b38f8a8dfce7504b27bbb64..0ed5f0f496882cef9e04f9a93fe718a9f55a268a 100644 (file)
@@ -184,7 +184,7 @@ static int queue_diff(struct diff_options *o,
        } else {
                struct diff_filespec *d1, *d2;
 
-               if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+               if (o->flags.reverse_diff) {
                        SWAP(mode1, mode2);
                        SWAP(name1, name2);
                }
@@ -276,16 +276,16 @@ void diff_no_index(struct rev_info *revs,
        if (!revs->diffopt.output_format)
                revs->diffopt.output_format = DIFF_FORMAT_PATCH;
 
-       DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
+       revs->diffopt.flags.no_index = 1;
 
-       DIFF_OPT_SET(&revs->diffopt, RELATIVE_NAME);
+       revs->diffopt.flags.relative_name = 1;
        revs->diffopt.prefix = prefix;
 
        revs->max_count = -2;
        diff_setup_done(&revs->diffopt);
 
        setup_diff_pager(&revs->diffopt);
-       DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
+       revs->diffopt.flags.exit_with_status = 1;
 
        if (queue_diff(&revs->diffopt, paths[0], paths[1]))
                exit(1);
diff --git a/diff.c b/diff.c
index 1898dd307d40db131ba2378e22ed3efed1dc2520..fb22b19f09ab13032b0484e64326fef92bb0681f 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -124,18 +124,18 @@ static int parse_dirstat_params(struct diff_options *options, const char *params
        for (i = 0; i < params.nr; i++) {
                const char *p = params.items[i].string;
                if (!strcmp(p, "changes")) {
-                       DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
-                       DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+                       options->flags.dirstat_by_line = 0;
+                       options->flags.dirstat_by_file = 0;
                } else if (!strcmp(p, "lines")) {
-                       DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
-                       DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+                       options->flags.dirstat_by_line = 1;
+                       options->flags.dirstat_by_file = 0;
                } else if (!strcmp(p, "files")) {
-                       DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
-                       DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+                       options->flags.dirstat_by_line = 0;
+                       options->flags.dirstat_by_file = 1;
                } else if (!strcmp(p, "noncumulative")) {
-                       DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
+                       options->flags.dirstat_cumulative = 0;
                } else if (!strcmp(p, "cumulative")) {
-                       DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
+                       options->flags.dirstat_cumulative = 1;
                } else if (isdigit(*p)) {
                        char *end;
                        int permille = strtoul(p, &end, 10) * 10;
@@ -1412,7 +1412,7 @@ static void emit_rewrite_diff(const char *name_a,
        struct emit_callback ecbdata;
        struct strbuf out = STRBUF_INIT;
 
-       if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
+       if (diff_mnemonic_prefix && o->flags.reverse_diff) {
                a_prefix = o->b_prefix;
                b_prefix = o->a_prefix;
        } else {
@@ -2660,7 +2660,7 @@ static void show_dirstat(struct diff_options *options)
        dir.alloc = 0;
        dir.nr = 0;
        dir.permille = options->dirstat_permille;
-       dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
+       dir.cumulative = options->flags.dirstat_cumulative;
 
        changed = 0;
        for (i = 0; i < q->nr; i++) {
@@ -2686,7 +2686,7 @@ static void show_dirstat(struct diff_options *options)
                        goto found_damage;
                }
 
-               if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) {
+               if (options->flags.dirstat_by_file) {
                        /*
                         * In --dirstat-by-file mode, we don't really need to
                         * look at the actual file contents at all.
@@ -2761,7 +2761,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
        dir.alloc = 0;
        dir.nr = 0;
        dir.permille = options->dirstat_permille;
-       dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
+       dir.cumulative = options->flags.dirstat_cumulative;
 
        changed = 0;
        for (i = 0; i < data->nr; i++) {
@@ -3048,7 +3048,7 @@ static void builtin_diff(const char *name_a,
        const char *line_prefix = diff_line_prefix(o);
 
        diff_set_mnemonic_prefix(o, "a/", "b/");
-       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+       if (o->flags.reverse_diff) {
                a_prefix = o->b_prefix;
                b_prefix = o->a_prefix;
        } else {
@@ -3072,7 +3072,7 @@ static void builtin_diff(const char *name_a,
                return;
        }
 
-       if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
+       if (o->flags.allow_textconv) {
                textconv_one = get_textconv(one);
                textconv_two = get_textconv(two);
        }
@@ -3132,13 +3132,13 @@ static void builtin_diff(const char *name_a,
                                 header.len, 0);
                strbuf_reset(&header);
                goto free_ab_and_return;
-       } else if (!DIFF_OPT_TST(o, TEXT) &&
+       } else if (!o->flags.text &&
            ( (!textconv_one && diff_filespec_is_binary(one)) ||
              (!textconv_two && diff_filespec_is_binary(two)) )) {
                struct strbuf sb = STRBUF_INIT;
                if (!one->data && !two->data &&
                    S_ISREG(one->mode) && S_ISREG(two->mode) &&
-                   !DIFF_OPT_TST(o, BINARY)) {
+                   !o->flags.binary) {
                        if (!oidcmp(&one->oid, &two->oid)) {
                                if (must_show_header)
                                        emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
@@ -3167,7 +3167,7 @@ static void builtin_diff(const char *name_a,
                }
                emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, header.len, 0);
                strbuf_reset(&header);
-               if (DIFF_OPT_TST(o, BINARY))
+               if (o->flags.binary)
                        emit_binary_diff(o, &mf1, &mf2);
                else {
                        strbuf_addf(&sb, "%sBinary files %s and %s differ\n",
@@ -3210,10 +3210,12 @@ static void builtin_diff(const char *name_a,
                ecbdata.opt = o;
                ecbdata.header = header.len ? &header : NULL;
                xpp.flags = o->xdl_opts;
+               xpp.anchors = o->anchors;
+               xpp.anchors_nr = o->anchors_nr;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
-               if (DIFF_OPT_TST(o, FUNCCONTEXT))
+               if (o->flags.funccontext)
                        xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
@@ -3302,6 +3304,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
+               xpp.anchors = o->anchors;
+               xpp.anchors_nr = o->anchors_nr;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
@@ -3378,7 +3382,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        if (data.status)
-               DIFF_OPT_SET(o, CHECK_FAILED);
+               o->flags.check_failed = 1;
 }
 
 struct diff_filespec *alloc_filespec(const char *path)
@@ -3870,9 +3874,9 @@ static void fill_metainfo(struct strbuf *msg,
                *must_show_header = 0;
        }
        if (one && two && oidcmp(&one->oid, &two->oid)) {
-               int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+               int abbrev = o->flags.full_index ? 40 : DEFAULT_ABBREV;
 
-               if (DIFF_OPT_TST(o, BINARY)) {
+               if (o->flags.binary) {
                        mmfile_t mf;
                        if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
@@ -3902,7 +3906,7 @@ static void run_diff_cmd(const char *pgm,
        int must_show_header = 0;
 
 
-       if (DIFF_OPT_TST(o, ALLOW_EXTERNAL)) {
+       if (o->flags.allow_external) {
                struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
                if (drv && drv->external)
                        pgm = drv->external;
@@ -3982,7 +3986,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
 
-       if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
+       if (!o->flags.allow_external)
                pgm = NULL;
 
        if (DIFF_PAIR_UNMERGED(p)) {
@@ -4081,7 +4085,7 @@ void diff_setup(struct diff_options *options)
        options->context = diff_context_default;
        options->interhunkcontext = diff_interhunk_context_default;
        options->ws_error_highlight = ws_error_highlight_default;
-       DIFF_OPT_SET(options, RENAME_EMPTY);
+       options->flags.rename_empty = 1;
 
        /* pathchange left =NULL by default */
        options->change = diff_change;
@@ -4129,17 +4133,15 @@ void diff_setup_done(struct diff_options *options)
         * inside contents.
         */
 
-       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
-           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
-           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
-               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       if ((options->xdl_opts & XDF_WHITESPACE_FLAGS))
+               options->flags.diff_from_contents = 1;
        else
-               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+               options->flags.diff_from_contents = 0;
 
-       if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
+       if (options->flags.find_copies_harder)
                options->detect_rename = DIFF_DETECT_COPY;
 
-       if (!DIFF_OPT_TST(options, RELATIVE_NAME))
+       if (!options->flags.relative_name)
                options->prefix = NULL;
        if (options->prefix)
                options->prefix_length = strlen(options->prefix);
@@ -4169,18 +4171,18 @@ void diff_setup_done(struct diff_options *options)
                                      DIFF_FORMAT_DIRSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
-               DIFF_OPT_SET(options, RECURSIVE);
+               options->flags.recursive = 1;
        /*
         * Also pickaxe would not work very well if you do not say recursive
         */
        if (options->pickaxe)
-               DIFF_OPT_SET(options, RECURSIVE);
+               options->flags.recursive = 1;
        /*
         * When patches are generated, submodules diffed against the work tree
         * must be checked for dirtiness too so it can be shown in the output
         */
        if (options->output_format & DIFF_FORMAT_PATCH)
-               DIFF_OPT_SET(options, DIRTY_SUBMODULES);
+               options->flags.dirty_submodules = 1;
 
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
@@ -4202,14 +4204,14 @@ void diff_setup_done(struct diff_options *options)
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUICK)) {
+       if (options->flags.quick) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
-               DIFF_OPT_SET(options, EXIT_WITH_STATUS);
+               options->flags.exit_with_status = 1;
        }
 
        options->diff_path_counter = 0;
 
-       if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1)
+       if (options->flags.follow_renames && options->pathspec.nr != 1)
                die(_("--follow requires exactly one pathspec"));
 
        if (!options->use_color || external_diff())
@@ -4510,17 +4512,12 @@ int diff_opt_parse(struct diff_options *options,
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        else if (!strcmp(arg, "--shortstat"))
                options->output_format |= DIFF_FORMAT_SHORTSTAT;
-       else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
-               return parse_dirstat_opt(options, "");
-       else if (skip_prefix(arg, "-X", &arg))
-               return parse_dirstat_opt(options, arg);
-       else if (skip_prefix(arg, "--dirstat=", &arg))
+       else if (skip_prefix(arg, "-X", &arg) ||
+                skip_to_optional_arg(arg, "--dirstat", &arg))
                return parse_dirstat_opt(options, arg);
        else if (!strcmp(arg, "--cumulative"))
                return parse_dirstat_opt(options, "cumulative");
-       else if (!strcmp(arg, "--dirstat-by-file"))
-               return parse_dirstat_opt(options, "files");
-       else if (skip_prefix(arg, "--dirstat-by-file=", &arg)) {
+       else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) {
                parse_dirstat_opt(options, "files");
                return parse_dirstat_opt(options, arg);
        }
@@ -4542,13 +4539,13 @@ int diff_opt_parse(struct diff_options *options,
                return stat_opt(options, av);
 
        /* renames options */
-       else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") ||
-                !strcmp(arg, "--break-rewrites")) {
+       else if (starts_with(arg, "-B") ||
+                skip_to_optional_arg(arg, "--break-rewrites", NULL)) {
                if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -B: %s", arg+2);
        }
-       else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") ||
-                !strcmp(arg, "--find-renames")) {
+       else if (starts_with(arg, "-M") ||
+                skip_to_optional_arg(arg, "--find-renames", NULL)) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -M: %s", arg+2);
                options->detect_rename = DIFF_DETECT_RENAME;
@@ -4556,10 +4553,10 @@ int diff_opt_parse(struct diff_options *options,
        else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
                options->irreversible_delete = 1;
        }
-       else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") ||
-                !strcmp(arg, "--find-copies")) {
+       else if (starts_with(arg, "-C") ||
+                skip_to_optional_arg(arg, "--find-copies", NULL)) {
                if (options->detect_rename == DIFF_DETECT_COPY)
-                       DIFF_OPT_SET(options, FIND_COPIES_HARDER);
+                       options->flags.find_copies_harder = 1;
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -C: %s", arg+2);
                options->detect_rename = DIFF_DETECT_COPY;
@@ -4567,14 +4564,13 @@ int diff_opt_parse(struct diff_options *options,
        else if (!strcmp(arg, "--no-renames"))
                options->detect_rename = 0;
        else if (!strcmp(arg, "--rename-empty"))
-               DIFF_OPT_SET(options, RENAME_EMPTY);
+               options->flags.rename_empty = 1;
        else if (!strcmp(arg, "--no-rename-empty"))
-               DIFF_OPT_CLR(options, RENAME_EMPTY);
-       else if (!strcmp(arg, "--relative"))
-               DIFF_OPT_SET(options, RELATIVE_NAME);
-       else if (skip_prefix(arg, "--relative=", &arg)) {
-               DIFF_OPT_SET(options, RELATIVE_NAME);
-               options->prefix = arg;
+               options->flags.rename_empty = 0;
+       else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) {
+               options->flags.relative_name = 1;
+               if (arg)
+                       options->prefix = arg;
        }
 
        /* xdiff options */
@@ -4588,15 +4584,26 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(arg, "--ignore-cr-at-eol"))
+               DIFF_XDL_SET(options, IGNORE_CR_AT_EOL);
        else if (!strcmp(arg, "--ignore-blank-lines"))
                DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
        else if (!strcmp(arg, "--indent-heuristic"))
                DIFF_XDL_SET(options, INDENT_HEURISTIC);
        else if (!strcmp(arg, "--no-indent-heuristic"))
                DIFF_XDL_CLR(options, INDENT_HEURISTIC);
-       else if (!strcmp(arg, "--patience"))
+       else if (!strcmp(arg, "--patience")) {
+               int i;
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
-       else if (!strcmp(arg, "--histogram"))
+               /*
+                * Both --patience and --anchored use PATIENCE_DIFF
+                * internally, so remove any anchors previously
+                * specified.
+                */
+               for (i = 0; i < options->anchors_nr; i++)
+                       free(options->anchors[i]);
+               options->anchors_nr = 0;
+       } else if (!strcmp(arg, "--histogram"))
                options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
        else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) {
                long value = parse_algorithm_value(optarg);
@@ -4608,29 +4615,32 @@ int diff_opt_parse(struct diff_options *options,
                options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
                options->xdl_opts |= value;
                return argcount;
+       } else if (skip_prefix(arg, "--anchored=", &arg)) {
+               options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
+               ALLOC_GROW(options->anchors, options->anchors_nr + 1,
+                          options->anchors_alloc);
+               options->anchors[options->anchors_nr++] = xstrdup(arg);
        }
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
                enable_patch_output(&options->output_format);
-               DIFF_OPT_SET(options, BINARY);
+               options->flags.binary = 1;
        }
        else if (!strcmp(arg, "--full-index"))
-               DIFF_OPT_SET(options, FULL_INDEX);
+               options->flags.full_index = 1;
        else if (!strcmp(arg, "-a") || !strcmp(arg, "--text"))
-               DIFF_OPT_SET(options, TEXT);
+               options->flags.text = 1;
        else if (!strcmp(arg, "-R"))
-               DIFF_OPT_SET(options, REVERSE_DIFF);
+               options->flags.reverse_diff = 1;
        else if (!strcmp(arg, "--find-copies-harder"))
-               DIFF_OPT_SET(options, FIND_COPIES_HARDER);
+               options->flags.find_copies_harder = 1;
        else if (!strcmp(arg, "--follow"))
-               DIFF_OPT_SET(options, FOLLOW_RENAMES);
+               options->flags.follow_renames = 1;
        else if (!strcmp(arg, "--no-follow")) {
-               DIFF_OPT_CLR(options, FOLLOW_RENAMES);
-               DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES);
-       } else if (!strcmp(arg, "--color"))
-               options->use_color = 1;
-       else if (skip_prefix(arg, "--color=", &arg)) {
+               options->flags.follow_renames = 0;
+               options->flags.default_follow_renames = 0;
+       } else if (skip_to_optional_arg_default(arg, "--color", &arg, "always")) {
                int value = git_config_colorbool(NULL, arg);
                if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
@@ -4650,15 +4660,10 @@ int diff_opt_parse(struct diff_options *options,
                if (cm < 0)
                        die("bad --color-moved argument: %s", arg);
                options->color_moved = cm;
-       } else if (!strcmp(arg, "--color-words")) {
+       } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) {
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
-       else if (skip_prefix(arg, "--color-words=", &arg)) {
-               options->use_color = 1;
-               options->word_diff = DIFF_WORDS_COLOR;
-               options->word_regex = arg;
-       }
        else if (!strcmp(arg, "--word-diff")) {
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
@@ -4684,26 +4689,22 @@ int diff_opt_parse(struct diff_options *options,
                return argcount;
        }
        else if (!strcmp(arg, "--exit-code"))
-               DIFF_OPT_SET(options, EXIT_WITH_STATUS);
+               options->flags.exit_with_status = 1;
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUICK);
+               options->flags.quick = 1;
        else if (!strcmp(arg, "--ext-diff"))
-               DIFF_OPT_SET(options, ALLOW_EXTERNAL);
+               options->flags.allow_external = 1;
        else if (!strcmp(arg, "--no-ext-diff"))
-               DIFF_OPT_CLR(options, ALLOW_EXTERNAL);
-       else if (!strcmp(arg, "--textconv"))
-               DIFF_OPT_SET(options, ALLOW_TEXTCONV);
-       else if (!strcmp(arg, "--no-textconv"))
-               DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
-       else if (!strcmp(arg, "--ignore-submodules")) {
-               DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
-               handle_ignore_submodules_arg(options, "all");
-       } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) {
-               DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
+               options->flags.allow_external = 0;
+       else if (!strcmp(arg, "--textconv")) {
+               options->flags.allow_textconv = 1;
+               options->flags.textconv_set_via_cmdline = 1;
+       } else if (!strcmp(arg, "--no-textconv"))
+               options->flags.allow_textconv = 0;
+       else if (skip_to_optional_arg_default(arg, "--ignore-submodules", &arg, "all")) {
+               options->flags.override_submodule_config = 1;
                handle_ignore_submodules_arg(options, arg);
-       } else if (!strcmp(arg, "--submodule"))
-               options->submodule_format = DIFF_SUBMODULE_LOG;
-       else if (skip_prefix(arg, "--submodule=", &arg))
+       } else if (skip_to_optional_arg_default(arg, "--submodule", &arg, "log"))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
                return parse_ws_error_highlight_opt(options, arg);
@@ -4774,11 +4775,11 @@ int diff_opt_parse(struct diff_options *options,
                         &options->interhunkcontext))
                ;
        else if (!strcmp(arg, "-W"))
-               DIFF_OPT_SET(options, FUNCCONTEXT);
+               options->flags.funccontext = 1;
        else if (!strcmp(arg, "--function-context"))
-               DIFF_OPT_SET(options, FUNCCONTEXT);
+               options->flags.funccontext = 1;
        else if (!strcmp(arg, "--no-function-context"))
-               DIFF_OPT_CLR(options, FUNCCONTEXT);
+               options->flags.funccontext = 0;
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                char *path = prefix_filename(prefix, optarg);
                options->file = xfopen(path, "w");
@@ -4901,14 +4902,20 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len)
        int abblen;
        const char *abbrev;
 
+       /* Do we want all 40 hex characters? */
        if (len == GIT_SHA1_HEXSZ)
                return oid_to_hex(oid);
 
+       /* An abbreviated value is fine, possibly followed by an ellipsis. */
        abbrev = diff_abbrev_oid(oid, len);
+
+       if (!print_sha1_ellipsis())
+               return abbrev;
+
        abblen = strlen(abbrev);
 
        /*
-        * In well-behaved cases, where the abbbreviated result is the
+        * In well-behaved cases, where the abbreviated result is the
         * same as the requested length, append three dots after the
         * abbreviation (hence the whole logic is limited to the case
         * where abblen < 37); when the actual abbreviated result is a
@@ -5453,7 +5460,7 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
                warning(_(rename_limit_warning));
        else
                return;
-       if (0 < needed && needed < 32767)
+       if (0 < needed)
                warning(_(rename_limit_advice), varname, needed);
 }
 
@@ -5528,7 +5535,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE))
+       if (output_format & DIFF_FORMAT_DIRSTAT && options->flags.dirstat_by_line)
                dirstat_by_line = 1;
 
        if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) ||
@@ -5563,8 +5570,8 @@ void diff_flush(struct diff_options *options)
        }
 
        if (output_format & DIFF_FORMAT_NO_OUTPUT &&
-           DIFF_OPT_TST(options, EXIT_WITH_STATUS) &&
-           DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+           options->flags.exit_with_status &&
+           options->flags.diff_from_contents) {
                /*
                 * run diff_flush_patch for the exit status. setting
                 * options->file to /dev/null should be safe, because we
@@ -5612,11 +5619,11 @@ void diff_flush(struct diff_options *options)
         * diff_addremove/diff_change does not set the bit when
         * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
         */
-       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+       if (options->flags.diff_from_contents) {
                if (options->found_changes)
-                       DIFF_OPT_SET(options, HAS_CHANGES);
+                       options->flags.has_changes = 1;
                else
-                       DIFF_OPT_CLR(options, HAS_CHANGES);
+                       options->flags.has_changes = 0;
        }
 }
 
@@ -5736,7 +5743,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
                         * to determine how many paths were dirty only
                         * due to stat info mismatch.
                         */
-                       if (!DIFF_OPT_TST(diffopt, NO_INDEX))
+                       if (!diffopt->flags.no_index)
                                diffopt->skip_stat_unmatch++;
                        diff_free_filepair(p);
                }
@@ -5785,10 +5792,10 @@ void diffcore_std(struct diff_options *options)
                diff_resolve_rename_copy();
        diffcore_apply_filter(options);
 
-       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
-               DIFF_OPT_SET(options, HAS_CHANGES);
+       if (diff_queued_diff.nr && !options->flags.diff_from_contents)
+               options->flags.has_changes = 1;
        else
-               DIFF_OPT_CLR(options, HAS_CHANGES);
+               options->flags.has_changes = 0;
 
        options->found_follow = 0;
 }
@@ -5800,23 +5807,23 @@ int diff_result_code(struct diff_options *opt, int status)
        diff_warn_rename_limit("diff.renameLimit",
                               opt->needed_rename_limit,
                               opt->degraded_cc_to_c);
-       if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+       if (!opt->flags.exit_with_status &&
            !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
                return status;
-       if (DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
-           DIFF_OPT_TST(opt, HAS_CHANGES))
+       if (opt->flags.exit_with_status &&
+           opt->flags.has_changes)
                result |= 01;
        if ((opt->output_format & DIFF_FORMAT_CHECKDIFF) &&
-           DIFF_OPT_TST(opt, CHECK_FAILED))
+           opt->flags.check_failed)
                result |= 02;
        return result;
 }
 
 int diff_can_quit_early(struct diff_options *opt)
 {
-       return (DIFF_OPT_TST(opt, QUICK) &&
+       return (opt->flags.quick &&
                !opt->filter &&
-               DIFF_OPT_TST(opt, HAS_CHANGES));
+               opt->flags.has_changes);
 }
 
 /*
@@ -5828,10 +5835,10 @@ int diff_can_quit_early(struct diff_options *opt)
 static int is_submodule_ignored(const char *path, struct diff_options *options)
 {
        int ignored = 0;
-       unsigned orig_flags = options->flags;
-       if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
+       struct diff_flags orig_flags = options->flags;
+       if (!options->flags.override_submodule_config)
                set_diffopt_flags_from_submodule_config(options, path);
-       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
+       if (options->flags.ignore_submodules)
                ignored = 1;
        options->flags = orig_flags;
        return ignored;
@@ -5860,7 +5867,7 @@ void diff_addremove(struct diff_options *options,
         * Before the final output happens, they are pruned after
         * merged into rename/copy pairs as appropriate.
         */
-       if (DIFF_OPT_TST(options, REVERSE_DIFF))
+       if (options->flags.reverse_diff)
                addremove = (addremove == '+' ? '-' :
                             addremove == '-' ? '+' : addremove);
 
@@ -5879,8 +5886,8 @@ void diff_addremove(struct diff_options *options,
        }
 
        diff_queue(&diff_queued_diff, one, two);
-       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
-               DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!options->flags.diff_from_contents)
+               options->flags.has_changes = 1;
 }
 
 void diff_change(struct diff_options *options,
@@ -5898,7 +5905,7 @@ void diff_change(struct diff_options *options,
            is_submodule_ignored(concatpath, options))
                return;
 
-       if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
+       if (options->flags.reverse_diff) {
                SWAP(old_mode, new_mode);
                SWAP(old_oid, new_oid);
                SWAP(old_oid_valid, new_oid_valid);
@@ -5917,14 +5924,14 @@ void diff_change(struct diff_options *options,
        two->dirty_submodule = new_dirty_submodule;
        p = diff_queue(&diff_queued_diff, one, two);
 
-       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+       if (options->flags.diff_from_contents)
                return;
 
-       if (DIFF_OPT_TST(options, QUICK) && options->skip_stat_unmatch &&
+       if (options->flags.quick && options->skip_stat_unmatch &&
            !diff_filespec_check_stat_unmatch(p))
                return;
 
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       options->flags.has_changes = 1;
 }
 
 struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
@@ -6062,7 +6069,7 @@ void setup_diff_pager(struct diff_options *opt)
         * and because it is easy to find people oneline advising "git diff
         * --exit-code" in hooks and other scripts, we do not do so.
         */
-       if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+       if (!opt->flags.exit_with_status &&
            check_pager_config("diff") != 0)
                setup_pager();
 }
diff --git a/diff.h b/diff.h
index aca150ba2e0f0b0a984e7f15c292af5f5b057d61..7cf276f07733afdf14618a8c47fce33c89a41e24 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -60,42 +60,52 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 
 #define DIFF_FORMAT_CALLBACK   0x1000
 
-#define DIFF_OPT_RECURSIVE           (1 <<  0)
-#define DIFF_OPT_TREE_IN_RECURSIVE   (1 <<  1)
-#define DIFF_OPT_BINARY              (1 <<  2)
-#define DIFF_OPT_TEXT                (1 <<  3)
-#define DIFF_OPT_FULL_INDEX          (1 <<  4)
-#define DIFF_OPT_SILENT_ON_REMOVE    (1 <<  5)
-#define DIFF_OPT_FIND_COPIES_HARDER  (1 <<  6)
-#define DIFF_OPT_FOLLOW_RENAMES      (1 <<  7)
-#define DIFF_OPT_RENAME_EMPTY        (1 <<  8)
-/* (1 <<  9) unused */
-#define DIFF_OPT_HAS_CHANGES         (1 << 10)
-#define DIFF_OPT_QUICK               (1 << 11)
-#define DIFF_OPT_NO_INDEX            (1 << 12)
-#define DIFF_OPT_ALLOW_EXTERNAL      (1 << 13)
-#define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
-#define DIFF_OPT_REVERSE_DIFF        (1 << 15)
-#define DIFF_OPT_CHECK_FAILED        (1 << 16)
-#define DIFF_OPT_RELATIVE_NAME       (1 << 17)
-#define DIFF_OPT_IGNORE_SUBMODULES   (1 << 18)
-#define DIFF_OPT_DIRSTAT_CUMULATIVE  (1 << 19)
-#define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
-#define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
-#define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
-#define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
-#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
-#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
-#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
-#define DIFF_OPT_DIRSTAT_BY_LINE     (1 << 28)
-#define DIFF_OPT_FUNCCONTEXT         (1 << 29)
-#define DIFF_OPT_PICKAXE_IGNORE_CASE (1 << 30)
-#define DIFF_OPT_DEFAULT_FOLLOW_RENAMES (1U << 31)
-
-#define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
-#define DIFF_OPT_TOUCHED(opts, flag)    ((opts)->touched_flags & DIFF_OPT_##flag)
-#define DIFF_OPT_SET(opts, flag)    (((opts)->flags |= DIFF_OPT_##flag),((opts)->touched_flags |= DIFF_OPT_##flag))
-#define DIFF_OPT_CLR(opts, flag)    (((opts)->flags &= ~DIFF_OPT_##flag),((opts)->touched_flags |= DIFF_OPT_##flag))
+#define DIFF_FLAGS_INIT { 0 }
+struct diff_flags {
+       unsigned recursive:1;
+       unsigned tree_in_recursive:1;
+       unsigned binary:1;
+       unsigned text:1;
+       unsigned full_index:1;
+       unsigned silent_on_remove:1;
+       unsigned find_copies_harder:1;
+       unsigned follow_renames:1;
+       unsigned rename_empty:1;
+       unsigned has_changes:1;
+       unsigned quick:1;
+       unsigned no_index:1;
+       unsigned allow_external:1;
+       unsigned exit_with_status:1;
+       unsigned reverse_diff:1;
+       unsigned check_failed:1;
+       unsigned relative_name:1;
+       unsigned ignore_submodules:1;
+       unsigned dirstat_cumulative:1;
+       unsigned dirstat_by_file:1;
+       unsigned allow_textconv:1;
+       unsigned textconv_set_via_cmdline:1;
+       unsigned diff_from_contents:1;
+       unsigned dirty_submodules:1;
+       unsigned ignore_untracked_in_submodules:1;
+       unsigned ignore_dirty_submodules:1;
+       unsigned override_submodule_config:1;
+       unsigned dirstat_by_line:1;
+       unsigned funccontext:1;
+       unsigned pickaxe_ignore_case:1;
+       unsigned default_follow_renames:1;
+};
+
+static inline void diff_flags_or(struct diff_flags *a,
+                                const struct diff_flags *b)
+{
+       char *tmp_a = (char *)a;
+       const char *tmp_b = (const char *)b;
+       int i;
+
+       for (i = 0; i < sizeof(struct diff_flags); i++)
+               tmp_a[i] |= tmp_b[i];
+}
+
 #define DIFF_XDL_TST(opts, flag)    ((opts)->xdl_opts & XDF_##flag)
 #define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
 #define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
@@ -122,8 +132,7 @@ struct diff_options {
        const char *a_prefix, *b_prefix;
        const char *line_prefix;
        size_t line_prefix_length;
-       unsigned flags;
-       unsigned touched_flags;
+       struct diff_flags flags;
 
        /* diff-filter bits */
        unsigned int filter;
@@ -157,6 +166,10 @@ struct diff_options {
        const char *stat_sep;
        long xdl_opts;
 
+       /* see Documentation/diff-options.txt */
+       char **anchors;
+       size_t anchors_nr, anchors_alloc;
+
        int stat_width;
        int stat_name_width;
        int stat_graph_width;
@@ -180,6 +193,7 @@ struct diff_options {
        pathchange_fn_t pathchange;
        change_fn_t change;
        add_remove_fn_t add_remove;
+       void *change_fn_data;
        diff_format_fn_t format_callback;
        void *format_callback_data;
        diff_prefix_fn_t output_prefix;
@@ -388,7 +402,8 @@ extern int diff_result_code(struct diff_options *, int);
 
 extern void diff_no_index(struct rev_info *, int, const char **);
 
-extern int index_differs_from(const char *def, int diff_flags, int ita_invisible_in_index);
+extern int index_differs_from(const char *def, const struct diff_flags *flags,
+                             int ita_invisible_in_index);
 
 /*
  * Fill the contents of the filespec "df", respecting any textconv defined by
index 341529b5a865ae6460f50d8593d52497059f2408..9476bd21081f456be6ae6412ea591e11dde50686 100644 (file)
@@ -131,7 +131,7 @@ static int pickaxe_match(struct diff_filepair *p, struct diff_options *o,
        if (!DIFF_FILE_VALID(p->one) && !DIFF_FILE_VALID(p->two))
                return 0;
 
-       if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
+       if (o->flags.allow_textconv) {
                textconv_one = get_textconv(p->one);
                textconv_two = get_textconv(p->two);
        }
@@ -222,11 +222,11 @@ void diffcore_pickaxe(struct diff_options *o)
 
        if (opts & (DIFF_PICKAXE_REGEX | DIFF_PICKAXE_KIND_G)) {
                int cflags = REG_EXTENDED | REG_NEWLINE;
-               if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE))
+               if (o->flags.pickaxe_ignore_case)
                        cflags |= REG_ICASE;
                regcomp_or_die(&regex, needle, cflags);
                regexp = &regex;
-       } else if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE) &&
+       } else if (o->flags.pickaxe_ignore_case &&
                   has_non_ascii(needle)) {
                struct strbuf sb = STRBUF_INIT;
                int cflags = REG_NEWLINE | REG_ICASE;
@@ -236,7 +236,7 @@ void diffcore_pickaxe(struct diff_options *o)
                strbuf_release(&sb);
                regexp = &regex;
        } else {
-               kws = kwsalloc(DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE)
+               kws = kwsalloc(o->flags.pickaxe_ignore_case
                               ? tolower_trans_tbl : NULL);
                kwsincr(kws, needle, strlen(needle));
                kwsprep(kws);
index 0d8c3d2ee480d70718f1e6f0b43a1175a14e4584..245e999fe5c6e9da62303423570d07aae5a88897 100644 (file)
@@ -391,21 +391,19 @@ static int too_many_rename_candidates(int num_create,
         * growing larger than a "rename_limit" square matrix, ie:
         *
         *    num_create * num_src > rename_limit * rename_limit
-        *
-        * but handles the potential overflow case specially (and we
-        * assume at least 32-bit integers)
         */
-       if (rename_limit <= 0 || rename_limit > 32767)
+       if (rename_limit <= 0)
                rename_limit = 32767;
        if ((num_create <= rename_limit || num_src <= rename_limit) &&
-           (num_create * num_src <= rename_limit * rename_limit))
+           ((uint64_t)num_create * (uint64_t)num_src
+            <= (uint64_t)rename_limit * (uint64_t)rename_limit))
                return 0;
 
        options->needed_rename_limit =
                num_src > num_create ? num_src : num_create;
 
        /* Are we running under -C -C? */
-       if (!DIFF_OPT_TST(options, FIND_COPIES_HARDER))
+       if (!options->flags.find_copies_harder)
                return 1;
 
        /* Would we bust the limit if we were running under -C? */
@@ -415,7 +413,8 @@ static int too_many_rename_candidates(int num_create,
                num_src++;
        }
        if ((num_create <= rename_limit || num_src <= rename_limit) &&
-           (num_create * num_src <= rename_limit * rename_limit))
+           ((uint64_t)num_create * (uint64_t)num_src
+            <= (uint64_t)rename_limit * (uint64_t)rename_limit))
                return 2;
        return 1;
 }
@@ -463,7 +462,7 @@ void diffcore_rename(struct diff_options *options)
                        else if (options->single_follow &&
                                 strcmp(options->single_follow, p->two->path))
                                continue; /* not interested */
-                       else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
+                       else if (!options->flags.rename_empty &&
                                 is_empty_blob_oid(&p->two->oid))
                                continue;
                        else if (add_rename_dst(p->two) < 0) {
@@ -473,7 +472,7 @@ void diffcore_rename(struct diff_options *options)
                                goto cleanup;
                        }
                }
-               else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
+               else if (!options->flags.rename_empty &&
                         is_empty_blob_oid(&p->one->oid))
                        continue;
                else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) {
@@ -534,7 +533,7 @@ void diffcore_rename(struct diff_options *options)
        if (options->show_rename_progress) {
                progress = start_delayed_progress(
                                _("Performing inexact rename detection"),
-                               rename_dst_nr * rename_src_nr);
+                               (uint64_t)rename_dst_nr * (uint64_t)rename_src_nr);
        }
 
        mx = xcalloc(st_mult(NUM_CANDIDATE_PER_DST, num_create), sizeof(*mx));
@@ -571,7 +570,7 @@ void diffcore_rename(struct diff_options *options)
                        diff_free_filespec_blob(two);
                }
                dst_cnt++;
-               display_progress(progress, (i+1)*rename_src_nr);
+               display_progress(progress, (uint64_t)(i+1)*(uint64_t)rename_src_nr);
        }
        stop_progress(&progress);
 
diff --git a/dir.c b/dir.c
index 9987011da57bcf171ba645788b8ccd2151561e5c..7c4b45e30e0ac87527ff0ef3fd8c90670a1e2064 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -18,6 +18,7 @@
 #include "utf8.h"
 #include "varint.h"
 #include "ewah/ewok.h"
+#include "fsmonitor.h"
 
 /*
  * Tells read_directory_recursive how a file or directory should be treated.
@@ -220,6 +221,57 @@ int within_depth(const char *name, int namelen,
        return 1;
 }
 
+/*
+ * Read the contents of the blob with the given OID into a buffer.
+ * Append a trailing LF to the end if the last line doesn't have one.
+ *
+ * Returns:
+ *    -1 when the OID is invalid or unknown or does not refer to a blob.
+ *     0 when the blob is empty.
+ *     1 along with { data, size } of the (possibly augmented) buffer
+ *       when successful.
+ *
+ * Optionally updates the given sha1_stat with the given OID (when valid).
+ */
+static int do_read_blob(const struct object_id *oid,
+                       struct sha1_stat *sha1_stat,
+                       size_t *size_out,
+                       char **data_out)
+{
+       enum object_type type;
+       unsigned long sz;
+       char *data;
+
+       *size_out = 0;
+       *data_out = NULL;
+
+       data = read_sha1_file(oid->hash, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return -1;
+       }
+
+       if (sha1_stat) {
+               memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
+               hashcpy(sha1_stat->sha1, oid->hash);
+       }
+
+       if (sz == 0) {
+               free(data);
+               return 0;
+       }
+
+       if (data[sz - 1] != '\n') {
+               data = xrealloc(data, st_add(sz, 1));
+               data[sz++] = '\n';
+       }
+
+       *size_out = xsize_t(sz);
+       *data_out = data;
+
+       return 1;
+}
+
 #define DO_MATCH_EXCLUDE   (1<<0)
 #define DO_MATCH_DIRECTORY (1<<1)
 #define DO_MATCH_SUBMODULE (1<<2)
@@ -600,32 +652,22 @@ void add_exclude(const char *string, const char *base,
        x->el = el;
 }
 
-static void *read_skip_worktree_file_from_index(const struct index_state *istate,
-                                               const char *path, size_t *size,
-                                               struct sha1_stat *sha1_stat)
+static int read_skip_worktree_file_from_index(const struct index_state *istate,
+                                             const char *path,
+                                             size_t *size_out,
+                                             char **data_out,
+                                             struct sha1_stat *sha1_stat)
 {
        int pos, len;
-       unsigned long sz;
-       enum object_type type;
-       void *data;
 
        len = strlen(path);
        pos = index_name_pos(istate, path, len);
        if (pos < 0)
-               return NULL;
+               return -1;
        if (!ce_skip_worktree(istate->cache[pos]))
-               return NULL;
-       data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz);
-       if (!data || type != OBJ_BLOB) {
-               free(data);
-               return NULL;
-       }
-       *size = xsize_t(sz);
-       if (sha1_stat) {
-               memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
-               hashcpy(sha1_stat->sha1, istate->cache[pos]->oid.hash);
-       }
-       return data;
+               return -1;
+
+       return do_read_blob(&istate->cache[pos]->oid, sha1_stat, size_out, data_out);
 }
 
 /*
@@ -739,6 +781,10 @@ static void invalidate_directory(struct untracked_cache *uc,
                dir->dirs[i]->recurse = 0;
 }
 
+static int add_excludes_from_buffer(char *buf, size_t size,
+                                   const char *base, int baselen,
+                                   struct exclude_list *el);
+
 /*
  * Given a file with name "fname", read it (either from disk, or from
  * an index if 'istate' is non-null), parse it and store the
@@ -754,9 +800,10 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                        struct sha1_stat *sha1_stat)
 {
        struct stat st;
-       int fd, i, lineno = 1;
+       int r;
+       int fd;
        size_t size = 0;
-       char *buf, *entry;
+       char *buf;
 
        fd = open(fname, O_RDONLY);
        if (fd < 0 || fstat(fd, &st) < 0) {
@@ -764,17 +811,13 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                        warn_on_fopen_errors(fname);
                else
                        close(fd);
-               if (!istate ||
-                   (buf = read_skip_worktree_file_from_index(istate, fname, &size, sha1_stat)) == NULL)
+               if (!istate)
                        return -1;
-               if (size == 0) {
-                       free(buf);
-                       return 0;
-               }
-               if (buf[size-1] != '\n') {
-                       buf = xrealloc(buf, st_add(size, 1));
-                       buf[size++] = '\n';
-               }
+               r = read_skip_worktree_file_from_index(istate, fname,
+                                                      &size, &buf,
+                                                      sha1_stat);
+               if (r != 1)
+                       return r;
        } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
@@ -813,6 +856,17 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                }
        }
 
+       add_excludes_from_buffer(buf, size, base, baselen, el);
+       return 0;
+}
+
+static int add_excludes_from_buffer(char *buf, size_t size,
+                                   const char *base, int baselen,
+                                   struct exclude_list *el)
+{
+       int i, lineno = 1;
+       char *entry;
+
        el->filebuf = buf;
 
        if (skip_utf8_bom(&buf, size))
@@ -841,6 +895,23 @@ int add_excludes_from_file_to_list(const char *fname, const char *base,
        return add_excludes(fname, base, baselen, el, istate, NULL);
 }
 
+int add_excludes_from_blob_to_list(
+       struct object_id *oid,
+       const char *base, int baselen,
+       struct exclude_list *el)
+{
+       char *buf;
+       size_t size;
+       int r;
+
+       r = do_read_blob(oid, NULL, &size, &buf);
+       if (r != 1)
+               return r;
+
+       add_excludes_from_buffer(buf, size, base, baselen, el);
+       return 0;
+}
+
 struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                      int group_type, const char *src)
 {
@@ -1389,9 +1460,33 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
        case index_nonexistent:
                if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
                        break;
+               if (exclude &&
+                       (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                       (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
+
+                       /*
+                        * This is an excluded directory and we are
+                        * showing ignored paths that match an exclude
+                        * pattern.  (e.g. show directory as ignored
+                        * only if it matches an exclude pattern).
+                        * This path will either be 'path_excluded`
+                        * (if we are showing empty directories or if
+                        * the directory is not empty), or will be
+                        * 'path_none' (empty directory, and we are
+                        * not showing empty directories).
+                        */
+                       if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+                               return path_excluded;
+
+                       if (read_directory_recursive(dir, istate, dirname, len,
+                                                    untracked, 1, 1, pathspec) == path_excluded)
+                               return path_excluded;
+
+                       return path_none;
+               }
                if (!(dir->flags & DIR_NO_GITLINKS)) {
-                       unsigned char sha1[20];
-                       if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
+                       struct object_id oid;
+                       if (resolve_gitlink_ref(dirname, "HEAD", &oid) == 0)
                                return exclude ? path_excluded : path_untracked;
                }
                return path_recurse;
@@ -1561,6 +1656,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
 {
        int exclude;
        int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
+       enum path_treatment path_treatment;
 
        if (dtype == DT_UNKNOWN)
                dtype = get_dtype(de, istate, path->buf, path->len);
@@ -1607,8 +1703,23 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
-               return treat_directory(dir, istate, untracked, path->buf, path->len,
-                                      baselen, exclude, pathspec);
+               path_treatment = treat_directory(dir, istate, untracked,
+                                                path->buf, path->len,
+                                                baselen, exclude, pathspec);
+               /*
+                * If 1) we only want to return directories that
+                * match an exclude pattern and 2) this directory does
+                * not match an exclude pattern but all of its
+                * contents are excluded, then indicate that we should
+                * recurse into this directory (instead of marking the
+                * directory itself as an ignored path).
+                */
+               if (!exclude &&
+                   path_treatment == path_excluded &&
+                   (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                   (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
+                       return path_recurse;
+               return path_treatment;
        case DT_REG:
        case DT_LNK:
                return exclude ? path_excluded : path_untracked;
@@ -1693,17 +1804,23 @@ static int valid_cached_dir(struct dir_struct *dir,
        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(istate, &untracked->stat_data, &st)) {
-               if (untracked->valid)
+       /*
+        * With fsmonitor, we can trust the untracked cache's valid field.
+        */
+       refresh_fsmonitor(istate);
+       if (!(dir->untracked->use_fsmonitor && untracked->valid)) {
+               if (stat(path->len ? path->buf : ".", &st)) {
                        invalidate_directory(dir->untracked, untracked);
-               fill_stat_data(&untracked->stat_data, &st);
-               return 0;
+                       memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
+                       return 0;
+               }
+               if (!untracked->valid ||
+                       match_stat_data_racy(istate, &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) {
@@ -2279,10 +2396,10 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
        int ret = 0, original_len = path->len, len, kept_down = 0;
        int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
        int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
-       unsigned char submodule_head[20];
+       struct object_id submodule_head;
 
        if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
-           !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
+           !resolve_gitlink_ref(path->buf, "HEAD", &submodule_head)) {
                /* Do not descend and nuke a nested git work tree. */
                if (kept_up)
                        *kept_up = 1;
diff --git a/dir.h b/dir.h
index e3717055d193366e6780b50d68f6a3c2360d32c4..11a047ba486b81f624fb021418f06cbbd65da676 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -139,6 +139,8 @@ struct untracked_cache {
        int gitignore_invalidated;
        int dir_invalidated;
        int dir_opened;
+       /* fsmonitor invalidation data */
+       unsigned int use_fsmonitor : 1;
 };
 
 struct dir_struct {
@@ -152,7 +154,8 @@ struct dir_struct {
                DIR_COLLECT_IGNORED = 1<<4,
                DIR_SHOW_IGNORED_TOO = 1<<5,
                DIR_COLLECT_KILLED_ONLY = 1<<6,
-               DIR_KEEP_UNTRACKED_CONTENTS = 1<<7
+               DIR_KEEP_UNTRACKED_CONTENTS = 1<<7,
+               DIR_SHOW_IGNORED_TOO_MODE_MATCHING = 1<<8
        } flags;
        struct dir_entry **entries;
        struct dir_entry **ignored;
@@ -256,6 +259,9 @@ extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
                                          struct exclude_list *el, struct  index_state *istate);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern int add_excludes_from_blob_to_list(struct object_id *oid,
+                                         const char *base, int baselen,
+                                         struct exclude_list *el);
 extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *el, int srcpos);
index 7519edecdc297a0c0fdbbb2f3c68cb55abf2e8b2..9a9b4e12d1db7f8ec2f4f99d9a5f348b680cecc3 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -7,11 +7,16 @@
 #define DEFAULT_EDITOR "vi"
 #endif
 
+int is_terminal_dumb(void)
+{
+       const char *terminal = getenv("TERM");
+       return !terminal || !strcmp(terminal, "dumb");
+}
+
 const char *git_editor(void)
 {
        const char *editor = getenv("GIT_EDITOR");
-       const char *terminal = getenv("TERM");
-       int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb");
+       int terminal_is_dumb = is_terminal_dumb();
 
        if (!editor && editor_program)
                editor = editor_program;
@@ -40,6 +45,23 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
                const char *args[] = { editor, real_path(path), NULL };
                struct child_process p = CHILD_PROCESS_INIT;
                int ret, sig;
+               int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
+
+               if (print_waiting_for_editor) {
+                       /*
+                        * A dumb terminal cannot erase the line later on. Add a
+                        * newline to separate the hint from subsequent output.
+                        *
+                        * Make sure that our message is separated with a whitespace
+                        * from further cruft that may be written by the editor.
+                        */
+                       const char term = is_terminal_dumb() ? '\n' : ' ';
+
+                       fprintf(stderr,
+                               _("hint: Waiting for your editor to close the file...%c"),
+                               term);
+                       fflush(stderr);
+               }
 
                p.argv = args;
                p.env = env;
@@ -58,6 +80,13 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
                if (ret)
                        return error("There was a problem with the editor '%s'.",
                                        editor);
+
+               if (print_waiting_for_editor && !is_terminal_dumb())
+                       /*
+                        * Go back to the beginning and erase the entire line to
+                        * avoid wasting the vertical space.
+                        */
+                       fputs("\r\033[K", stderr);
        }
 
        if (!buffer)
diff --git a/entry.c b/entry.c
index 944c183b0f9cce90d62a2d998098ca48fc53c450..30211447ac839899d2c232af232c95312856816c 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -4,6 +4,7 @@
 #include "streaming.h"
 #include "submodule.h"
 #include "progress.h"
+#include "fsmonitor.h"
 
 static void create_directories(const char *path, int path_len,
                               const struct checkout *state)
@@ -373,6 +374,7 @@ static int write_entry(struct cache_entry *ce,
                                                   ce->name);
                fill_stat_cache_info(ce, &st);
                ce->ce_flags |= CE_UPDATE_IN_BASE;
+               mark_fsmonitor_invalid(state->istate, ce);
                state->istate->cache_changed |= CE_ENTRY_CHANGED;
        }
 delayed:
index 8289c25b44d74a0f054f9fe3eaf44c5fdcf0b3d2..63ac38a46f8f01ee3d09ed8e09bd93acba63b21a 100644 (file)
@@ -76,6 +76,7 @@ int protect_hfs = PROTECT_HFS_DEFAULT;
 #define PROTECT_NTFS_DEFAULT 0
 #endif
 int protect_ntfs = PROTECT_NTFS_DEFAULT;
+const char *core_fsmonitor;
 
 /*
  * The character that begins a commented line in user-editable file
@@ -343,3 +344,18 @@ int use_optional_locks(void)
 {
        return git_env_bool(GIT_OPTIONAL_LOCKS_ENVIRONMENT, 1);
 }
+
+int print_sha1_ellipsis(void)
+{
+       /*
+        * Determine if the calling environment contains the variable
+        * GIT_PRINT_SHA1_ELLIPSIS set to "yes".
+        */
+       static int cached_result = -1; /* unknown */
+
+       if (cached_result < 0) {
+               const char *v = getenv("GIT_PRINT_SHA1_ELLIPSIS");
+               cached_result = (v && !strcasecmp(v, "yes"));
+       }
+       return cached_result;
+}
index d5e4cf0bad411dad859dbe421ab7583cfb0f1561..b70ac025e0428b239c9cb8b8a70dda70c0cc1007 100644 (file)
@@ -1758,7 +1758,7 @@ static int update_branch(struct branch *b)
                        delete_ref(NULL, b->name, NULL, 0);
                return 0;
        }
-       if (read_ref(b->name, old_oid.hash))
+       if (read_ref(b->name, &old_oid))
                oidclr(&old_oid);
        if (!force_update && !is_null_oid(&old_oid)) {
                struct commit *old_cmit, *new_cmit;
@@ -1778,7 +1778,7 @@ static int update_branch(struct branch *b)
        }
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, b->name, b->oid.hash, old_oid.hash,
+           ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
                                   0, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@ -1820,7 +1820,7 @@ static void dump_tags(void)
                strbuf_addf(&ref_name, "refs/tags/%s", t->name);
 
                if (ref_transaction_update(transaction, ref_name.buf,
-                                          t->oid.hash, NULL, 0, msg, &err)) {
+                                          &t->oid, NULL, 0, msg, &err)) {
                        failure |= error("%s", err.buf);
                        goto cleanup;
                }
index 008b25d3db08726b0fc1569f88fba76694bbcc12..9f6b07ad91f8c2c17e85004a0e71a8b69752d120 100644 (file)
@@ -716,7 +716,8 @@ static int everything_local(struct fetch_pack_args *args,
        for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
 
-               if (!has_object_file(&ref->old_oid))
+               if (!has_object_file_with_flags(&ref->old_oid,
+                                               OBJECT_INFO_QUICK))
                        continue;
 
                o = parse_object(&ref->old_oid);
diff --git a/fsmonitor.c b/fsmonitor.c
new file mode 100644 (file)
index 0000000..0af7c4e
--- /dev/null
@@ -0,0 +1,266 @@
+#include "cache.h"
+#include "config.h"
+#include "dir.h"
+#include "ewah/ewok.h"
+#include "fsmonitor.h"
+#include "run-command.h"
+#include "strbuf.h"
+
+#define INDEX_EXTENSION_VERSION        (1)
+#define HOOK_INTERFACE_VERSION (1)
+
+struct trace_key trace_fsmonitor = TRACE_KEY_INIT(FSMONITOR);
+
+static void fsmonitor_ewah_callback(size_t pos, void *is)
+{
+       struct index_state *istate = (struct index_state *)is;
+       struct cache_entry *ce = istate->cache[pos];
+
+       ce->ce_flags &= ~CE_FSMONITOR_VALID;
+}
+
+int read_fsmonitor_extension(struct index_state *istate, const void *data,
+       unsigned long sz)
+{
+       const char *index = data;
+       uint32_t hdr_version;
+       uint32_t ewah_size;
+       struct ewah_bitmap *fsmonitor_dirty;
+       int ret;
+
+       if (sz < sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint32_t))
+               return error("corrupt fsmonitor extension (too short)");
+
+       hdr_version = get_be32(index);
+       index += sizeof(uint32_t);
+       if (hdr_version != INDEX_EXTENSION_VERSION)
+               return error("bad fsmonitor version %d", hdr_version);
+
+       istate->fsmonitor_last_update = get_be64(index);
+       index += sizeof(uint64_t);
+
+       ewah_size = get_be32(index);
+       index += sizeof(uint32_t);
+
+       fsmonitor_dirty = ewah_new();
+       ret = ewah_read_mmap(fsmonitor_dirty, index, ewah_size);
+       if (ret != ewah_size) {
+               ewah_free(fsmonitor_dirty);
+               return error("failed to parse ewah bitmap reading fsmonitor index extension");
+       }
+       istate->fsmonitor_dirty = fsmonitor_dirty;
+
+       trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful");
+       return 0;
+}
+
+void fill_fsmonitor_bitmap(struct index_state *istate)
+{
+       int i;
+       istate->fsmonitor_dirty = ewah_new();
+       for (i = 0; i < istate->cache_nr; i++)
+               if (!(istate->cache[i]->ce_flags & CE_FSMONITOR_VALID))
+                       ewah_set(istate->fsmonitor_dirty, i);
+}
+
+void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
+{
+       uint32_t hdr_version;
+       uint64_t tm;
+       uint32_t ewah_start;
+       uint32_t ewah_size = 0;
+       int fixup = 0;
+
+       put_be32(&hdr_version, INDEX_EXTENSION_VERSION);
+       strbuf_add(sb, &hdr_version, sizeof(uint32_t));
+
+       put_be64(&tm, istate->fsmonitor_last_update);
+       strbuf_add(sb, &tm, sizeof(uint64_t));
+       fixup = sb->len;
+       strbuf_add(sb, &ewah_size, sizeof(uint32_t)); /* we'll fix this up later */
+
+       ewah_start = sb->len;
+       ewah_serialize_strbuf(istate->fsmonitor_dirty, sb);
+       ewah_free(istate->fsmonitor_dirty);
+       istate->fsmonitor_dirty = NULL;
+
+       /* fix up size field */
+       put_be32(&ewah_size, sb->len - ewah_start);
+       memcpy(sb->buf + fixup, &ewah_size, sizeof(uint32_t));
+
+       trace_printf_key(&trace_fsmonitor, "write fsmonitor extension successful");
+}
+
+/*
+ * Call the query-fsmonitor hook passing the time of the last saved results.
+ */
+static int query_fsmonitor(int version, uint64_t last_update, struct strbuf *query_result)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+       char ver[64];
+       char date[64];
+       const char *argv[4];
+
+       if (!(argv[0] = core_fsmonitor))
+               return -1;
+
+       snprintf(ver, sizeof(version), "%d", version);
+       snprintf(date, sizeof(date), "%" PRIuMAX, (uintmax_t)last_update);
+       argv[1] = ver;
+       argv[2] = date;
+       argv[3] = NULL;
+       cp.argv = argv;
+       cp.use_shell = 1;
+       cp.dir = get_git_work_tree();
+
+       return capture_command(&cp, query_result, 1024);
+}
+
+static void fsmonitor_refresh_callback(struct index_state *istate, const char *name)
+{
+       int pos = index_name_pos(istate, name, strlen(name));
+
+       if (pos >= 0) {
+               struct cache_entry *ce = istate->cache[pos];
+               ce->ce_flags &= ~CE_FSMONITOR_VALID;
+       }
+
+       /*
+        * Mark the untracked cache dirty even if it wasn't found in the index
+        * as it could be a new untracked file.
+        */
+       trace_printf_key(&trace_fsmonitor, "fsmonitor_refresh_callback '%s'", name);
+       untracked_cache_invalidate_path(istate, name);
+}
+
+void refresh_fsmonitor(struct index_state *istate)
+{
+       static int has_run_once = 0;
+       struct strbuf query_result = STRBUF_INIT;
+       int query_success = 0;
+       size_t bol; /* beginning of line */
+       uint64_t last_update;
+       char *buf;
+       int i;
+
+       if (!core_fsmonitor || has_run_once)
+               return;
+       has_run_once = 1;
+
+       trace_printf_key(&trace_fsmonitor, "refresh fsmonitor");
+       /*
+        * This could be racy so save the date/time now and query_fsmonitor
+        * should be inclusive to ensure we don't miss potential changes.
+        */
+       last_update = getnanotime();
+
+       /*
+        * If we have a last update time, call query_fsmonitor for the set of
+        * changes since that time, else assume everything is possibly dirty
+        * and check it all.
+        */
+       if (istate->fsmonitor_last_update) {
+               query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION,
+                       istate->fsmonitor_last_update, &query_result);
+               trace_performance_since(last_update, "fsmonitor process '%s'", core_fsmonitor);
+               trace_printf_key(&trace_fsmonitor, "fsmonitor process '%s' returned %s",
+                       core_fsmonitor, query_success ? "success" : "failure");
+       }
+
+       /* a fsmonitor process can return '/' to indicate all entries are invalid */
+       if (query_success && query_result.buf[0] != '/') {
+               /* Mark all entries returned by the monitor as dirty */
+               buf = query_result.buf;
+               bol = 0;
+               for (i = 0; i < query_result.len; i++) {
+                       if (buf[i] != '\0')
+                               continue;
+                       fsmonitor_refresh_callback(istate, buf + bol);
+                       bol = i + 1;
+               }
+               if (bol < query_result.len)
+                       fsmonitor_refresh_callback(istate, buf + bol);
+       } else {
+               /* Mark all entries invalid */
+               for (i = 0; i < istate->cache_nr; i++)
+                       istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
+
+               if (istate->untracked)
+                       istate->untracked->use_fsmonitor = 0;
+       }
+       strbuf_release(&query_result);
+
+       /* Now that we've updated istate, save the last_update time */
+       istate->fsmonitor_last_update = last_update;
+}
+
+void add_fsmonitor(struct index_state *istate)
+{
+       int i;
+
+       if (!istate->fsmonitor_last_update) {
+               trace_printf_key(&trace_fsmonitor, "add fsmonitor");
+               istate->cache_changed |= FSMONITOR_CHANGED;
+               istate->fsmonitor_last_update = getnanotime();
+
+               /* reset the fsmonitor state */
+               for (i = 0; i < istate->cache_nr; i++)
+                       istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
+
+               /* reset the untracked cache */
+               if (istate->untracked) {
+                       add_untracked_cache(istate);
+                       istate->untracked->use_fsmonitor = 1;
+               }
+
+               /* Update the fsmonitor state */
+               refresh_fsmonitor(istate);
+       }
+}
+
+void remove_fsmonitor(struct index_state *istate)
+{
+       if (istate->fsmonitor_last_update) {
+               trace_printf_key(&trace_fsmonitor, "remove fsmonitor");
+               istate->cache_changed |= FSMONITOR_CHANGED;
+               istate->fsmonitor_last_update = 0;
+       }
+}
+
+void tweak_fsmonitor(struct index_state *istate)
+{
+       int i;
+       int fsmonitor_enabled = git_config_get_fsmonitor();
+
+       if (istate->fsmonitor_dirty) {
+               if (fsmonitor_enabled) {
+                       /* Mark all entries valid */
+                       for (i = 0; i < istate->cache_nr; i++) {
+                               istate->cache[i]->ce_flags |= CE_FSMONITOR_VALID;
+                       }
+
+                       /* Mark all previously saved entries as dirty */
+                       ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate);
+
+                       /* Now mark the untracked cache for fsmonitor usage */
+                       if (istate->untracked)
+                               istate->untracked->use_fsmonitor = 1;
+               }
+
+               ewah_free(istate->fsmonitor_dirty);
+               istate->fsmonitor_dirty = NULL;
+       }
+
+       switch (fsmonitor_enabled) {
+       case -1: /* keep: do nothing */
+               break;
+       case 0: /* false */
+               remove_fsmonitor(istate);
+               break;
+       case 1: /* true */
+               add_fsmonitor(istate);
+               break;
+       default: /* unknown value: do nothing */
+               break;
+       }
+}
diff --git a/fsmonitor.h b/fsmonitor.h
new file mode 100644 (file)
index 0000000..cd3cc0c
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef FSMONITOR_H
+#define FSMONITOR_H
+
+extern struct trace_key trace_fsmonitor;
+
+/*
+ * Read the fsmonitor index extension and (if configured) restore the
+ * CE_FSMONITOR_VALID state.
+ */
+extern int read_fsmonitor_extension(struct index_state *istate, const void *data, unsigned long sz);
+
+/*
+ * Fill the fsmonitor_dirty ewah bits with their state from the index,
+ * before it is split during writing.
+ */
+extern void fill_fsmonitor_bitmap(struct index_state *istate);
+
+/*
+ * Write the CE_FSMONITOR_VALID state into the fsmonitor index
+ * extension.  Reads from the fsmonitor_dirty ewah in the index.
+ */
+extern void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate);
+
+/*
+ * Add/remove the fsmonitor index extension
+ */
+extern void add_fsmonitor(struct index_state *istate);
+extern void remove_fsmonitor(struct index_state *istate);
+
+/*
+ * Add/remove the fsmonitor index extension as necessary based on the current
+ * core.fsmonitor setting.
+ */
+extern void tweak_fsmonitor(struct index_state *istate);
+
+/*
+ * Run the configured fsmonitor integration script and clear the
+ * CE_FSMONITOR_VALID bit for any files returned as dirty.  Also invalidate
+ * any corresponding untracked cache directory structures. Optimized to only
+ * run the first time it is called.
+ */
+extern void refresh_fsmonitor(struct index_state *istate);
+
+/*
+ * Set the given cache entries CE_FSMONITOR_VALID bit. This should be
+ * called any time the cache entry has been updated to reflect the
+ * current state of the file on disk.
+ */
+static inline void mark_fsmonitor_valid(struct cache_entry *ce)
+{
+       if (core_fsmonitor) {
+               ce->ce_flags |= CE_FSMONITOR_VALID;
+               trace_printf_key(&trace_fsmonitor, "mark_fsmonitor_clean '%s'", ce->name);
+       }
+}
+
+/*
+ * Clear the given cache entry's CE_FSMONITOR_VALID bit and invalidate
+ * any corresponding untracked cache directory structures. This should
+ * be called any time git creates or modifies a file that should
+ * trigger an lstat() or invalidate the untracked cache for the
+ * corresponding directory
+ */
+static inline void mark_fsmonitor_invalid(struct index_state *istate, struct cache_entry *ce)
+{
+       if (core_fsmonitor) {
+               ce->ce_flags &= ~CE_FSMONITOR_VALID;
+               untracked_cache_invalidate_path(istate, ce->name);
+               trace_printf_key(&trace_fsmonitor, "mark_fsmonitor_invalid '%s'", ce->name);
+       }
+}
+
+#endif
index ab0d1b0c064a72480d7eac454b297ce95a48c8f3..eeea4b67ea7f1ccee1770c18a36d018b5f3b32b0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-echo "/* Automatically generated by $0 */
+echo "/* Automatically generated by generate-cmdlist.sh */
 struct cmdname_help {
        char name[16];
        char help[80];
index ae3cb013e7d5679bc4c63fe4cea35d40167178c5..54cbfecc5ab0531513ff9e069be55d74339ad427 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
+USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|view|replay|log|run]'
 LONG_USAGE='git bisect help
        print this long help message.
 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
@@ -20,7 +20,7 @@ git bisect next
        find next bisection to test and check it out.
 git bisect reset [<commit>]
        finish bisection search and go back to commit.
-git bisect visualize
+git bisect (visualize|view)
        show bisect status in gitk.
 git bisect replay <logfile>
        replay bisection log.
@@ -186,7 +186,7 @@ bisect_start() {
        #
        # Get rid of any old bisect state.
        #
-       bisect_clean_state || exit
+       git bisect--helper --bisect-clean-state || exit
 
        #
        # Change state.
@@ -195,7 +195,7 @@ bisect_start() {
        # We have to trap this to be able to clean up using
        # "bisect_clean_state".
        #
-       trap 'bisect_clean_state' 0
+       trap 'git bisect--helper --bisect-clean-state' 0
        trap 'exit 255' 1 2 3 15
 
        #
@@ -209,7 +209,7 @@ bisect_start() {
        eval "$eval true" &&
        if test $must_write_terms -eq 1
        then
-               write_terms "$TERM_BAD" "$TERM_GOOD"
+               git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
        fi &&
        echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
        #
@@ -237,22 +237,6 @@ bisect_write() {
        test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
 }
 
-is_expected_rev() {
-       test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
-       test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
-}
-
-check_expected_revs() {
-       for _rev in "$@"; do
-               if ! is_expected_rev "$_rev"
-               then
-                       rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
-                       rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
-                       return
-               fi
-       done
-}
-
 bisect_skip() {
        all=''
        for arg in "$@"
@@ -280,7 +264,7 @@ bisect_state() {
                rev=$(git rev-parse --verify "$bisected_head") ||
                        die "$(eval_gettext "Bad rev input: \$bisected_head")"
                bisect_write "$state" "$rev"
-               check_expected_revs "$rev" ;;
+               git bisect--helper --check-expected-revs "$rev" ;;
        2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
                shift
                hash_list=''
@@ -294,7 +278,7 @@ bisect_state() {
                do
                        bisect_write "$state" "$rev"
                done
-               check_expected_revs $hash_list ;;
+               git bisect--helper --check-expected-revs $hash_list ;;
        *,"$TERM_BAD")
                die "$(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.")" ;;
        *)
@@ -430,27 +414,7 @@ bisect_reset() {
                die "$(eval_gettext "Could not check out original HEAD '\$branch'.
 Try 'git bisect reset <commit>'.")"
        fi
-       bisect_clean_state
-}
-
-bisect_clean_state() {
-       # There may be some refs packed during bisection.
-       git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
-       while read ref hash
-       do
-               git update-ref -d $ref $hash || exit
-       done
-       rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
-       rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
-       rm -f "$GIT_DIR/BISECT_LOG" &&
-       rm -f "$GIT_DIR/BISECT_NAMES" &&
-       rm -f "$GIT_DIR/BISECT_RUN" &&
-       rm -f "$GIT_DIR/BISECT_TERMS" &&
-       # Cleanup head-name if it got left by an old version of git-bisect
-       rm -f "$GIT_DIR/head-name" &&
-       git update-ref -d --no-deref BISECT_HEAD &&
-       # clean up BISECT_START last
-       rm -f "$GIT_DIR/BISECT_START"
+       git bisect--helper --bisect-clean-state || exit
 }
 
 bisect_replay () {
@@ -486,6 +450,8 @@ bisect_replay () {
 bisect_run () {
        bisect_next_check fail
 
+       test -n "$*" || die "$(gettext "bisect run failed: no command provided.")"
+
        while true
        do
                command="$@"
@@ -557,45 +523,6 @@ get_terms () {
        fi
 }
 
-write_terms () {
-       TERM_BAD=$1
-       TERM_GOOD=$2
-       if test "$TERM_BAD" = "$TERM_GOOD"
-       then
-               die "$(gettext "please use two different terms")"
-       fi
-       check_term_format "$TERM_BAD" bad
-       check_term_format "$TERM_GOOD" good
-       printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
-}
-
-check_term_format () {
-       term=$1
-       git check-ref-format refs/bisect/"$term" ||
-       die "$(eval_gettext "'\$term' is not a valid term")"
-       case "$term" in
-       help|start|terms|skip|next|reset|visualize|replay|log|run)
-               die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
-               ;;
-       bad|new)
-               if test "$2" != bad
-               then
-                       # In theory, nothing prevents swapping
-                       # completely good and bad, but this situation
-                       # could be confusing and hasn't been tested
-                       # enough. Forbid it for now.
-                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
-               fi
-               ;;
-       good|old)
-               if test "$2" != good
-               then
-                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
-               fi
-               ;;
-       esac
-}
-
 check_and_set_terms () {
        cmd="$1"
        case "$cmd" in
@@ -609,13 +536,17 @@ check_and_set_terms () {
                bad|good)
                        if ! test -s "$GIT_DIR/BISECT_TERMS"
                        then
-                               write_terms bad good
+                               TERM_BAD=bad
+                               TERM_GOOD=good
+                               git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
                        fi
                        ;;
                new|old)
                        if ! test -s "$GIT_DIR/BISECT_TERMS"
                        then
-                               write_terms new old
+                               TERM_BAD=new
+                               TERM_GOOD=old
+                               git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
                        fi
                        ;;
                esac ;;
index cedad4d5818a6e5c6e1732d34cf8fef6dab36718..68b2ad531ea6f9cf1127a0e5986c523fd86b0967 100644 (file)
@@ -484,6 +484,29 @@ static inline int skip_prefix(const char *str, const char *prefix,
        return 0;
 }
 
+/*
+ * If the string "str" is the same as the string in "prefix", then the "arg"
+ * parameter is set to the "def" parameter and 1 is returned.
+ * If the string "str" begins with the string found in "prefix" and then a
+ * "=" sign, then the "arg" parameter is set to "str + strlen(prefix) + 1"
+ * (i.e., to the point in the string right after the prefix and the "=" sign),
+ * and 1 is returned.
+ *
+ * Otherwise, return 0 and leave "arg" untouched.
+ *
+ * When we accept both a "--key" and a "--key=<val>" option, this function
+ * can be used instead of !strcmp(arg, "--key") and then
+ * skip_prefix(arg, "--key=", &arg) to parse such an option.
+ */
+int skip_to_optional_arg_default(const char *str, const char *prefix,
+                                const char **arg, const char *def);
+
+static inline int skip_to_optional_arg(const char *str, const char *prefix,
+                                      const char **arg)
+{
+       return skip_to_optional_arg_default(str, prefix, arg, "");
+}
+
 /*
  * Like skip_prefix, but promises never to read past "len" bytes of the input
  * buffer, and returns the remaining number of bytes in "out" via "outlen".
index 36929921ea79006dc659f6996ae158131ccf2b73..2d8df831722913093a46e9325796b85f67a97073 100755 (executable)
@@ -642,7 +642,7 @@ sub is_sha1 {
 
 sub get_headref ($) {
        my $name = shift;
-       $name =~ s/'/'\\''/;
+       $name =~ s/'/'\\''/g;
        my $r = `git rev-parse --verify '$name' 2>/dev/null`;
        return undef unless $? == 0;
        chomp $r;
index 918a8de369113bb022f5080ac9c516771f544577..f10caedaa7a3970628148258ed8dc9f6a5e8c827 100644 (file)
@@ -254,7 +254,7 @@ $(ALL_MSGFILES): %.msg : %.po
 lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS
        $(QUIET_INDEX)if echo \
          $(foreach p,$(PRELOAD_FILES),source $p\;) \
-         auto_mkindex lib '*.tcl' \
+         auto_mkindex lib $(patsubst lib/%,%,$(sort $(ALL_LIBFILES))) \
        | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \
        else \
         echo >&2 "    * $(TCL_PATH) failed; using unoptimized loading"; \
index 83620b7cbc3a194fb17d395c17c10c9dd7f1683f..75ea965dacd71289dd6b965a51f40bb3c1298f5b 100644 (file)
@@ -25,6 +25,8 @@ You are currently in the middle of a merge that has not been fully completed.  Y
        set msg {}
        set parents [list]
        if {[catch {
+                       set name ""
+                       set email ""
                        set fd [git_read cat-file commit $curHEAD]
                        fconfigure $fd -encoding binary -translation lf
                        # By default commits are assumed to be in utf-8
@@ -34,9 +36,7 @@ You are currently in the middle of a merge that has not been fully completed.  Y
                                        lappend parents [string range $line 7 end]
                                } elseif {[string match {encoding *} $line]} {
                                        set enc [string tolower [string range $line 9 end]]
-                               } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} {
-                                       set commit_author [list name $name email $email date $time]
-                               }
+                               } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} { }
                        }
                        set msg [read $fd]
                        close $fd
@@ -44,7 +44,13 @@ You are currently in the middle of a merge that has not been fully completed.  Y
                        set enc [tcl_encoding $enc]
                        if {$enc ne {}} {
                                set msg [encoding convertfrom $enc $msg]
+                               set name [encoding convertfrom $enc $name]
+                               set email [encoding convertfrom $enc $email]
                        }
+                       if {$name ne {} && $email ne {}} {
+                               set commit_author [list name $name email $email date $time]
+                       }
+
                        set msg [string trim $msg]
                } err]} {
                error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
index 437815669f00b5b6639d48364be1e3c21a63696b..e3f5a0abf3c76c6e6b1e4528a20fca4c8784894b 100644 (file)
@@ -722,27 +722,6 @@ collapse_todo_ids() {
        git rebase--helper --shorten-ids
 }
 
-# Add commands after a pick or after a squash/fixup series
-# in the todo list.
-add_exec_commands () {
-       {
-               first=t
-               while read -r insn rest
-               do
-                       case $insn in
-                       pick)
-                               test -n "$first" ||
-                               printf "%s" "$cmd"
-                               ;;
-                       esac
-                       printf "%s %s\n" "$insn" "$rest"
-                       first=
-               done
-               printf "%s" "$cmd"
-       } <"$1" >"$1.new" &&
-       mv "$1.new" "$1"
-}
-
 # Switch to the branch in $into and notify it in the reflog
 checkout_onto () {
        GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
@@ -982,7 +961,7 @@ fi
 
 test -s "$todo" || echo noop >> "$todo"
 test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
-test -n "$cmd" && add_exec_commands "$todo"
+test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
 
 todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
 todocount=${todocount##* }
index aabbf6b69e5396080234a08e03cd951f9fe5d964..fd72a35c65b43537b292445b87ffb7e682cc076a 100755 (executable)
@@ -9,7 +9,7 @@ OPTIONS_STUCKLONG=t
 OPTIONS_SPEC="\
 git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
 git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
-git-rebase --continue | --abort | --skip | --edit-todo
+git rebase --continue | --abort | --skip | --edit-todo
 --
  Available options are
 v,verbose!         display a diffstat of what changed upstream
@@ -216,7 +216,7 @@ run_pre_rebase_hook () {
 }
 
 test -f "$apply_dir"/applying &&
-       die "$(gettext "It looks like git-am is in progress. Cannot rebase.")"
+       die "$(gettext "It looks like 'git am' is in progress. Cannot rebase.")"
 
 if test -d "$apply_dir"
 then
@@ -477,7 +477,7 @@ then
                ;;
        esac
        upstream=$(peel_committish "${upstream_name}") ||
-       die "$(eval_gettext "invalid upstream \$upstream_name")"
+       die "$(eval_gettext "invalid upstream '\$upstream_name'")"
        upstream_arg="$upstream_name"
 else
        if test -z "$onto"
@@ -518,7 +518,7 @@ case "$onto_name" in
 esac
 
 # If the branch to rebase is given, that is the branch we will rebase
-# $branch_name -- branch being rebased, or HEAD (already detached)
+# $branch_name -- branch/commit being rebased, or HEAD (already detached)
 # $orig_head -- commit object name of tip of the branch before rebasing
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
@@ -528,15 +528,18 @@ case "$#" in
        branch_name="$1"
        switch_to="$1"
 
-       if git show-ref --verify --quiet -- "refs/heads/$1" &&
-          orig_head=$(git rev-parse -q --verify "refs/heads/$1")
+       # Is it a local branch?
+       if git show-ref --verify --quiet -- "refs/heads/$branch_name" &&
+          orig_head=$(git rev-parse -q --verify "refs/heads/$branch_name")
        then
-               head_name="refs/heads/$1"
-       elif orig_head=$(git rev-parse -q --verify "$1")
+               head_name="refs/heads/$branch_name"
+       # If not is it a valid ref (branch or commit)?
+       elif orig_head=$(git rev-parse -q --verify "$branch_name")
        then
                head_name="detached HEAD"
+
        else
-               die "$(eval_gettext "fatal: no such branch: \$branch_name")"
+               die "$(eval_gettext "fatal: no such branch/commit '\$branch_name'")"
        fi
        ;;
 0)
@@ -547,7 +550,7 @@ case "$#" in
                branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)')
        else
                head_name="detached HEAD"
-               branch_name=HEAD ;# detached
+               branch_name=HEAD
        fi
        orig_head=$(git rev-parse --verify HEAD) || exit
        ;;
@@ -598,11 +601,23 @@ then
                test -z "$switch_to" ||
                GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \
                        git checkout -q "$switch_to" --
-               say "$(eval_gettext "Current branch \$branch_name is up to date.")"
+               if test "$branch_name" = "HEAD" &&
+                        ! git symbolic-ref -q HEAD
+               then
+                       say "$(eval_gettext "HEAD is up to date.")"
+               else
+                       say "$(eval_gettext "Current branch \$branch_name is up to date.")"
+               fi
                finish_rebase
                exit 0
        else
-               say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")"
+               if test "$branch_name" = "HEAD" &&
+                        ! git symbolic-ref -q HEAD
+               then
+                       say "$(eval_gettext "HEAD is up to date, rebase forced.")"
+               else
+                       say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")"
+               fi
        fi
 fi
 
index 2208dcc2139be7130749fd3bae462320f17d3b21..edcc6d34692b28575d9728c25a407782310a39fb 100755 (executable)
@@ -885,7 +885,9 @@ sub expand_one_alias {
 }
 
 if (!defined $smtp_server) {
-       foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
+       my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );
+       push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};
+       foreach (@sendmail_paths) {
                if (-x $_) {
                        $smtp_server = $_;
                        last;
index 8b2ce9afdab6d67e8b9af5fc97a30dc6941323f2..1114005ce2829011cde2e6de608a2b90df355fc9 100755 (executable)
@@ -76,6 +76,12 @@ create_stash () {
                        shift
                        stash_msg=${1?"BUG: create_stash () -m requires an argument"}
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                -u|--include-untracked)
                        shift
                        untracked=${1?"BUG: create_stash () -u requires an argument"}
@@ -193,6 +199,12 @@ store_stash () {
                        shift
                        stash_msg="$1"
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                -q|--quiet)
                        quiet=t
                        ;;
@@ -251,6 +263,12 @@ push_stash () {
                        test -z ${1+x} && usage
                        stash_msg=$1
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                --help)
                        show_help
                        ;;
@@ -260,18 +278,7 @@ push_stash () {
                        ;;
                -*)
                        option="$1"
-                       # TRANSLATORS: $option is an invalid option, like
-                       # `--blah-blah'. The 7 spaces at the beginning of the
-                       # second line correspond to "error: ". So you should line
-                       # up the second line with however many characters the
-                       # translation of "error: " takes in your language. E.g. in
-                       # English this is:
-                       #
-                       #    $ git stash save --blah-blah 2>&1 | head -n 2
-                       #    error: unknown option for 'stash save': --blah-blah
-                       #           To provide a message, use git stash save -- '--blah-blah'
-                       eval_gettextln "error: unknown option for 'stash save': \$option
-       To provide a message, use git stash save -- '\$option'"
+                       eval_gettextln "error: unknown option for 'stash push': \$option"
                        usage
                        ;;
                *)
index 66d1ae8ef6c5f12c856a2f88eabce515dcafd489..156255a9e56e7a5930f3f652ee38994064db03ea 100755 (executable)
@@ -758,18 +758,6 @@ cmd_update()
        }
 }
 
-set_name_rev () {
-       revname=$( (
-               sanitize_submodule_env
-               cd "$1" && {
-                       git describe "$2" 2>/dev/null ||
-                       git describe --tags "$2" 2>/dev/null ||
-                       git describe --contains "$2" 2>/dev/null ||
-                       git describe --all --always "$2"
-               }
-       ) )
-       test -z "$revname" || revname=" ($revname)"
-}
 #
 # Show commit summary for submodules in index or working tree
 #
@@ -1016,54 +1004,7 @@ cmd_status()
                shift
        done
 
-       {
-               git submodule--helper list --prefix "$wt_prefix" "$@" ||
-               echo "#unmatched" $?
-       } |
-       while read -r mode sha1 stage sm_path
-       do
-               die_if_unmatched "$mode" "$sha1"
-               name=$(git submodule--helper name "$sm_path") || exit
-               displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-               if test "$stage" = U
-               then
-                       say "U$sha1 $displaypath"
-                       continue
-               fi
-               if ! git submodule--helper is-active "$sm_path" ||
-               {
-                       ! test -d "$sm_path"/.git &&
-                       ! test -f "$sm_path"/.git
-               }
-               then
-                       say "-$sha1 $displaypath"
-                       continue;
-               fi
-               if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
-               then
-                       set_name_rev "$sm_path" "$sha1"
-                       say " $sha1 $displaypath$revname"
-               else
-                       if test -z "$cached"
-                       then
-                               sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD)
-                       fi
-                       set_name_rev "$sm_path" "$sha1"
-                       say "+$sha1 $displaypath$revname"
-               fi
-
-               if test -n "$recursive"
-               then
-                       (
-                               prefix="$displaypath/"
-                               sanitize_submodule_env
-                               wt_prefix=
-                               cd "$sm_path" &&
-                               eval cmd_status
-                       ) ||
-                       die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
-               fi
-       done
+       git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper status ${GIT_QUIET:+--quiet} ${cached:+--cached} ${recursive:+--recursive} "$@"
 }
 #
 # Sync remote urls for submodules
index d2404184ba2edc1ac330898ca610ad5e5df8d6c9..aa242d4f4f4363736730ccfcb4c2694d5be9a670 100755 (executable)
@@ -1865,6 +1865,7 @@ sub get_commit_entry {
                        }
                }
                $msgbuf =~ s/\s+$//s;
+               $msgbuf =~ s/\r\n/\n/sg; # SVN 1.6+ disallows CRLF
                if ($Git::SVN::_add_author_from && defined($author)
                    && !$saw_from) {
                        $msgbuf .= "\n\nFrom: $author";
diff --git a/git.c b/git.c
index 9e96dd409017c643e8a9bebbc41281728978b0b8..c870b9719c21b2db23ea7ab5d56fdeda483cb02e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -372,7 +372,7 @@ static struct cmd_struct commands[] = {
        { "archive", cmd_archive, RUN_SETUP_GENTLY },
        { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
        { "blame", cmd_blame, RUN_SETUP },
-       { "branch", cmd_branch, RUN_SETUP },
+       { "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
        { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
        { "cat-file", cmd_cat_file, RUN_SETUP },
        { "check-attr", cmd_check_attr, RUN_SETUP },
index 959f04b494e610258488867aa7de013a3477e5fc..2417057f2bc61a98e68dc6c817e456a21bf6044e 100755 (executable)
@@ -10,6 +10,8 @@
 use 5.008;
 use strict;
 use warnings;
+# handle ACL in file access tests
+use filetest 'access';
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser set_message);
diff --git a/grep.c b/grep.c
index d0b9b6cdfa74537ed67dcfef4ae486466d7185f8..3d7cd0e96f1ee160a66dd500c58d4026bf24e34c 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -477,6 +477,8 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
        int options = PCRE2_MULTILINE;
        const uint8_t *character_tables = NULL;
        int jitret;
+       int patinforet;
+       size_t jitsizearg;
 
        assert(opt->pcre2);
 
@@ -511,6 +513,30 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
                jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE);
                if (jitret)
                        die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret);
+
+               /*
+                * The pcre2_config(PCRE2_CONFIG_JIT, ...) call just
+                * tells us whether the library itself supports JIT,
+                * but to see whether we're going to be actually using
+                * JIT we need to extract PCRE2_INFO_JITSIZE from the
+                * pattern *after* we do pcre2_jit_compile() above.
+                *
+                * This is because if the pattern contains the
+                * (*NO_JIT) verb (see pcre2syntax(3))
+                * pcre2_jit_compile() will exit early with 0. If we
+                * then proceed to call pcre2_jit_match() further down
+                * the line instead of pcre2_match() we'll either
+                * segfault (pre PCRE 10.31) or run into a fatal error
+                * (post PCRE2 10.31)
+                */
+               patinforet = pcre2_pattern_info(p->pcre2_pattern, PCRE2_INFO_JITSIZE, &jitsizearg);
+               if (patinforet)
+                       BUG("pcre2_pattern_info() failed: %d", patinforet);
+               if (jitsizearg == 0) {
+                       p->pcre2_jit_on = 0;
+                       return;
+               }
+
                p->pcre2_jit_stack = pcre2_jit_stack_create(1, 1024 * 1024, NULL);
                if (!p->pcre2_jit_stack)
                        die("Couldn't allocate PCRE2 JIT stack");
@@ -1476,31 +1502,52 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
        }
 }
 
+static int is_empty_line(const char *bol, const char *eol);
+
 static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
                             char *bol, char *end, unsigned lno)
 {
-       unsigned cur = lno, from = 1, funcname_lno = 0;
-       int funcname_needed = !!opt->funcname;
-
-       if (opt->funcbody && !match_funcname(opt, gs, bol, end))
-               funcname_needed = 2;
+       unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
+       int funcname_needed = !!opt->funcname, comment_needed = 0;
 
        if (opt->pre_context < lno)
                from = lno - opt->pre_context;
        if (from <= opt->last_shown)
                from = opt->last_shown + 1;
+       orig_from = from;
+       if (opt->funcbody) {
+               if (match_funcname(opt, gs, bol, end))
+                       comment_needed = 1;
+               else
+                       funcname_needed = 1;
+               from = opt->last_shown + 1;
+       }
 
        /* Rewind. */
-       while (bol > gs->buf &&
-              cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
+       while (bol > gs->buf && cur > from) {
+               char *next_bol = bol;
                char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                cur--;
+               if (comment_needed && (is_empty_line(bol, eol) ||
+                                      match_funcname(opt, gs, bol, eol))) {
+                       comment_needed = 0;
+                       from = orig_from;
+                       if (cur < from) {
+                               cur++;
+                               bol = next_bol;
+                               break;
+                       }
+               }
                if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
                        funcname_lno = cur;
                        funcname_needed = 0;
+                       if (opt->funcbody)
+                               comment_needed = 1;
+                       else
+                               from = orig_from;
                }
        }
 
diff --git a/hash.h b/hash.h
index 024d0d3d50b174aac1d948e9ecfa7ae92020355a..7d7a864f5dd43bb53ee6b31d391df3e15cbdbb08 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -1,6 +1,8 @@
 #ifndef HASH_H
 #define HASH_H
 
+#include "git-compat-util.h"
+
 #if defined(SHA1_PPC)
 #include "ppc/sha1.h"
 #elif defined(SHA1_APPLE)
 #include "block-sha1/sha1.h"
 #endif
 
+/*
+ * Note that these constants are suitable for indexing the hash_algos array and
+ * comparing against each other, but are otherwise arbitrary, so they should not
+ * be exposed to the user or serialized to disk.  To know whether a
+ * git_hash_algo struct points to some usable hash function, test the format_id
+ * field for being non-zero.  Use the name field for user-visible situations and
+ * the format_id field for fixed-length fields on disk.
+ */
+/* An unknown hash function. */
+#define GIT_HASH_UNKNOWN 0
+/* SHA-1 */
+#define GIT_HASH_SHA1 1
+/* Number of algorithms supported (including unknown). */
+#define GIT_HASH_NALGOS (GIT_HASH_SHA1 + 1)
+
+typedef void (*git_hash_init_fn)(void *ctx);
+typedef void (*git_hash_update_fn)(void *ctx, const void *in, size_t len);
+typedef void (*git_hash_final_fn)(unsigned char *hash, void *ctx);
+
+struct git_hash_algo {
+       /*
+        * The name of the algorithm, as appears in the config file and in
+        * messages.
+        */
+       const char *name;
+
+       /* A four-byte version identifier, used in pack indices. */
+       uint32_t format_id;
+
+       /* The size of a hash context (e.g. git_SHA_CTX). */
+       size_t ctxsz;
+
+       /* The length of the hash in binary. */
+       size_t rawsz;
+
+       /* The length of the hash in hex characters. */
+       size_t hexsz;
+
+       /* The hash initialization function. */
+       git_hash_init_fn init_fn;
+
+       /* The hash update function. */
+       git_hash_update_fn update_fn;
+
+       /* The hash finalization function. */
+       git_hash_final_fn final_fn;
+
+       /* The OID of the empty tree. */
+       const struct object_id *empty_tree;
+
+       /* The OID of the empty blob. */
+       const struct object_id *empty_blob;
+};
+extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
+
 #endif
index 7cb29a6aede8e24320555e1845f8e4eb2032ade9..7ce79f3f72c144063b56f44368ad00c34e6aa786 100644 (file)
--- a/hashmap.h
+++ b/hashmap.h
  *
  * #define COMPARE_VALUE 1
  *
- * static int long2string_cmp(const struct long2string *e1,
+ * static int long2string_cmp(const void *hashmap_cmp_fn_data,
+ *                            const struct long2string *e1,
  *                            const struct long2string *e2,
- *                            const void *keydata, const void *userdata)
+ *                            const void *keydata)
  * {
- *     char *string = keydata;
- *     unsigned *flags = (unsigned*)userdata;
+ *     const char *string = keydata;
+ *     unsigned flags = *(unsigned *)hashmap_cmp_fn_data;
  *
  *     if (flags & COMPARE_VALUE)
- *         return !(e1->key == e2->key) || (keydata ?
- *                  strcmp(e1->value, keydata) : strcmp(e1->value, e2->value));
+ *         return e1->key != e2->key ||
+ *                  strcmp(e1->value, string ? string : e2->value);
  *     else
- *         return !(e1->key == e2->key);
+ *         return e1->key != e2->key;
  * }
  *
  * int main(int argc, char **argv)
  * {
  *     long key;
- *     char *value, *action;
- *
- *     unsigned flags = ALLOW_DUPLICATE_KEYS;
+ *     char value[255], action[32];
+ *     unsigned flags = 0;
  *
  *     hashmap_init(&map, (hashmap_cmp_fn) long2string_cmp, &flags, 0);
  *
- *     while (scanf("%s %l %s", action, key, value)) {
+ *     while (scanf("%s %ld %s", action, &key, value)) {
  *
  *         if (!strcmp("add", action)) {
  *             struct long2string *e;
- *             e = malloc(sizeof(struct long2string) + strlen(value));
+ *             FLEX_ALLOC_STR(e, value, value);
  *             hashmap_entry_init(e, memhash(&key, sizeof(long)));
  *             e->key = key;
- *             memcpy(e->value, value, strlen(value));
  *             hashmap_add(&map, e);
  *         }
  *
  *         if (!strcmp("print_all_by_key", action)) {
- *             flags &= ~COMPARE_VALUE;
- *
- *             struct long2string k;
+ *             struct long2string k, *e;
  *             hashmap_entry_init(&k, memhash(&key, sizeof(long)));
  *             k.key = key;
  *
- *             struct long2string *e = hashmap_get(&map, &k, NULL);
+ *             flags &= ~COMPARE_VALUE;
+ *             e = hashmap_get(&map, &k, NULL);
  *             if (e) {
- *                 printf("first: %l %s\n", e->key, e->value);
- *                 while (e = hashmap_get_next(&map, e))
- *                     printf("found more: %l %s\n", e->key, e->value);
+ *                 printf("first: %ld %s\n", e->key, e->value);
+ *                 while ((e = hashmap_get_next(&map, e)))
+ *                     printf("found more: %ld %s\n", e->key, e->value);
  *             }
  *         }
  *
  *         if (!strcmp("has_exact_match", action)) {
- *             flags |= COMPARE_VALUE;
- *
  *             struct long2string *e;
- *             e = malloc(sizeof(struct long2string) + strlen(value));
+ *             FLEX_ALLOC_STR(e, value, value);
  *             hashmap_entry_init(e, memhash(&key, sizeof(long)));
  *             e->key = key;
- *             memcpy(e->value, value, strlen(value));
  *
- *             printf("%s found\n", hashmap_get(&map, e, NULL) ? "" : "not");
+ *             flags |= COMPARE_VALUE;
+ *             printf("%sfound\n", hashmap_get(&map, e, NULL) ? "" : "not ");
+ *             free(e);
  *         }
  *
  *         if (!strcmp("has_exact_match_no_heap_alloc", action)) {
- *             flags |= COMPARE_VALUE;
- *
- *             struct long2string e;
- *             hashmap_entry_init(e, memhash(&key, sizeof(long)));
- *             e.key = key;
+ *             struct long2string k;
+ *             hashmap_entry_init(&k, memhash(&key, sizeof(long)));
+ *             k.key = key;
  *
- *             printf("%s found\n", hashmap_get(&map, e, value) ? "" : "not");
+ *             flags |= COMPARE_VALUE;
+ *             printf("%sfound\n", hashmap_get(&map, &k, value) ? "" : "not ");
  *         }
  *
  *         if (!strcmp("end", action)) {
@@ -94,6 +90,8 @@
  *             break;
  *         }
  *     }
+ *
+ *     return 0;
  * }
  */
 
diff --git a/help.c b/help.c
index 88a3aeaeb9f0b3983c9cd035fd371133506a54ec..60071a9beaaedf45730385b7fc1443b402c50156 100644 (file)
--- a/help.c
+++ b/help.c
@@ -412,6 +412,12 @@ int cmd_version(int argc, const char **argv, const char *prefix)
        printf("git version %s\n", git_version_string);
 
        if (build_options) {
+               printf("cpu: %s\n", GIT_HOST_CPU);
+               if (git_built_from_commit_string[0])
+                       printf("built from commit: %s\n",
+                              git_built_from_commit_string);
+               else
+                       printf("no commit associated with this build\n");
                printf("sizeof-long: %d\n", (int)sizeof(long));
                /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
        }
diff --git a/hex.c b/hex.c
index 28b44118cbf2c75b26d017a34ecfdf65dbf4c7eb..8df2d63728f0405a9b1dc70aa404954dd921b6bc 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -35,6 +35,18 @@ const signed char hexval_table[256] = {
         -1, -1, -1, -1, -1, -1, -1, -1,                /* f8-ff */
 };
 
+int hex_to_bytes(unsigned char *binary, const char *hex, size_t len)
+{
+       for (; len; len--, hex += 2) {
+               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+
+               if (val & ~0xff)
+                       return -1;
+               *binary++ = val;
+       }
+       return 0;
+}
+
 int get_sha1_hex(const char *hex, unsigned char *sha1)
 {
        int i;
index 493ee7d719d488f25cd6c45bb4c7ce1d5a75977b..14435ab65d1bbe3fc73be6a6d774b6ab6ffc8360 100644 (file)
@@ -1007,20 +1007,18 @@ static void remote_ls(const char *path, int flags,
                      void (*userFunc)(struct remote_ls_ctx *ls),
                      void *userData);
 
-/* extract hex from sharded "xx/x{40}" filename */
+/* extract hex from sharded "xx/x{38}" filename */
 static int get_oid_hex_from_objpath(const char *path, struct object_id *oid)
 {
-       char hex[GIT_MAX_HEXSZ];
-
        if (strlen(path) != GIT_SHA1_HEXSZ + 1)
                return -1;
 
-       memcpy(hex, path, 2);
+       if (hex_to_bytes(oid->hash, path, 1))
+               return -1;
        path += 2;
        path++; /* skip '/' */
-       memcpy(hex + 2, path, GIT_SHA1_HEXSZ - 2);
 
-       return get_oid_hex(hex, oid);
+       return hex_to_bytes(oid->hash + 1, path, GIT_SHA1_RAWSZ - 1);
 }
 
 static void process_ls_object(struct remote_ls_ctx *ls)
diff --git a/http.c b/http.c
index 713525f38ed3f254aed2b0dd65fece7972444072..32d33261c98cce2d5fd25c07a503654a3d5d2e52 100644 (file)
--- a/http.c
+++ b/http.c
@@ -12,6 +12,7 @@
 #include "gettext.h"
 #include "transport.h"
 #include "packfile.h"
+#include "protocol.h"
 
 static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
 #if LIBCURL_VERSION_NUM >= 0x070a08
@@ -864,6 +865,11 @@ static CURL *get_curl_handle(void)
                else if (starts_with(curl_http_proxy, "socks"))
                        curl_easy_setopt(result,
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x073400
+               else if (starts_with(curl_http_proxy, "https"))
+                       curl_easy_setopt(result,
+                               CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
 #endif
                if (strstr(curl_http_proxy, "://"))
                        credential_from_url(&proxy_auth, curl_http_proxy);
@@ -898,6 +904,21 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
+static void protocol_http_header(void)
+{
+       if (get_protocol_version_config() > 0) {
+               struct strbuf protocol_header = STRBUF_INIT;
+
+               strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d",
+                           get_protocol_version_config());
+
+
+               extra_http_headers = curl_slist_append(extra_http_headers,
+                                                      protocol_header.buf);
+               strbuf_release(&protocol_header);
+       }
+}
+
 void http_init(struct remote *remote, const char *url, int proactive_auth)
 {
        char *low_speed_limit;
@@ -928,6 +949,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        if (remote)
                var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
 
+       protocol_http_header();
+
        pragma_header = curl_slist_append(http_copy_default_headers(),
                "Pragma: no-cache");
        no_pragma_header = curl_slist_append(http_copy_default_headers(),
index 54e6a80fd64e16420a526461e90aed559e0bd1d6..36c7c1b4f6195b2d2f00013611a5d0aa96ecf408 100644 (file)
@@ -1412,6 +1412,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
 {
        CURL *curl;
        struct strbuf path = STRBUF_INIT;
+       char *uri_encoded_folder;
 
        if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
                die("curl_global_init failed");
@@ -1429,7 +1430,12 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
        strbuf_addstr(&path, server.host);
        if (!path.len || path.buf[path.len - 1] != '/')
                strbuf_addch(&path, '/');
-       strbuf_addstr(&path, server.folder);
+
+       uri_encoded_folder = curl_easy_escape(curl, server.folder, 0);
+       if (!uri_encoded_folder)
+               die("failed to encode server folder");
+       strbuf_addstr(&path, uri_encoded_folder);
+       curl_free(uri_encoded_folder);
 
        curl_easy_setopt(curl, CURLOPT_URL, path.buf);
        strbuf_release(&path);
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
new file mode 100644 (file)
index 0000000..4c5b34e
--- /dev/null
@@ -0,0 +1,92 @@
+#include "cache.h"
+#include "commit.h"
+#include "config.h"
+#include "revision.h"
+#include "argv-array.h"
+#include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
+
+/*
+ * Parse value of the argument to the "filter" keyword.
+ * On the command line this looks like:
+ *       --filter=<arg>
+ * and in the pack protocol as:
+ *       "filter" SP <arg>
+ *
+ * The filter keyword will be used by many commands.
+ * See Documentation/rev-list-options.txt for allowed values for <arg>.
+ *
+ * Capture the given arg as the "filter_spec".  This can be forwarded to
+ * subordinate commands when necessary.  We also "intern" the arg for
+ * the convenience of the current command.
+ */
+int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
+                             const char *arg)
+{
+       const char *v0;
+
+       if (filter_options->choice)
+               die(_("multiple object filter types cannot be combined"));
+
+       filter_options->filter_spec = strdup(arg);
+
+       if (!strcmp(arg, "blob:none")) {
+               filter_options->choice = LOFC_BLOB_NONE;
+               return 0;
+       }
+
+       if (skip_prefix(arg, "blob:limit=", &v0)) {
+               if (!git_parse_ulong(v0, &filter_options->blob_limit_value))
+                       die(_("invalid filter-spec expression '%s'"), arg);
+               filter_options->choice = LOFC_BLOB_LIMIT;
+               return 0;
+       }
+
+       if (skip_prefix(arg, "sparse:oid=", &v0)) {
+               struct object_context oc;
+               struct object_id sparse_oid;
+
+               /*
+                * Try to parse <oid-expression> into an OID for the current
+                * command, but DO NOT complain if we don't have the blob or
+                * ref locally.
+                */
+               if (!get_oid_with_context(v0, GET_OID_BLOB,
+                                         &sparse_oid, &oc))
+                       filter_options->sparse_oid_value = oiddup(&sparse_oid);
+               filter_options->choice = LOFC_SPARSE_OID;
+               return 0;
+       }
+
+       if (skip_prefix(arg, "sparse:path=", &v0)) {
+               filter_options->choice = LOFC_SPARSE_PATH;
+               filter_options->sparse_path_value = strdup(v0);
+               return 0;
+       }
+
+       die(_("invalid filter-spec expression '%s'"), arg);
+       return 0;
+}
+
+int opt_parse_list_objects_filter(const struct option *opt,
+                                 const char *arg, int unset)
+{
+       struct list_objects_filter_options *filter_options = opt->value;
+
+       if (unset || !arg) {
+               list_objects_filter_release(filter_options);
+               return 0;
+       }
+
+       return parse_list_objects_filter(filter_options, arg);
+}
+
+void list_objects_filter_release(
+       struct list_objects_filter_options *filter_options)
+{
+       free(filter_options->filter_spec);
+       free(filter_options->sparse_oid_value);
+       free(filter_options->sparse_path_value);
+       memset(filter_options, 0, sizeof(*filter_options));
+}
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h
new file mode 100644 (file)
index 0000000..eea44a1
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef LIST_OBJECTS_FILTER_OPTIONS_H
+#define LIST_OBJECTS_FILTER_OPTIONS_H
+
+#include "parse-options.h"
+
+/*
+ * The list of defined filters for list-objects.
+ */
+enum list_objects_filter_choice {
+       LOFC_DISABLED = 0,
+       LOFC_BLOB_NONE,
+       LOFC_BLOB_LIMIT,
+       LOFC_SPARSE_OID,
+       LOFC_SPARSE_PATH,
+       LOFC__COUNT /* must be last */
+};
+
+struct list_objects_filter_options {
+       /*
+        * 'filter_spec' is the raw argument value given on the command line
+        * or protocol request.  (The part after the "--keyword=".)  For
+        * commands that launch filtering sub-processes, this value should be
+        * passed to them as received by the current process.
+        */
+       char *filter_spec;
+
+       /*
+        * 'choice' is determined by parsing the filter-spec.  This indicates
+        * the filtering algorithm to use.
+        */
+       enum list_objects_filter_choice choice;
+
+       /*
+        * Parsed values (fields) from within the filter-spec.  These are
+        * choice-specific; not all values will be defined for any given
+        * choice.
+        */
+       struct object_id *sparse_oid_value;
+       char *sparse_path_value;
+       unsigned long blob_limit_value;
+};
+
+/* Normalized command line arguments */
+#define CL_ARG__FILTER "filter"
+
+int parse_list_objects_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg);
+
+int opt_parse_list_objects_filter(const struct option *opt,
+                                 const char *arg, int unset);
+
+#define OPT_PARSE_LIST_OBJECTS_FILTER(fo) \
+       { OPTION_CALLBACK, 0, CL_ARG__FILTER, fo, N_("args"), \
+         N_("object filtering"), 0, \
+         opt_parse_list_objects_filter }
+
+void list_objects_filter_release(
+       struct list_objects_filter_options *filter_options);
+
+#endif /* LIST_OBJECTS_FILTER_OPTIONS_H */
diff --git a/list-objects-filter.c b/list-objects-filter.c
new file mode 100644 (file)
index 0000000..4356c45
--- /dev/null
@@ -0,0 +1,401 @@
+#include "cache.h"
+#include "dir.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+#include "tree-walk.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
+#include "oidset.h"
+
+/* Remember to update object flag allocation in object.h */
+/*
+ * FILTER_SHOWN_BUT_REVISIT -- we set this bit on tree objects
+ * that have been shown, but should be revisited if they appear
+ * in the traversal (until we mark it SEEN).  This is a way to
+ * let us silently de-dup calls to show() in the caller.  This
+ * is subtly different from the "revision.h:SHOWN" and the
+ * "sha1_name.c:ONELINE_SEEN" bits.  And also different from
+ * the non-de-dup usage in pack-bitmap.c
+ */
+#define FILTER_SHOWN_BUT_REVISIT (1<<21)
+
+/*
+ * A filter for list-objects to omit ALL blobs from the traversal.
+ * And to OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_blobs_none_data {
+       struct oidset *omits;
+};
+
+static enum list_objects_filter_result filter_blobs_none(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data_)
+{
+       struct filter_blobs_none_data *filter_data = filter_data_;
+
+       switch (filter_situation) {
+       default:
+               die("unknown filter_situation");
+               return LOFR_ZERO;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+               /* always include all tree objects */
+               return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+       case LOFS_END_TREE:
+               assert(obj->type == OBJ_TREE);
+               return LOFR_ZERO;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+               assert((obj->flags & SEEN) == 0);
+
+               if (filter_data->omits)
+                       oidset_insert(filter_data->omits, &obj->oid);
+               return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
+       }
+}
+
+static void *filter_blobs_none__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+
+       *filter_fn = filter_blobs_none;
+       *filter_free_fn = free;
+       return d;
+}
+
+/*
+ * A filter for list-objects to omit large blobs.
+ * And to OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_blobs_limit_data {
+       struct oidset *omits;
+       unsigned long max_bytes;
+};
+
+static enum list_objects_filter_result filter_blobs_limit(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data_)
+{
+       struct filter_blobs_limit_data *filter_data = filter_data_;
+       unsigned long object_length;
+       enum object_type t;
+
+       switch (filter_situation) {
+       default:
+               die("unknown filter_situation");
+               return LOFR_ZERO;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+               /* always include all tree objects */
+               return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+       case LOFS_END_TREE:
+               assert(obj->type == OBJ_TREE);
+               return LOFR_ZERO;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+               assert((obj->flags & SEEN) == 0);
+
+               t = sha1_object_info(obj->oid.hash, &object_length);
+               if (t != OBJ_BLOB) { /* probably OBJ_NONE */
+                       /*
+                        * We DO NOT have the blob locally, so we cannot
+                        * apply the size filter criteria.  Be conservative
+                        * and force show it (and let the caller deal with
+                        * the ambiguity).
+                        */
+                       goto include_it;
+               }
+
+               if (object_length < filter_data->max_bytes)
+                       goto include_it;
+
+               if (filter_data->omits)
+                       oidset_insert(filter_data->omits, &obj->oid);
+               return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
+       }
+
+include_it:
+       if (filter_data->omits)
+               oidset_remove(filter_data->omits, &obj->oid);
+       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+}
+
+static void *filter_blobs_limit__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+       d->max_bytes = filter_options->blob_limit_value;
+
+       *filter_fn = filter_blobs_limit;
+       *filter_free_fn = free;
+       return d;
+}
+
+/*
+ * A filter driven by a sparse-checkout specification to only
+ * include blobs that a sparse checkout would populate.
+ *
+ * The sparse-checkout spec can be loaded from a blob with the
+ * given OID or from a local pathname.  We allow an OID because
+ * the repo may be bare or we may be doing the filtering on the
+ * server.
+ */
+struct frame {
+       /*
+        * defval is the usual default include/exclude value that
+        * should be inherited as we recurse into directories based
+        * upon pattern matching of the directory itself or of a
+        * containing directory.
+        */
+       int defval;
+
+       /*
+        * 1 if the directory (recursively) contains any provisionally
+        * omitted objects.
+        *
+        * 0 if everything (recursively) contained in this directory
+        * has been explicitly included (SHOWN) in the result and
+        * the directory may be short-cut later in the traversal.
+        */
+       unsigned child_prov_omit : 1;
+};
+
+struct filter_sparse_data {
+       struct oidset *omits;
+       struct exclude_list el;
+
+       size_t nr, alloc;
+       struct frame *array_frame;
+};
+
+static enum list_objects_filter_result filter_sparse(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data_)
+{
+       struct filter_sparse_data *filter_data = filter_data_;
+       int val, dtype;
+       struct frame *frame;
+
+       switch (filter_situation) {
+       default:
+               die("unknown filter_situation");
+               return LOFR_ZERO;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+               dtype = DT_DIR;
+               val = is_excluded_from_list(pathname, strlen(pathname),
+                                           filename, &dtype, &filter_data->el,
+                                           &the_index);
+               if (val < 0)
+                       val = filter_data->array_frame[filter_data->nr].defval;
+
+               ALLOC_GROW(filter_data->array_frame, filter_data->nr + 1,
+                          filter_data->alloc);
+               filter_data->nr++;
+               filter_data->array_frame[filter_data->nr].defval = val;
+               filter_data->array_frame[filter_data->nr].child_prov_omit = 0;
+
+               /*
+                * A directory with this tree OID may appear in multiple
+                * places in the tree. (Think of a directory move or copy,
+                * with no other changes, so the OID is the same, but the
+                * full pathnames of objects within this directory are new
+                * and may match is_excluded() patterns differently.)
+                * So we cannot mark this directory as SEEN (yet), since
+                * that will prevent process_tree() from revisiting this
+                * tree object with other pathname prefixes.
+                *
+                * Only _DO_SHOW the tree object the first time we visit
+                * this tree object.
+                *
+                * We always show all tree objects.  A future optimization
+                * may want to attempt to narrow this.
+                */
+               if (obj->flags & FILTER_SHOWN_BUT_REVISIT)
+                       return LOFR_ZERO;
+               obj->flags |= FILTER_SHOWN_BUT_REVISIT;
+               return LOFR_DO_SHOW;
+
+       case LOFS_END_TREE:
+               assert(obj->type == OBJ_TREE);
+               assert(filter_data->nr > 0);
+
+               frame = &filter_data->array_frame[filter_data->nr];
+               filter_data->nr--;
+
+               /*
+                * Tell our parent directory if any of our children were
+                * provisionally omitted.
+                */
+               filter_data->array_frame[filter_data->nr].child_prov_omit |=
+                       frame->child_prov_omit;
+
+               /*
+                * If there are NO provisionally omitted child objects (ALL child
+                * objects in this folder were INCLUDED), then we can mark the
+                * folder as SEEN (so we will not have to revisit it again).
+                */
+               if (!frame->child_prov_omit)
+                       return LOFR_MARK_SEEN;
+               return LOFR_ZERO;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+               assert((obj->flags & SEEN) == 0);
+
+               frame = &filter_data->array_frame[filter_data->nr];
+
+               dtype = DT_REG;
+               val = is_excluded_from_list(pathname, strlen(pathname),
+                                           filename, &dtype, &filter_data->el,
+                                           &the_index);
+               if (val < 0)
+                       val = frame->defval;
+               if (val > 0) {
+                       if (filter_data->omits)
+                               oidset_remove(filter_data->omits, &obj->oid);
+                       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+               }
+
+               /*
+                * Provisionally omit it.  We've already established that
+                * this pathname is not in the sparse-checkout specification
+                * with the CURRENT pathname, so we *WANT* to omit this blob.
+                *
+                * However, a pathname elsewhere in the tree may also
+                * reference this same blob, so we cannot reject it yet.
+                * Leave the LOFR_ bits unset so that if the blob appears
+                * again in the traversal, we will be asked again.
+                */
+               if (filter_data->omits)
+                       oidset_insert(filter_data->omits, &obj->oid);
+
+               /*
+                * Remember that at least 1 blob in this tree was
+                * provisionally omitted.  This prevents us from short
+                * cutting the tree in future iterations.
+                */
+               frame->child_prov_omit = 1;
+               return LOFR_ZERO;
+       }
+}
+
+
+static void filter_sparse_free(void *filter_data)
+{
+       struct filter_sparse_data *d = filter_data;
+       /* TODO free contents of 'd' */
+       free(d);
+}
+
+static void *filter_sparse_oid__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+       if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value,
+                                          NULL, 0, &d->el) < 0)
+               die("could not load filter specification");
+
+       ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
+       d->array_frame[d->nr].defval = 0; /* default to include */
+       d->array_frame[d->nr].child_prov_omit = 0;
+
+       *filter_fn = filter_sparse;
+       *filter_free_fn = filter_sparse_free;
+       return d;
+}
+
+static void *filter_sparse_path__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+       if (add_excludes_from_file_to_list(filter_options->sparse_path_value,
+                                          NULL, 0, &d->el, NULL) < 0)
+               die("could not load filter specification");
+
+       ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
+       d->array_frame[d->nr].defval = 0; /* default to include */
+       d->array_frame[d->nr].child_prov_omit = 0;
+
+       *filter_fn = filter_sparse;
+       *filter_free_fn = filter_sparse_free;
+       return d;
+}
+
+typedef void *(*filter_init_fn)(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn);
+
+/*
+ * Must match "enum list_objects_filter_choice".
+ */
+static filter_init_fn s_filters[] = {
+       NULL,
+       filter_blobs_none__init,
+       filter_blobs_limit__init,
+       filter_sparse_oid__init,
+       filter_sparse_path__init,
+};
+
+void *list_objects_filter__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       filter_init_fn init_fn;
+
+       assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
+
+       if (filter_options->choice >= LOFC__COUNT)
+               die("invalid list-objects filter choice: %d",
+                   filter_options->choice);
+
+       init_fn = s_filters[filter_options->choice];
+       if (init_fn)
+               return init_fn(omitted, filter_options,
+                              filter_fn, filter_free_fn);
+       *filter_fn = NULL;
+       *filter_free_fn = NULL;
+       return NULL;
+}
diff --git a/list-objects-filter.h b/list-objects-filter.h
new file mode 100644 (file)
index 0000000..a963d02
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef LIST_OBJECTS_FILTER_H
+#define LIST_OBJECTS_FILTER_H
+
+/*
+ * During list-object traversal we allow certain objects to be
+ * filtered (omitted) from the result.  The active filter uses
+ * these result values to guide list-objects.
+ *
+ * _ZERO      : Do nothing with the object at this time.  It may
+ *              be revisited if it appears in another place in
+ *              the tree or in another commit during the overall
+ *              traversal.
+ *
+ * _MARK_SEEN : Mark this object as "SEEN" in the object flags.
+ *              This will prevent it from being revisited during
+ *              the remainder of the traversal.  This DOES NOT
+ *              imply that it will be included in the results.
+ *
+ * _DO_SHOW   : Show this object in the results (call show() on it).
+ *              In general, objects should only be shown once, but
+ *              this result DOES NOT imply that we mark it SEEN.
+ *
+ * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW)
+ * but they can be used independently, such as when sparse-checkout
+ * pattern matching is being applied.
+ *
+ * A _MARK_SEEN without _DO_SHOW can be called a hard-omit -- the
+ * object is not shown and will never be reconsidered (unless a
+ * previous iteration has already shown it).
+ *
+ * A _DO_SHOW without _MARK_SEEN can be used, for example, to
+ * include a directory, but then revisit it to selectively include
+ * or omit objects within it.
+ *
+ * A _ZERO can be called a provisional-omit -- the object is NOT shown,
+ * but *may* be revisited (if the object appears again in the traversal).
+ * Therefore, it will be omitted from the results *unless* a later
+ * iteration causes it to be shown.
+ */
+enum list_objects_filter_result {
+       LOFR_ZERO      = 0,
+       LOFR_MARK_SEEN = 1<<0,
+       LOFR_DO_SHOW   = 1<<1,
+};
+
+enum list_objects_filter_situation {
+       LOFS_BEGIN_TREE,
+       LOFS_END_TREE,
+       LOFS_BLOB
+};
+
+typedef enum list_objects_filter_result (*filter_object_fn)(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data);
+
+typedef void (*filter_free_fn)(void *filter_data);
+
+/*
+ * Constructor for the set of defined list-objects filters.
+ * Returns a generic "void *filter_data".
+ *
+ * The returned "filter_fn" will be used by traverse_commit_list()
+ * to filter the results.
+ *
+ * The returned "filter_free_fn" is a destructor for the
+ * filter_data.
+ */
+void *list_objects_filter__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn);
+
+#endif /* LIST_OBJECTS_FILTER_H */
index b3931fa434dc99a481569697585e3bb21190c932..0966cdc9fa8dd448aa9955a2fb5324ae8ebbf97d 100644 (file)
@@ -7,16 +7,21 @@
 #include "tree-walk.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
 
 static void process_blob(struct rev_info *revs,
                         struct blob *blob,
                         show_object_fn show,
                         struct strbuf *path,
                         const char *name,
-                        void *cb_data)
+                        void *cb_data,
+                        filter_object_fn filter_fn,
+                        void *filter_data)
 {
        struct object *obj = &blob->object;
        size_t pathlen;
+       enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
 
        if (!revs->blob_objects)
                return;
@@ -24,11 +29,17 @@ static void process_blob(struct rev_info *revs,
                die("bad blob object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
-       obj->flags |= SEEN;
 
        pathlen = path->len;
        strbuf_addstr(path, name);
-       show(obj, path->buf, cb_data);
+       if (filter_fn)
+               r = filter_fn(LOFS_BLOB, obj,
+                             path->buf, &path->buf[pathlen],
+                             filter_data);
+       if (r & LOFR_MARK_SEEN)
+               obj->flags |= SEEN;
+       if (r & LOFR_DO_SHOW)
+               show(obj, path->buf, cb_data);
        strbuf_setlen(path, pathlen);
 }
 
@@ -69,7 +80,9 @@ static void process_tree(struct rev_info *revs,
                         show_object_fn show,
                         struct strbuf *base,
                         const char *name,
-                        void *cb_data)
+                        void *cb_data,
+                        filter_object_fn filter_fn,
+                        void *filter_data)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
@@ -77,6 +90,7 @@ static void process_tree(struct rev_info *revs,
        enum interesting match = revs->diffopt.pathspec.nr == 0 ?
                all_entries_interesting: entry_not_interesting;
        int baselen = base->len;
+       enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
 
        if (!revs->tree_objects)
                return;
@@ -90,9 +104,15 @@ static void process_tree(struct rev_info *revs,
                die("bad tree object %s", oid_to_hex(&obj->oid));
        }
 
-       obj->flags |= SEEN;
        strbuf_addstr(base, name);
-       show(obj, base->buf, cb_data);
+       if (filter_fn)
+               r = filter_fn(LOFS_BEGIN_TREE, obj,
+                             base->buf, &base->buf[baselen],
+                             filter_data);
+       if (r & LOFR_MARK_SEEN)
+               obj->flags |= SEEN;
+       if (r & LOFR_DO_SHOW)
+               show(obj, base->buf, cb_data);
        if (base->len)
                strbuf_addch(base, '/');
 
@@ -112,7 +132,7 @@ static void process_tree(struct rev_info *revs,
                        process_tree(revs,
                                     lookup_tree(entry.oid),
                                     show, base, entry.path,
-                                    cb_data);
+                                    cb_data, filter_fn, filter_data);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.oid->hash,
                                        show, base, entry.path,
@@ -121,8 +141,19 @@ static void process_tree(struct rev_info *revs,
                        process_blob(revs,
                                     lookup_blob(entry.oid),
                                     show, base, entry.path,
-                                    cb_data);
+                                    cb_data, filter_fn, filter_data);
        }
+
+       if (filter_fn) {
+               r = filter_fn(LOFS_END_TREE, obj,
+                             base->buf, &base->buf[baselen],
+                             filter_data);
+               if (r & LOFR_MARK_SEEN)
+                       obj->flags |= SEEN;
+               if (r & LOFR_DO_SHOW)
+                       show(obj, base->buf, cb_data);
+       }
+
        strbuf_setlen(base, baselen);
        free_tree_buffer(tree);
 }
@@ -183,25 +214,17 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
        add_pending_object(revs, &tree->object, "");
 }
 
-void traverse_commit_list(struct rev_info *revs,
-                         show_commit_fn show_commit,
-                         show_object_fn show_object,
-                         void *data)
+static void traverse_trees_and_blobs(struct rev_info *revs,
+                                    struct strbuf *base,
+                                    show_object_fn show_object,
+                                    void *show_data,
+                                    filter_object_fn filter_fn,
+                                    void *filter_data)
 {
        int i;
-       struct commit *commit;
-       struct strbuf base;
 
-       strbuf_init(&base, PATH_MAX);
-       while ((commit = get_revision(revs)) != NULL) {
-               /*
-                * an uninteresting boundary commit may not have its tree
-                * parsed yet, but we are not going to show them anyway
-                */
-               if (commit->tree)
-                       add_pending_tree(revs, commit->tree);
-               show_commit(commit, data);
-       }
+       assert(base->len == 0);
+
        for (i = 0; i < revs->pending.nr; i++) {
                struct object_array_entry *pending = revs->pending.objects + i;
                struct object *obj = pending->item;
@@ -211,24 +234,89 @@ void traverse_commit_list(struct rev_info *revs,
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       show_object(obj, name, data);
+                       show_object(obj, name, show_data);
                        continue;
                }
                if (!path)
                        path = "";
                if (obj->type == OBJ_TREE) {
                        process_tree(revs, (struct tree *)obj, show_object,
-                                    &base, path, data);
+                                    base, path, show_data,
+                                    filter_fn, filter_data);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
                        process_blob(revs, (struct blob *)obj, show_object,
-                                    &base, path, data);
+                                    base, path, show_data,
+                                    filter_fn, filter_data);
                        continue;
                }
                die("unknown pending object %s (%s)",
                    oid_to_hex(&obj->oid), name);
        }
        object_array_clear(&revs->pending);
-       strbuf_release(&base);
+}
+
+static void do_traverse(struct rev_info *revs,
+                       show_commit_fn show_commit,
+                       show_object_fn show_object,
+                       void *show_data,
+                       filter_object_fn filter_fn,
+                       void *filter_data)
+{
+       struct commit *commit;
+       struct strbuf csp; /* callee's scratch pad */
+       strbuf_init(&csp, PATH_MAX);
+
+       while ((commit = get_revision(revs)) != NULL) {
+               /*
+                * an uninteresting boundary commit may not have its tree
+                * parsed yet, but we are not going to show them anyway
+                */
+               if (commit->tree)
+                       add_pending_tree(revs, commit->tree);
+               show_commit(commit, show_data);
+
+               if (revs->tree_blobs_in_commit_order)
+                       /*
+                        * NEEDSWORK: Adding the tree and then flushing it here
+                        * needs a reallocation for each commit. Can we pass the
+                        * tree directory without allocation churn?
+                        */
+                       traverse_trees_and_blobs(revs, &csp,
+                                                show_object, show_data,
+                                                filter_fn, filter_data);
+       }
+       traverse_trees_and_blobs(revs, &csp,
+                                show_object, show_data,
+                                filter_fn, filter_data);
+       strbuf_release(&csp);
+}
+
+void traverse_commit_list(struct rev_info *revs,
+                         show_commit_fn show_commit,
+                         show_object_fn show_object,
+                         void *show_data)
+{
+       do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
+}
+
+void traverse_commit_list_filtered(
+       struct list_objects_filter_options *filter_options,
+       struct rev_info *revs,
+       show_commit_fn show_commit,
+       show_object_fn show_object,
+       void *show_data,
+       struct oidset *omitted)
+{
+       filter_object_fn filter_fn = NULL;
+       filter_free_fn filter_free_fn = NULL;
+       void *filter_data = NULL;
+
+       filter_data = list_objects_filter__init(omitted, filter_options,
+                                               &filter_fn, &filter_free_fn);
+       do_traverse(revs, show_commit, show_object, show_data,
+                   filter_fn, filter_data);
+       if (filter_data && filter_free_fn)
+               filter_free_fn(filter_data);
 }
index 0cebf8585cb179ae46fd8238eaf2b2162739bbaa..aa618d7f4579b2ec9c4e506f821caa1fc3fb2b37 100644 (file)
@@ -8,4 +8,15 @@ void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, voi
 typedef void (*show_edge_fn)(struct commit *);
 void mark_edges_uninteresting(struct rev_info *, show_edge_fn);
 
-#endif
+struct oidset;
+struct list_objects_filter_options;
+
+void traverse_commit_list_filtered(
+       struct list_objects_filter_options *filter_options,
+       struct rev_info *revs,
+       show_commit_fn show_commit,
+       show_object_fn show_object,
+       void *show_data,
+       struct oidset *omitted);
+
+#endif /* LIST_OBJECTS_H */
index 7c1c484d7cfdfab126fd0ce947ac40f650e5d107..f401c979f0ed907b40bfb509ab8f06ad2632c19e 100644 (file)
@@ -240,8 +240,8 @@ extern char *get_locked_file_path(struct lock_file *lk);
  * If the lockfile is still open, close it (and the file pointer if it
  * has been opened using `fdopen_lock_file()`) without renaming the
  * lockfile over the file being locked. Return 0 upon success. On
- * failure to `close(2)`, return a negative value and roll back the
- * lock file. Usually `commit_lock_file()`, `commit_lock_file_to()`,
+ * failure to `close(2)`, return a negative value (the lockfile is not
+ * rolled back). Usually `commit_lock_file()`, `commit_lock_file_to()`,
  * or `rollback_lock_file()` should eventually be called.
  */
 static inline int close_lock_file_gently(struct lock_file *lk)
index 580b3a98a03df8b1920b3f729c783b79ed781164..fca29d4799da27164394a8c59da067a0522241fe 100644 (file)
@@ -94,8 +94,12 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid,
 {
        struct object *obj;
        enum decoration_type type = DECORATION_NONE;
+       struct decoration_filter *filter = (struct decoration_filter *)cb_data;
 
-       assert(cb_data == NULL);
+       if (filter && !ref_filter_match(refname,
+                             filter->include_ref_pattern,
+                             filter->exclude_ref_pattern))
+               return 0;
 
        if (starts_with(refname, git_replace_ref_base)) {
                struct object_id original_oid;
@@ -148,15 +152,23 @@ static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
        return 0;
 }
 
-void load_ref_decorations(int flags)
+void load_ref_decorations(struct decoration_filter *filter, int flags)
 {
        if (!decoration_loaded) {
-
+               if (filter) {
+                       struct string_list_item *item;
+                       for_each_string_list_item(item, filter->exclude_ref_pattern) {
+                               normalize_glob_ref(item, NULL, item->string);
+                       }
+                       for_each_string_list_item(item, filter->include_ref_pattern) {
+                               normalize_glob_ref(item, NULL, item->string);
+                       }
+               }
                decoration_loaded = 1;
                decoration_flags = flags;
-               for_each_ref(add_ref_decoration, NULL);
-               head_ref(add_ref_decoration, NULL);
-               for_each_commit_graft(add_graft_decoration, NULL);
+               for_each_ref(add_ref_decoration, filter);
+               head_ref(add_ref_decoration, filter);
+               for_each_commit_graft(add_graft_decoration, filter);
        }
 }
 
@@ -793,7 +805,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
        struct commit_list *parents;
        struct object_id *oid;
 
-       if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
+       if (!opt->diff && !opt->diffopt.flags.exit_with_status)
                return 0;
 
        parse_commit_or_die(commit);
index 48f11fb740ddf670cac819cb733cbc736cc30d0e..deba035187185bc28a83520b47480c3141d8398e 100644 (file)
@@ -7,6 +7,10 @@ struct log_info {
        struct commit *commit, *parent;
 };
 
+struct decoration_filter {
+       struct string_list *include_ref_pattern, *exclude_ref_pattern;
+};
+
 int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
 void init_log_tree_opt(struct rev_info *);
 int log_tree_diff_flush(struct rev_info *);
@@ -24,7 +28,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit);
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p);
-void load_ref_decorations(int flags);
+void load_ref_decorations(struct decoration_filter *filter, int flags);
 
 #define FORMAT_PATCH_NAME_MAX 64
 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);
index 9fb0b9f8fdfe12b58b5876a4984f4bf9a9f5c0fb..2ecf495cc2a4a6d4544b92db1d2a8c3c0cc36f87 100644 (file)
@@ -540,8 +540,8 @@ static struct string_list *get_renames(struct merge_options *o,
                return renames;
 
        diff_setup(&opts);
-       DIFF_OPT_SET(&opts, RECURSIVE);
-       DIFF_OPT_CLR(&opts, RENAME_EMPTY);
+       opts.flags.recursive = 1;
+       opts.flags.rename_empty = 0;
        opts.detect_rename = DIFF_DETECT_RENAME;
        opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
                            o->diff_rename_limit >= 0 ? o->diff_rename_limit :
@@ -646,7 +646,7 @@ static int remove_file(struct merge_options *o, int clean,
                if (ignore_case) {
                        struct cache_entry *ce;
                        ce = cache_file_exists(path, strlen(path), ignore_case);
-                       if (ce && ce_stage(ce) == 0)
+                       if (ce && ce_stage(ce) == 0 && strcmp(path, ce->name))
                                return 0;
                }
                if (remove_path(path))
@@ -2082,7 +2082,7 @@ int merge_recursive(struct merge_options *o,
                /* if there is no common ancestor, use an empty tree */
                struct tree *tree;
 
-               tree = lookup_tree(&empty_tree_oid);
+               tree = lookup_tree(the_hash_algo->empty_tree);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
@@ -2163,7 +2163,7 @@ int merge_recursive_generic(struct merge_options *o,
                            struct commit **result)
 {
        int clean;
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       struct lock_file lock = LOCK_INIT;
        struct commit *head_commit = get_ref(head, o->branch1);
        struct commit *next_commit = get_ref(merge, o->branch2);
        struct commit_list *ca = NULL;
@@ -2179,14 +2179,14 @@ int merge_recursive_generic(struct merge_options *o,
                }
        }
 
-       hold_locked_index(lock, LOCK_DIE_ON_ERROR);
+       hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        clean = merge_recursive(o, head_commit, next_commit, ca,
                        result);
        if (clean < 0)
                return clean;
 
        if (active_cache_changed &&
-           write_locked_index(&the_index, lock, COMMIT_LOCK))
+           write_locked_index(&the_index, &lock, COMMIT_LOCK))
                return err(o, _("Unable to write index."));
 
        return clean ? 0 : 1;
@@ -2202,6 +2202,7 @@ static void merge_recursive_config(struct merge_options *o)
 
 void init_merge_options(struct merge_options *o)
 {
+       const char *merge_verbosity;
        memset(o, 0, sizeof(struct merge_options));
        o->verbosity = 2;
        o->buffer_output = 1;
@@ -2210,9 +2211,9 @@ void init_merge_options(struct merge_options *o)
        o->renormalize = 0;
        o->detect_rename = 1;
        merge_recursive_config(o);
-       if (getenv("GIT_MERGE_VERBOSITY"))
-               o->verbosity =
-                       strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
+       merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
+       if (merge_verbosity)
+               o->verbosity = strtol(merge_verbosity, NULL, 10);
        if (o->verbosity >= 5)
                o->buffer_output = 0;
        strbuf_init(&o->obuf, 0);
@@ -2252,6 +2253,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)
                DIFF_XDL_SET(o, IGNORE_WHITESPACE);
        else if (!strcmp(s, "ignore-space-at-eol"))
                DIFF_XDL_SET(o, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(s, "ignore-cr-at-eol"))
+               DIFF_XDL_SET(o, IGNORE_CR_AT_EOL);
        else if (!strcmp(s, "renormalize"))
                o->renormalize = 1;
        else if (!strcmp(s, "no-renormalize"))
diff --git a/merge.c b/merge.c
index 1d441ad94254257ad2ac28b8a17dc0b8e5ce14f9..e5d796c9f2d110bdcd378b361fe2052ac1dcc266 100644 (file)
--- a/merge.c
+++ b/merge.c
@@ -53,11 +53,11 @@ int checkout_fast_forward(const struct object_id *head,
        struct tree_desc t[MAX_UNPACK_TREES];
        int i, nr_trees = 0;
        struct dir_struct dir;
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       struct lock_file lock_file = LOCK_INIT;
 
        refresh_cache(REFRESH_QUIET);
 
-       if (hold_locked_index(lock_file, LOCK_REPORT_ON_ERROR) < 0)
+       if (hold_locked_index(&lock_file, LOCK_REPORT_ON_ERROR) < 0)
                return -1;
 
        memset(&trees, 0, sizeof(trees));
@@ -91,9 +91,7 @@ int checkout_fast_forward(const struct object_id *head,
        }
        if (unpack_trees(nr_trees, t, &opts))
                return -1;
-       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) {
-               rollback_lock_file(lock_file);
+       if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                return error(_("unable to write new index file"));
-       }
        return 0;
 }
index 29b4cede5f8d539416d0af22f31b0aabc0e28c46..17ee8602b3d2fbc2469a98e6cdeab0f2ff2b7ad3 100644 (file)
@@ -11,7 +11,7 @@ static int notes_cache_match_validity(const char *ref, const char *validity)
        struct strbuf msg = STRBUF_INIT;
        int ret;
 
-       if (read_ref(ref, oid.hash) < 0)
+       if (read_ref(ref, &oid) < 0)
                return 0;
 
        commit = lookup_commit_reference_gently(&oid, 1);
@@ -59,7 +59,7 @@ int notes_cache_write(struct notes_cache *c)
        if (commit_tree(c->validity, strlen(c->validity), tree_oid.hash, NULL,
                        commit_oid.hash, NULL, NULL) < 0)
                return -1;
-       if (update_ref("update notes cache", c->tree.update_ref, commit_oid.hash,
+       if (update_ref("update notes cache", c->tree.update_ref, &commit_oid,
                       NULL, 0, UPDATE_REFS_QUIET_ON_ERR) < 0)
                return -1;
 
index 4352c34a6ee50f907f2d03e7d0ed1a596345a5d3..0f6573cb17cbbbc2219453bdca424fd7ebc156dc 100644 (file)
@@ -125,7 +125,7 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o,
               oid_to_hex(base), oid_to_hex(remote));
 
        diff_setup(&opt);
-       DIFF_OPT_SET(&opt, RECURSIVE);
+       opt.flags.recursive = 1;
        opt.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_setup_done(&opt);
        diff_tree_oid(base, remote, "", &opt);
@@ -188,7 +188,7 @@ static void diff_tree_local(struct notes_merge_options *o,
               len, oid_to_hex(base), oid_to_hex(local));
 
        diff_setup(&opt);
-       DIFF_OPT_SET(&opt, RECURSIVE);
+       opt.flags.recursive = 1;
        opt.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_setup_done(&opt);
        diff_tree_oid(base, local, "", &opt);
@@ -547,7 +547,7 @@ int notes_merge(struct notes_merge_options *o,
               o->local_ref, o->remote_ref);
 
        /* Dereference o->local_ref into local_sha1 */
-       if (read_ref_full(o->local_ref, 0, local_oid.hash, NULL))
+       if (read_ref_full(o->local_ref, 0, &local_oid, NULL))
                die("Failed to resolve local notes ref '%s'", o->local_ref);
        else if (!check_refname_format(o->local_ref, 0) &&
                is_null_oid(&local_oid))
@@ -595,7 +595,7 @@ int notes_merge(struct notes_merge_options *o,
        bases = get_merge_bases(local, remote);
        if (!bases) {
                base_oid = &null_oid;
-               base_tree_oid = &empty_tree_oid;
+               base_tree_oid = the_hash_algo->empty_tree;
                if (o->verbosity >= 4)
                        printf("No merge base found; doing history-less merge\n");
        } else if (!bases->next) {
index 9765deb41ab02dba909ec9027701bf2fdc53f310..5c8e70c98fd26cca8bc690bc92653dd578a78c92 100644 (file)
@@ -18,7 +18,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
        if (!parents) {
                /* Deduce parent commit from t->ref */
                struct object_id parent_oid;
-               if (!read_ref(t->ref, parent_oid.hash)) {
+               if (!read_ref(t->ref, &parent_oid)) {
                        struct commit *parent = lookup_commit(&parent_oid);
                        if (parse_commit(parent))
                                die("Failed to find/parse commit %s", t->ref);
@@ -49,7 +49,7 @@ void commit_notes(struct notes_tree *t, const char *msg)
 
        create_notes_commit(t, NULL, buf.buf, buf.len, commit_oid.hash);
        strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
-       update_ref(buf.buf, t->update_ref, commit_oid.hash, NULL, 0,
+       update_ref(buf.buf, t->update_ref, &commit_oid, NULL, 0,
                   UPDATE_REFS_DIE_ON_ERR);
 
        strbuf_release(&buf);
diff --git a/notes.c b/notes.c
index 5c62862574399acb486530d3fab5a7f659eb7c2f..c7f21fae441067f7250caf0b4186fcbcbc7630bb 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -334,23 +334,6 @@ static void note_tree_free(struct int_node *tree)
        }
 }
 
-/*
- * Read `len` pairs of hexadecimal digits from `hex` and write the
- * values to `binary` as `len` bytes. Return 0 on success, or -1 if
- * the input does not consist of hex digits).
- */
-static int hex_to_bytes(unsigned char *binary, const char *hex, size_t len)
-{
-       for (; len; len--, hex += 2) {
-               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
-
-               if (val & ~0xff)
-                       return -1;
-               *binary++ = val;
-       }
-       return 0;
-}
-
 static int non_note_cmp(const struct non_note *a, const struct non_note *b)
 {
        return strcmp(a->path, b->path);
@@ -1027,7 +1010,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
        if (flags & NOTES_INIT_EMPTY || !notes_ref ||
            get_oid_treeish(notes_ref, &object_oid))
                return;
-       if (flags & NOTES_INIT_WRITABLE && read_ref(notes_ref, object_oid.hash))
+       if (flags & NOTES_INIT_WRITABLE && read_ref(notes_ref, &object_oid))
                die("Cannot use notes ref %s", notes_ref);
        if (get_tree_entry(object_oid.hash, "", oid.hash, &mode))
                die("Failed to read notes tree referenced by %s (%s)",
index df8abe96f7da2585f7f07c68f53695a2d450e2ae..f34461d4afde1b25c30fa8ac72428955c475c5b5 100644 (file)
--- a/object.h
+++ b/object.h
@@ -38,6 +38,7 @@ struct object_array {
  * http-push.c:                            16-----19
  * commit.c:                               16-----19
  * sha1_name.c:                                     20
+ * list-objects-filter.c:                             21
  * builtin/fsck.c:  0--3
  */
 #define FLAG_BITS  27
index 18f54cde143e58403fb448d227e9342540bbd0fc..d3cd2bb5902964554a16313cdb9ba4924e871e56 100644 (file)
--- a/oidmap.h
+++ b/oidmap.h
@@ -65,4 +65,26 @@ extern void *oidmap_put(struct oidmap *map, void *entry);
  */
 extern void *oidmap_remove(struct oidmap *map, const struct object_id *key);
 
+
+struct oidmap_iter {
+       struct hashmap_iter h_iter;
+};
+
+static inline void oidmap_iter_init(struct oidmap *map, struct oidmap_iter *iter)
+{
+       hashmap_iter_init(&map->map, &iter->h_iter);
+}
+
+static inline void *oidmap_iter_next(struct oidmap_iter *iter)
+{
+       return hashmap_iter_next(&iter->h_iter);
+}
+
+static inline void *oidmap_iter_first(struct oidmap *map,
+                                     struct oidmap_iter *iter)
+{
+       oidmap_iter_init(map, iter);
+       return oidmap_iter_next(iter);
+}
+
 #endif
index f1f874aaad2c0375eb80d3b714ae6ad460c75ddc..454c54f93396efccfb9957423931d0ea727ff340 100644 (file)
--- a/oidset.c
+++ b/oidset.c
@@ -24,6 +24,16 @@ int oidset_insert(struct oidset *set, const struct object_id *oid)
        return 0;
 }
 
+int oidset_remove(struct oidset *set, const struct object_id *oid)
+{
+       struct oidmap_entry *entry;
+
+       entry = oidmap_remove(&set->map, oid);
+       free(entry);
+
+       return (entry != NULL);
+}
+
 void oidset_clear(struct oidset *set)
 {
        oidmap_free(&set->map, 1);
index f4c9e0f9c04e71440aee3c1382297cbf8b1c7827..783abceccd11e1786c7d6b54b2848f3eeb6d4983 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -24,6 +24,12 @@ struct oidset {
 
 #define OIDSET_INIT { OIDMAP_INIT }
 
+
+static inline void oidset_init(struct oidset *set, size_t initial_size)
+{
+       return oidmap_init(&set->map, initial_size);
+}
+
 /**
  * Returns true iff `set` contains `oid`.
  */
@@ -38,10 +44,40 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid);
  */
 int oidset_insert(struct oidset *set, const struct object_id *oid);
 
+/**
+ * Remove the oid from the set.
+ *
+ * Returns 1 if the oid was present in the set, 0 otherwise.
+ */
+int oidset_remove(struct oidset *set, const struct object_id *oid);
+
 /**
  * Remove all entries from the oidset, freeing any resources associated with
  * it.
  */
 void oidset_clear(struct oidset *set);
 
+struct oidset_iter {
+       struct oidmap_iter m_iter;
+};
+
+static inline void oidset_iter_init(struct oidset *set,
+                                   struct oidset_iter *iter)
+{
+       oidmap_iter_init(&set->map, &iter->m_iter);
+}
+
+static inline struct object_id *oidset_iter_next(struct oidset_iter *iter)
+{
+       struct oidmap_entry *e = oidmap_iter_next(&iter->m_iter);
+       return e ? &e->oid : NULL;
+}
+
+static inline struct object_id *oidset_iter_first(struct oidset *set,
+                                                 struct oidset_iter *iter)
+{
+       oidset_iter_init(set, iter);
+       return oidset_iter_next(iter);
+}
+
 #endif /* OIDSET_H */
index 42e3d5f4f26ee0f31d13c2a3d15bc31da5f7c1d4..9270983e5f581e40f894a8885396e43d13e71015 100644 (file)
@@ -587,7 +587,7 @@ static void show_extended_objects(struct bitmap *objects,
                        continue;
 
                obj = eindex->objects[i];
-               show_reach(obj->oid.hash, obj->type, 0, eindex->hashes[i], NULL, 0);
+               show_reach(&obj->oid, obj->type, 0, eindex->hashes[i], NULL, 0);
        }
 }
 
@@ -612,7 +612,7 @@ static void show_objects_for_type(
                eword_t word = objects->words[i] & filter;
 
                for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
-                       const unsigned char *sha1;
+                       struct object_id oid;
                        struct revindex_entry *entry;
                        uint32_t hash = 0;
 
@@ -625,12 +625,12 @@ static void show_objects_for_type(
                                continue;
 
                        entry = &bitmap_git.pack->revindex[pos + offset];
-                       sha1 = nth_packed_object_sha1(bitmap_git.pack, entry->nr);
+                       nth_packed_object_oid(&oid, bitmap_git.pack, entry->nr);
 
                        if (bitmap_git.hashes)
                                hash = get_be32(bitmap_git.hashes + entry->nr);
 
-                       show_reach(sha1, object_type, 0, hash, bitmap_git.pack, entry->offset);
+                       show_reach(&oid, object_type, 0, hash, bitmap_git.pack, entry->offset);
                }
 
                pos += BITS_IN_EWORD;
index 0adcef77b58cc13822f1b400d5418ec6f8b34421..3742a00e14a0d4da335253b2a76f978edb499d35 100644 (file)
@@ -27,7 +27,7 @@ enum pack_bitmap_flags {
 };
 
 typedef int (*show_reachable_fn)(
-       const unsigned char *sha1,
+       const struct object_id *oid,
        enum object_type type,
        int flags,
        uint32_t hash,
index 7a583b3011390c1a3cb1c0885e420e76b7751d26..8f7c25d5dbf5ccdcbf27db51fbe1970addebf9a7 100644 (file)
@@ -61,7 +61,7 @@ int init_patch_ids(struct patch_ids *ids)
        memset(ids, 0, sizeof(*ids));
        diff_setup(&ids->diffopts);
        ids->diffopts.detect_rename = 0;
-       DIFF_OPT_SET(&ids->diffopts, RECURSIVE);
+       ids->diffopts.flags.recursive = 1;
        diff_setup_done(&ids->diffopts);
        hashmap_init(&ids->patches, patch_id_cmp, &ids->diffopts, 256);
        return 0;
diff --git a/path.h b/path.h
index 9541620c79dcda31e4a1b7437a79f0f63b156523..1ccd0373c9db8460c7ad7a3f9c5465f84408fd56 100644 (file)
--- a/path.h
+++ b/path.h
 struct repository;
 
 /*
- * Return a statically allocated filename, either generically (mkpath), in
- * the repository directory (git_path), or in a submodule's repository
- * directory (git_path_submodule). In all cases, note that the result
- * may be overwritten by another call to _any_ of the functions. Consider
- * using the safer "dup" or "strbuf" formats below (in some cases, the
- * unsafe versions have already been removed).
+ * The result to all functions which return statically allocated memory may be
+ * overwritten by another call to _any_ one of these functions. Consider using
+ * the safer variants which operate on strbufs or return allocated memory.
  */
-extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 
+/*
+ * Return a statically allocated path.
+ */
+extern const char *mkpath(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Return a path.
+ */
+extern char *mkpathdup(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Construct a path and place the result in the provided buffer `buf`.
+ */
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
-extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
-       __attribute__((format (printf, 2, 3)));
+
+/*
+ * The `git_common_path` family of functions will construct a path into a
+ * repository's common git directory, which is shared by all worktrees.
+ */
+
+/*
+ * Constructs a path into the common git directory of repository `repo` and
+ * append it in the provided buffer `sb`.
+ */
 extern void strbuf_git_common_path(struct strbuf *sb,
                                   const struct repository *repo,
                                   const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
-extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
-       __attribute__((format (printf, 2, 3)));
-extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path,
-                                    const char *fmt, ...)
-       __attribute__((format (printf, 3, 4)));
-extern char *git_pathdup(const char *fmt, ...)
-       __attribute__((format (printf, 1, 2)));
-extern char *mkpathdup(const char *fmt, ...)
+
+/*
+ * Return a statically allocated path into the main repository's
+ * (the_repository) common git directory.
+ */
+extern const char *git_common_path(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
-extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
-       __attribute__((format (printf, 2, 3)));
 
+
+/*
+ * The `git_path` family of functions will construct a path into a repository's
+ * git directory.
+ *
+ * These functions will perform adjustments to the resultant path to account
+ * for special paths which are either considered common among worktrees (e.g.
+ * paths into the object directory) or have been explicitly set via an
+ * environment variable or config (e.g. path to the index file).
+ *
+ * For an exhaustive list of the adjustments made look at `common_list` and
+ * `adjust_git_path` in path.c.
+ */
+
+/*
+ * Return a path into the git directory of repository `repo`.
+ */
 extern char *repo_git_path(const struct repository *repo,
                           const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into the git directory of repository `repo` and append it
+ * to the provided buffer `sb`.
+ */
 extern void strbuf_repo_git_path(struct strbuf *sb,
                                 const struct repository *repo,
                                 const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 
+/*
+ * Return a statically allocated path into the main repository's
+ * (the_repository) git directory.
+ */
+extern const char *git_path(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Return a path into the main repository's (the_repository) git directory.
+ */
+extern char *git_pathdup(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Construct a path into the main repository's (the_repository) git directory
+ * and place it in the provided buffer `buf`, the contents of the buffer will
+ * be overridden.
+ */
+extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into the main repository's (the_repository) git directory
+ * and append it to the provided buffer `sb`.
+ */
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
+/*
+ * Return a path into the worktree of repository `repo`.
+ *
+ * If the repository doesn't have a worktree NULL is returned.
+ */
 extern char *repo_worktree_path(const struct repository *repo,
                                const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into the worktree of repository `repo` and append it
+ * to the provided buffer `sb`.
+ *
+ * If the repository doesn't have a worktree nothing will be appended to `sb`.
+ */
 extern void strbuf_repo_worktree_path(struct strbuf *sb,
                                      const struct repository *repo,
                                      const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 
+/*
+ * Return a path into a submodule's git directory located at `path`.  `path`
+ * must only reference a submodule of the main repository (the_repository).
+ */
+extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into a submodule's git directory located at `path` and
+ * append it to the provided buffer `sb`.  `path` must only reference a
+ * submodule of the main repository (the_repository).
+ */
+extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path,
+                                    const char *fmt, ...)
+       __attribute__((format (printf, 3, 4)));
+
 extern void report_linked_checkout_garbage(void);
 
 /*
index cdefdc7cc0e0be248297d1191eefe72418923f7a..82eb39cd679ffbce82abac691c6f4343335b9924 100644 (file)
@@ -532,7 +532,7 @@ void parse_pathspec(struct pathspec *pathspec,
 {
        struct pathspec_item *item;
        const char *entry = argv ? *argv : NULL;
-       int i, n, prefixlen, warn_empty_string, nr_exclude = 0;
+       int i, n, prefixlen, nr_exclude = 0;
 
        memset(pathspec, 0, sizeof(*pathspec));
 
@@ -565,13 +565,10 @@ void parse_pathspec(struct pathspec *pathspec,
        }
 
        n = 0;
-       warn_empty_string = 1;
        while (argv[n]) {
-               if (*argv[n] == '\0' && warn_empty_string) {
-                       warning(_("empty strings as pathspecs will be made invalid in upcoming releases. "
-                                 "please use . instead if you meant to match all paths"));
-                       warn_empty_string = 0;
-               }
+               if (*argv[n] == '\0')
+                       die("empty string is not a valid pathspec. "
+                                 "please use . instead if you meant to match all paths");
                n++;
        }
 
index 6420d1080ae4165ad81b60376c714bd178463b6a..099a170c2ef6eb01bee98c7ebf638f34655fda86 100644 (file)
@@ -24,6 +24,7 @@ struct pathspec {
        int nr;
        unsigned int has_wildcard:1;
        unsigned int recursive:1;
+       unsigned int recurse_submodules:1;
        unsigned magic;
        int max_depth;
        struct pathspec_item {
diff --git a/perl/Git/Packet.pm b/perl/Git/Packet.pm
new file mode 100644 (file)
index 0000000..b75738b
--- /dev/null
@@ -0,0 +1,173 @@
+package Git::Packet;
+use 5.008;
+use strict;
+use warnings;
+BEGIN {
+       require Exporter;
+       if ($] < 5.008003) {
+               *import = \&Exporter::import;
+       } else {
+               # Exporter 5.57 which supports this invocation was
+               # released with perl 5.8.3
+               Exporter->import('import');
+       }
+}
+
+our @EXPORT = qw(
+                       packet_compare_lists
+                       packet_bin_read
+                       packet_txt_read
+                       packet_key_val_read
+                       packet_bin_write
+                       packet_txt_write
+                       packet_flush
+                       packet_initialize
+                       packet_read_capabilities
+                       packet_read_and_check_capabilities
+                       packet_check_and_write_capabilities
+               );
+our @EXPORT_OK = @EXPORT;
+
+sub packet_compare_lists {
+       my ($expect, @result) = @_;
+       my $ix;
+       if (scalar @$expect != scalar @result) {
+               return undef;
+       }
+       for ($ix = 0; $ix < $#result; $ix++) {
+               if ($expect->[$ix] ne $result[$ix]) {
+                       return undef;
+               }
+       }
+       return 1;
+}
+
+sub packet_bin_read {
+       my $buffer;
+       my $bytes_read = read STDIN, $buffer, 4;
+       if ( $bytes_read == 0 ) {
+               # EOF - Git stopped talking to us!
+               return ( -1, "" );
+       } elsif ( $bytes_read != 4 ) {
+               die "invalid packet: '$buffer'";
+       }
+       my $pkt_size = hex($buffer);
+       if ( $pkt_size == 0 ) {
+               return ( 1, "" );
+       } elsif ( $pkt_size > 4 ) {
+               my $content_size = $pkt_size - 4;
+               $bytes_read = read STDIN, $buffer, $content_size;
+               if ( $bytes_read != $content_size ) {
+                       die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
+               }
+               return ( 0, $buffer );
+       } else {
+               die "invalid packet size: $pkt_size";
+       }
+}
+
+sub remove_final_lf_or_die {
+       my $buf = shift;
+       if ( $buf =~ s/\n$// ) {
+               return $buf;
+       }
+       die "A non-binary line MUST be terminated by an LF.\n"
+           . "Received: '$buf'";
+}
+
+sub packet_txt_read {
+       my ( $res, $buf ) = packet_bin_read();
+       if ( $res != -1 and $buf ne '' ) {
+               $buf = remove_final_lf_or_die($buf);
+       }
+       return ( $res, $buf );
+}
+
+# Read a text packet, expecting that it is in the form "key=value" for
+# the given $key.  An EOF does not trigger any error and is reported
+# back to the caller (like packet_txt_read() does).  Die if the "key"
+# part of "key=value" does not match the given $key, or the value part
+# is empty.
+sub packet_key_val_read {
+       my ( $key ) = @_;
+       my ( $res, $buf ) = packet_txt_read();
+       if ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) {
+               return ( $res, $buf );
+       }
+       die "bad $key: '$buf'";
+}
+
+sub packet_bin_write {
+       my $buf = shift;
+       print STDOUT sprintf( "%04x", length($buf) + 4 );
+       print STDOUT $buf;
+       STDOUT->flush();
+}
+
+sub packet_txt_write {
+       packet_bin_write( $_[0] . "\n" );
+}
+
+sub packet_flush {
+       print STDOUT sprintf( "%04x", 0 );
+       STDOUT->flush();
+}
+
+sub packet_initialize {
+       my ($name, $version) = @_;
+
+       packet_compare_lists([0, $name . "-client"], packet_txt_read()) ||
+               die "bad initialize";
+       packet_compare_lists([0, "version=" . $version], packet_txt_read()) ||
+               die "bad version";
+       packet_compare_lists([1, ""], packet_bin_read()) ||
+               die "bad version end";
+
+       packet_txt_write( $name . "-server" );
+       packet_txt_write( "version=" . $version );
+       packet_flush();
+}
+
+sub packet_read_capabilities {
+       my @cap;
+       while (1) {
+               my ( $res, $buf ) = packet_bin_read();
+               if ( $res == -1 ) {
+                       die "unexpected EOF when reading capabilities";
+               }
+               return ( $res, @cap ) if ( $res != 0 );
+               $buf = remove_final_lf_or_die($buf);
+               unless ( $buf =~ s/capability=// ) {
+                       die "bad capability buf: '$buf'";
+               }
+               push @cap, $buf;
+       }
+}
+
+# Read remote capabilities and check them against capabilities we require
+sub packet_read_and_check_capabilities {
+       my @required_caps = @_;
+       my ($res, @remote_caps) = packet_read_capabilities();
+       my %remote_caps = map { $_ => 1 } @remote_caps;
+       foreach (@required_caps) {
+               unless (exists($remote_caps{$_})) {
+                       die "required '$_' capability not available from remote" ;
+               }
+       }
+       return %remote_caps;
+}
+
+# Check our capabilities we want to advertise against the remote ones
+# and then advertise our capabilities
+sub packet_check_and_write_capabilities {
+       my ($remote_caps, @our_caps) = @_;
+       foreach (@our_caps) {
+               unless (exists($remote_caps->{$_})) {
+                       die "our capability '$_' is not available from remote"
+               }
+               packet_txt_write( "capability=" . $_ );
+       }
+       packet_flush();
+}
+
+1;
index 15d96fcc7a5a81ae0304bc51a603fe54bcbb95c6..f657de20e3983af8f990114c24bddd2775845d88 100644 (file)
@@ -30,6 +30,7 @@ instdir_SQ = $(subst ','\'',$(prefix)/lib)
 modules += Git
 modules += Git/I18N
 modules += Git/IndexInfo
+modules += Git/Packet
 modules += Git/SVN
 modules += Git/SVN/Memoize/YAML
 modules += Git/SVN/Fetcher
index 93ea311443a37b81b85a72dc569c715417d7ef29..2827ca772a3703f71bc588d0f6cacd5caa318fe7 100644 (file)
@@ -188,6 +188,12 @@ static int packet_write_gently(const int fd_out, const char *buf, size_t size)
        return 0;
 }
 
+void packet_write(int fd_out, const char *buf, size_t size)
+{
+       if (packet_write_gently(fd_out, buf, size))
+               die_errno("packet write failed");
+}
+
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
 {
        va_list args;
index 66ef610fc4f7de2f6b7572f1e5fb36438c5dec19..3dad583e2d02264c4a831c939ae0e13a54de2ff6 100644 (file)
@@ -22,6 +22,7 @@
 void packet_flush(int fd);
 void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 void packet_buf_flush(struct strbuf *buf);
+void packet_write(int fd_out, const char *buf, size_t size);
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 int packet_flush_gently(int fd);
 int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
index 70a4c808783bab92b4b6319e3dbd8e16641b1918..2a83255e4eeaf8e8ce74516880b0333b23d6f583 100644 (file)
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "pathspec.h"
 #include "dir.h"
+#include "fsmonitor.h"
 
 #ifdef NO_PTHREADS
 static void preload_index(struct index_state *index,
@@ -55,15 +56,18 @@ static void *preload_thread(void *_data)
                        continue;
                if (ce_skip_worktree(ce))
                        continue;
+               if (ce->ce_flags & CE_FSMONITOR_VALID)
+                       continue;
                if (!ce_path_match(ce, &p->pathspec, NULL))
                        continue;
                if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
                        continue;
                if (lstat(ce->name, &st))
                        continue;
-               if (ie_match_stat(index, ce, &st, CE_MATCH_RACY_IS_DIRTY))
+               if (ie_match_stat(index, ce, &st, CE_MATCH_RACY_IS_DIRTY|CE_MATCH_IGNORE_FSMONITOR))
                        continue;
                ce_mark_uptodate(ce);
+               mark_fsmonitor_valid(ce);
        } while (--nr > 0);
        cache_def_clear(&cache);
        return NULL;
@@ -79,6 +83,8 @@ static void preload_index(struct index_state *index,
                return;
 
        threads = index->cache_nr / THREAD_COST;
+       if ((index->cache_nr > 1) && (threads < 2) && getenv("GIT_FORCE_PRELOAD_TEST"))
+               threads = 2;
        if (threads < 2)
                return;
        if (threads > MAX_PARALLEL)
index 2f6b0ae6c1486660bb37442b991cb1140a9f2bb6..f7ce4902301490d73bdd79bd396cf7bbe5f893ea 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1186,11 +1186,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                strbuf_addstr(sb, get_revision_mark(NULL, commit));
                return 1;
        case 'd':
-               load_ref_decorations(DECORATE_SHORT_REFS);
+               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
                format_decorations(sb, commit, c->auto_color);
                return 1;
        case 'D':
-               load_ref_decorations(DECORATE_SHORT_REFS);
+               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
                format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
                return 1;
        case 'g':               /* reflog info */
diff --git a/pretty.h b/pretty.h
new file mode 100644 (file)
index 0000000..5c85d94
--- /dev/null
+++ b/pretty.h
@@ -0,0 +1,131 @@
+#ifndef PRETTY_H
+#define PRETTY_H
+
+struct commit;
+
+/* Commit formats */
+enum cmit_fmt {
+       CMIT_FMT_RAW,
+       CMIT_FMT_MEDIUM,
+       CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
+       CMIT_FMT_SHORT,
+       CMIT_FMT_FULL,
+       CMIT_FMT_FULLER,
+       CMIT_FMT_ONELINE,
+       CMIT_FMT_EMAIL,
+       CMIT_FMT_MBOXRD,
+       CMIT_FMT_USERFORMAT,
+
+       CMIT_FMT_UNSPECIFIED
+};
+
+struct pretty_print_context {
+       /*
+        * Callers should tweak these to change the behavior of pp_* functions.
+        */
+       enum cmit_fmt fmt;
+       int abbrev;
+       const char *after_subject;
+       int preserve_subject;
+       struct date_mode date_mode;
+       unsigned date_mode_explicit:1;
+       int print_email_subject;
+       int expand_tabs_in_log;
+       int need_8bit_cte;
+       char *notes_message;
+       struct reflog_walk_info *reflog_info;
+       struct rev_info *rev;
+       const char *output_encoding;
+       struct string_list *mailmap;
+       int color;
+       struct ident_split *from_ident;
+
+       /*
+        * Fields below here are manipulated internally by pp_* functions and
+        * should not be counted on by callers.
+        */
+       struct string_list in_body_headers;
+       int graph_width;
+};
+
+/* Check whether commit format is mail. */
+static inline int cmit_fmt_is_mail(enum cmit_fmt fmt)
+{
+       return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD);
+}
+
+struct userformat_want {
+       unsigned notes:1;
+};
+
+/* Set the flag "w->notes" if there is placeholder %N in "fmt". */
+void userformat_find_requirements(const char *fmt, struct userformat_want *w);
+
+/*
+ * Shortcut for invoking pretty_print_commit if we do not have any context.
+ * Context would be set empty except "fmt".
+ */
+void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                       struct strbuf *sb);
+
+/*
+ * Get information about user and date from "line", format it and
+ * put it into "sb".
+ * Format of "line" must be readable for split_ident_line function.
+ * The resulting format is "what: name <email> date".
+ */
+void pp_user_info(struct pretty_print_context *pp, const char *what,
+                       struct strbuf *sb, const char *line,
+                       const char *encoding);
+
+/*
+ * Format title line of commit message taken from "msg_p" and
+ * put it into "sb".
+ * First line of "msg_p" is also affected.
+ */
+void pp_title_line(struct pretty_print_context *pp, const char **msg_p,
+                       struct strbuf *sb, const char *encoding,
+                       int need_8bit_cte);
+
+/*
+ * Get current state of commit message from "msg_p" and continue formatting
+ * by adding indentation and '>' signs. Put result into "sb".
+ */
+void pp_remainder(struct pretty_print_context *pp, const char **msg_p,
+                       struct strbuf *sb, int indent);
+
+/*
+ * Create a text message about commit using given "format" and "context".
+ * Put the result to "sb".
+ * Please use this function for custom formats.
+ */
+void format_commit_message(const struct commit *commit,
+                       const char *format, struct strbuf *sb,
+                       const struct pretty_print_context *context);
+
+/*
+ * Parse given arguments from "arg", check it for correctness and
+ * fill struct rev_info.
+ */
+void get_commit_format(const char *arg, struct rev_info *);
+
+/*
+ * Make a commit message with all rules from given "pp"
+ * and put it into "sb".
+ * Please use this function if you have a context (candidate for "pp").
+ */
+void pretty_print_commit(struct pretty_print_context *pp,
+                       const struct commit *commit,
+                       struct strbuf *sb);
+
+/*
+ * Change line breaks in "msg" to "line_separator" and put it into "sb".
+ * Return "msg" itself.
+ */
+const char *format_subject(struct strbuf *sb, const char *msg,
+                       const char *line_separator);
+
+/* Check if "cmit_fmt" will produce an empty output. */
+int commit_format_is_empty(enum cmit_fmt);
+
+#endif /* PRETTY_H */
index 289678d43d801411dbe56b8090a2e1a18a158499..5a99c9fbf00bfb98d3a51cc7a7948b4e963b3517 100644 (file)
@@ -30,11 +30,10 @@ struct throughput {
 
 struct progress {
        const char *title;
-       int last_value;
-       unsigned total;
+       uint64_t last_value;
+       uint64_t total;
        unsigned last_percent;
        unsigned delay;
-       unsigned delayed_percent_threshold;
        struct throughput *throughput;
        uint64_t start_ns;
 };
@@ -79,24 +78,12 @@ static int is_foreground_fd(int fd)
        return tpgrp < 0 || tpgrp == getpgid(0);
 }
 
-static int display(struct progress *progress, unsigned n, const char *done)
+static int display(struct progress *progress, uint64_t n, const char *done)
 {
        const char *eol, *tp;
 
-       if (progress->delay) {
-               if (!progress_update || --progress->delay)
-                       return 0;
-               if (progress->total) {
-                       unsigned percent = n * 100 / progress->total;
-                       if (percent > progress->delayed_percent_threshold) {
-                               /* inhibit this progress report entirely */
-                               clear_progress_signal();
-                               progress->delay = -1;
-                               progress->total = 0;
-                               return 0;
-                       }
-               }
-       }
+       if (progress->delay && (!progress_update || --progress->delay))
+               return 0;
 
        progress->last_value = n;
        tp = (progress->throughput) ? progress->throughput->display.buf : "";
@@ -106,9 +93,10 @@ static int display(struct progress *progress, unsigned n, const char *done)
                if (percent != progress->last_percent || progress_update) {
                        progress->last_percent = percent;
                        if (is_foreground_fd(fileno(stderr)) || done) {
-                               fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
-                                       progress->title, percent, n,
-                                       progress->total, tp, eol);
+                               fprintf(stderr, "%s: %3u%% (%"PRIuMAX"/%"PRIuMAX")%s%s",
+                                       progress->title, percent,
+                                       (uintmax_t)n, (uintmax_t)progress->total,
+                                       tp, eol);
                                fflush(stderr);
                        }
                        progress_update = 0;
@@ -116,8 +104,8 @@ static int display(struct progress *progress, unsigned n, const char *done)
                }
        } else if (progress_update) {
                if (is_foreground_fd(fileno(stderr)) || done) {
-                       fprintf(stderr, "%s: %u%s%s",
-                               progress->title, n, tp, eol);
+                       fprintf(stderr, "%s: %"PRIuMAX"%s%s",
+                               progress->title, (uintmax_t)n, tp, eol);
                        fflush(stderr);
                }
                progress_update = 0;
@@ -127,7 +115,7 @@ static int display(struct progress *progress, unsigned n, const char *done)
        return 0;
 }
 
-static void throughput_string(struct strbuf *buf, off_t total,
+static void throughput_string(struct strbuf *buf, uint64_t total,
                              unsigned int rate)
 {
        strbuf_reset(buf);
@@ -138,7 +126,7 @@ static void throughput_string(struct strbuf *buf, off_t total,
        strbuf_addstr(buf, "/s");
 }
 
-void display_throughput(struct progress *progress, off_t total)
+void display_throughput(struct progress *progress, uint64_t total)
 {
        struct throughput *tp;
        uint64_t now_ns;
@@ -200,13 +188,13 @@ void display_throughput(struct progress *progress, off_t total)
                display(progress, progress->last_value, NULL);
 }
 
-int display_progress(struct progress *progress, unsigned n)
+int display_progress(struct progress *progress, uint64_t n)
 {
        return progress ? display(progress, n, NULL) : 0;
 }
 
-static struct progress *start_progress_delay(const char *title, unsigned total,
-                                            unsigned percent_threshold, unsigned delay)
+static struct progress *start_progress_delay(const char *title, uint64_t total,
+                                            unsigned delay)
 {
        struct progress *progress = malloc(sizeof(*progress));
        if (!progress) {
@@ -219,7 +207,6 @@ static struct progress *start_progress_delay(const char *title, unsigned total,
        progress->total = total;
        progress->last_value = -1;
        progress->last_percent = -1;
-       progress->delayed_percent_threshold = percent_threshold;
        progress->delay = delay;
        progress->throughput = NULL;
        progress->start_ns = getnanotime();
@@ -227,14 +214,14 @@ static struct progress *start_progress_delay(const char *title, unsigned total,
        return progress;
 }
 
-struct progress *start_delayed_progress(const char *title, unsigned total)
+struct progress *start_delayed_progress(const char *title, uint64_t total)
 {
-       return start_progress_delay(title, total, 0, 2);
+       return start_progress_delay(title, total, 2);
 }
 
-struct progress *start_progress(const char *title, unsigned total)
+struct progress *start_progress(const char *title, uint64_t total)
 {
-       return start_progress_delay(title, total, 0, 0);
+       return start_progress_delay(title, total, 0);
 }
 
 void stop_progress(struct progress **p_progress)
index 6392b633710c84dcff3a238a26f4d4b359053059..70a4d4a0d66ebda2423acf4b5dc84ba485a874b8 100644 (file)
@@ -3,10 +3,10 @@
 
 struct progress;
 
-void display_throughput(struct progress *progress, off_t total);
-int display_progress(struct progress *progress, unsigned n);
-struct progress *start_progress(const char *title, unsigned total);
-struct progress *start_delayed_progress(const char *title, unsigned total);
+void display_throughput(struct progress *progress, uint64_t total);
+int display_progress(struct progress *progress, uint64_t n);
+struct progress *start_progress(const char *title, uint64_t total);
+struct progress *start_delayed_progress(const char *title, uint64_t total);
 void stop_progress(struct progress **progress);
 void stop_progress_msg(struct progress **progress, const char *msg);
 
diff --git a/protocol.c b/protocol.c
new file mode 100644 (file)
index 0000000..43012b7
--- /dev/null
@@ -0,0 +1,79 @@
+#include "cache.h"
+#include "config.h"
+#include "protocol.h"
+
+static enum protocol_version parse_protocol_version(const char *value)
+{
+       if (!strcmp(value, "0"))
+               return protocol_v0;
+       else if (!strcmp(value, "1"))
+               return protocol_v1;
+       else
+               return protocol_unknown_version;
+}
+
+enum protocol_version get_protocol_version_config(void)
+{
+       const char *value;
+       if (!git_config_get_string_const("protocol.version", &value)) {
+               enum protocol_version version = parse_protocol_version(value);
+
+               if (version == protocol_unknown_version)
+                       die("unknown value for config 'protocol.version': %s",
+                           value);
+
+               return version;
+       }
+
+       return protocol_v0;
+}
+
+enum protocol_version determine_protocol_version_server(void)
+{
+       const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT);
+       enum protocol_version version = protocol_v0;
+
+       /*
+        * Determine which protocol version the client has requested.  Since
+        * multiple 'version' keys can be sent by the client, indicating that
+        * the client is okay to speak any of them, select the greatest version
+        * that the client has requested.  This is due to the assumption that
+        * the most recent protocol version will be the most state-of-the-art.
+        */
+       if (git_protocol) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+               const struct string_list_item *item;
+               string_list_split(&list, git_protocol, ':', -1);
+
+               for_each_string_list_item(item, &list) {
+                       const char *value;
+                       enum protocol_version v;
+
+                       if (skip_prefix(item->string, "version=", &value)) {
+                               v = parse_protocol_version(value);
+                               if (v > version)
+                                       version = v;
+                       }
+               }
+
+               string_list_clear(&list, 0);
+       }
+
+       return version;
+}
+
+enum protocol_version determine_protocol_version_client(const char *server_response)
+{
+       enum protocol_version version = protocol_v0;
+
+       if (skip_prefix(server_response, "version ", &server_response)) {
+               version = parse_protocol_version(server_response);
+
+               if (version == protocol_unknown_version)
+                       die("server is speaking an unknown protocol");
+               if (version == protocol_v0)
+                       die("protocol error: server explicitly said version 0");
+       }
+
+       return version;
+}
diff --git a/protocol.h b/protocol.h
new file mode 100644 (file)
index 0000000..1b2bc94
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+enum protocol_version {
+       protocol_unknown_version = -1,
+       protocol_v0 = 0,
+       protocol_v1 = 1,
+};
+
+/*
+ * Used by a client to determine which protocol version to request be used when
+ * communicating with a server, reflecting the configured value of the
+ * 'protocol.version' config.  If unconfigured, a value of 'protocol_v0' is
+ * returned.
+ */
+extern enum protocol_version get_protocol_version_config(void);
+
+/*
+ * Used by a server to determine which protocol version should be used based on
+ * a client's request, communicated via the 'GIT_PROTOCOL' environment variable
+ * by setting appropriate values for the key 'version'.  If a client doesn't
+ * request a particular protocol version, a default of 'protocol_v0' will be
+ * used.
+ */
+extern enum protocol_version determine_protocol_version_server(void);
+
+/*
+ * Used by a client to determine which protocol version the server is speaking
+ * based on the server's initial response.
+ */
+extern enum protocol_version determine_protocol_version_client(const char *server_response);
+
+#endif /* PROTOCOL_H */
index 65f4fe8375d59234d5e58f87a8593fc50e6336a2..2eb81a66b941325dc339d61a0ab804a60cbb8be2 100644 (file)
@@ -19,6 +19,7 @@
 #include "varint.h"
 #include "split-index.h"
 #include "utf8.h"
+#include "fsmonitor.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
 #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
 #define CACHE_EXT_LINK 0x6c696e6b        /* "link" */
 #define CACHE_EXT_UNTRACKED 0x554E5452   /* "UNTR" */
+#define CACHE_EXT_FSMONITOR 0x46534D4E   /* "FSMN" */
 
 /* 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 | UNTRACKED_CHANGED)
+                SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED | FSMONITOR_CHANGED)
 
 struct index_state the_index;
 static const char *alternate_index_output;
@@ -62,6 +64,7 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
        free(old);
        set_index_entry(istate, nr, ce);
        ce->ce_flags |= CE_UPDATE_IN_BASE;
+       mark_fsmonitor_invalid(istate, ce);
        istate->cache_changed |= CE_ENTRY_CHANGED;
 }
 
@@ -150,8 +153,10 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
        if (assume_unchanged)
                ce->ce_flags |= CE_VALID;
 
-       if (S_ISREG(st->st_mode))
+       if (S_ISREG(st->st_mode)) {
                ce_mark_uptodate(ce);
+               mark_fsmonitor_valid(ce);
+       }
 }
 
 static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
@@ -191,7 +196,7 @@ static int ce_compare_link(const struct cache_entry *ce, size_t expected_size)
 
 static int ce_compare_gitlink(const struct cache_entry *ce)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
 
        /*
         * We don't actually require that the .git directory
@@ -201,9 +206,9 @@ static int ce_compare_gitlink(const struct cache_entry *ce)
         *
         * If so, we consider it always to match.
         */
-       if (resolve_gitlink_ref(ce->name, "HEAD", sha1) < 0)
+       if (resolve_gitlink_ref(ce->name, "HEAD", &oid) < 0)
                return 0;
-       return hashcmp(sha1, ce->oid.hash);
+       return oidcmp(&oid, &ce->oid);
 }
 
 static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
@@ -301,7 +306,7 @@ int match_stat_data_racy(const struct index_state *istate,
        return match_stat_data(sd, st);
 }
 
-int ie_match_stat(const struct index_state *istate,
+int ie_match_stat(struct index_state *istate,
                  const struct cache_entry *ce, struct stat *st,
                  unsigned int options)
 {
@@ -309,7 +314,10 @@ int ie_match_stat(const struct index_state *istate,
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
        int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
        int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
+       int ignore_fsmonitor = options & CE_MATCH_IGNORE_FSMONITOR;
 
+       if (!ignore_fsmonitor)
+               refresh_fsmonitor(istate);
        /*
         * If it's marked as always valid in the index, it's
         * valid whatever the checked-out copy says.
@@ -320,6 +328,8 @@ int ie_match_stat(const struct index_state *istate,
                return 0;
        if (!ignore_valid && (ce->ce_flags & CE_VALID))
                return 0;
+       if (!ignore_fsmonitor && (ce->ce_flags & CE_FSMONITOR_VALID))
+               return 0;
 
        /*
         * Intent-to-add entries have not been added, so the index entry
@@ -357,7 +367,7 @@ int ie_match_stat(const struct index_state *istate,
        return changed;
 }
 
-int ie_modified(const struct index_state *istate,
+int ie_modified(struct index_state *istate,
                const struct cache_entry *ce,
                struct stat *st, unsigned int options)
 {
@@ -631,13 +641,17 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 {
        int size, namelen, was_same;
        mode_t st_mode = st->st_mode;
-       struct cache_entry *ce, *alias;
+       struct cache_entry *ce, *alias = NULL;
        unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
        int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
        int pretend = flags & ADD_CACHE_PRETEND;
        int intent_only = flags & ADD_CACHE_INTENT;
        int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
                          (intent_only ? ADD_CACHE_NEW_ONLY : 0));
+       int newflags = HASH_WRITE_OBJECT;
+
+       if (flags & HASH_RENORMALIZE)
+               newflags |= HASH_RENORMALIZE;
 
        if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
                return error("%s: can only add regular files, symbolic links or git-directories", path);
@@ -678,19 +692,23 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        if (ignore_case) {
                adjust_dirname_case(istate, ce->name);
        }
+       if (!(flags & HASH_RENORMALIZE)) {
+               alias = index_file_exists(istate, ce->name,
+                                         ce_namelen(ce), ignore_case);
+               if (alias &&
+                   !ce_stage(alias) &&
+                   !ie_match_stat(istate, alias, st, ce_option)) {
+                       /* Nothing changed, really */
+                       if (!S_ISGITLINK(alias->ce_mode))
+                               ce_mark_uptodate(alias);
+                       alias->ce_flags |= CE_ADDED;
 
-       alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case);
-       if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
-               /* Nothing changed, really */
-               if (!S_ISGITLINK(alias->ce_mode))
-                       ce_mark_uptodate(alias);
-               alias->ce_flags |= CE_ADDED;
-
-               free(ce);
-               return 0;
+                       free(ce);
+                       return 0;
+               }
        }
        if (!intent_only) {
-               if (index_path(&ce->oid, path, st, HASH_WRITE_OBJECT)) {
+               if (index_path(&ce->oid, path, st, newflags)) {
                        free(ce);
                        return error("unable to index file %s", path);
                }
@@ -778,6 +796,7 @@ int chmod_index_entry(struct index_state *istate, struct cache_entry *ce,
        }
        cache_tree_invalidate_path(istate, ce->name);
        ce->ce_flags |= CE_UPDATE_IN_BASE;
+       mark_fsmonitor_invalid(istate, ce);
        istate->cache_changed |= CE_ENTRY_CHANGED;
 
        return 0;
@@ -1229,10 +1248,13 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
        int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
        int ignore_missing = options & CE_MATCH_IGNORE_MISSING;
+       int ignore_fsmonitor = options & CE_MATCH_IGNORE_FSMONITOR;
 
        if (!refresh || ce_uptodate(ce))
                return ce;
 
+       if (!ignore_fsmonitor)
+               refresh_fsmonitor(istate);
        /*
         * CE_VALID or CE_SKIP_WORKTREE means the user promised us
         * that the change to the work tree does not matter and told
@@ -1246,6 +1268,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                ce_mark_uptodate(ce);
                return ce;
        }
+       if (!ignore_fsmonitor && (ce->ce_flags & CE_FSMONITOR_VALID)) {
+               ce_mark_uptodate(ce);
+               return ce;
+       }
 
        if (has_symlink_leading_path(ce->name, ce_namelen(ce))) {
                if (ignore_missing)
@@ -1283,8 +1309,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                         * because CE_UPTODATE flag is in-core only;
                         * we are not going to write this change out.
                         */
-                       if (!S_ISGITLINK(ce->ce_mode))
+                       if (!S_ISGITLINK(ce->ce_mode)) {
                                ce_mark_uptodate(ce);
+                               mark_fsmonitor_valid(ce);
+                       }
                        return ce;
                }
        }
@@ -1392,6 +1420,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
                                 */
                                ce->ce_flags &= ~CE_VALID;
                                ce->ce_flags |= CE_UPDATE_IN_BASE;
+                               mark_fsmonitor_invalid(istate, ce);
                                istate->cache_changed |= CE_ENTRY_CHANGED;
                        }
                        if (quiet)
@@ -1511,6 +1540,9 @@ struct ondisk_cache_entry_extended {
 /* Allow fsck to force verification of the index checksum. */
 int verify_index_checksum;
 
+/* Allow fsck to force verification of the cache entry order. */
+int verify_ce_order;
+
 static int verify_hdr(struct cache_header *hdr, unsigned long size)
 {
        git_SHA_CTX c;
@@ -1551,6 +1583,9 @@ static int read_index_extension(struct index_state *istate,
        case CACHE_EXT_UNTRACKED:
                istate->untracked = read_untracked_extension(data, sz);
                break;
+       case CACHE_EXT_FSMONITOR:
+               read_fsmonitor_extension(istate, data, sz);
+               break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
                        return error("index uses %.4s extension, which we do not understand",
@@ -1668,6 +1703,9 @@ static void check_ce_order(struct index_state *istate)
 {
        unsigned int i;
 
+       if (!verify_ce_order)
+               return;
+
        for (i = 1; i < istate->cache_nr; i++) {
                struct cache_entry *ce = istate->cache[i - 1];
                struct cache_entry *next_ce = istate->cache[i];
@@ -1723,6 +1761,7 @@ static void post_read_index_from(struct index_state *istate)
        check_ce_order(istate);
        tweak_untracked_cache(istate);
        tweak_split_index(istate);
+       tweak_fsmonitor(istate);
 }
 
 /* remember to discard_cache() before reading a different cache! */
@@ -2176,17 +2215,22 @@ static int has_racy_timestamp(struct index_state *istate)
        return 0;
 }
 
-/*
- * Opportunistically update the index but do not complain if we can't
- */
 void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
 {
        if ((istate->cache_changed || has_racy_timestamp(istate)) &&
-           verify_index(istate) &&
-           write_locked_index(istate, lockfile, COMMIT_LOCK))
+           verify_index(istate))
+               write_locked_index(istate, lockfile, COMMIT_LOCK);
+       else
                rollback_lock_file(lockfile);
 }
 
+/*
+ * On success, `tempfile` is closed. If it is the temporary file
+ * of a `struct lock_file`, we will therefore effectively perform
+ * a 'close_lock_file_gently()`. Since that is an implementation
+ * detail of lockfiles, callers of `do_write_index()` should not
+ * rely on it.
+ */
 static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
                          int strip_extensions)
 {
@@ -2309,12 +2353,21 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
                if (err)
                        return -1;
        }
+       if (!strip_extensions && istate->fsmonitor_last_update) {
+               struct strbuf sb = STRBUF_INIT;
+
+               write_fsmonitor_extension(&sb, istate);
+               err = write_index_ext_header(&c, newfd, CACHE_EXT_FSMONITOR, 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))
                return -1;
        if (close_tempfile_gently(tempfile)) {
                error(_("could not close '%s'"), tempfile->filename.buf);
-               delete_tempfile(&tempfile);
                return -1;
        }
        if (stat(tempfile->filename.buf, &st))
@@ -2343,14 +2396,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
        int ret = do_write_index(istate, lock->tempfile, 0);
        if (ret)
                return ret;
-       assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) !=
-              (COMMIT_LOCK | CLOSE_LOCK));
        if (flags & COMMIT_LOCK)
                return commit_locked_index(lock);
-       else if (flags & CLOSE_LOCK)
-               return close_lock_file_gently(lock);
-       else
-               return ret;
+       return close_lock_file_gently(lock);
 }
 
 static int write_split_index(struct index_state *istate,
@@ -2495,11 +2543,15 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        int new_shared_index, ret;
        struct split_index *si = istate->split_index;
 
+       if (istate->fsmonitor_last_update)
+               fill_fsmonitor_bitmap(istate);
+
        if (!si || alternate_index_output ||
            (istate->cache_changed & ~EXTMASK)) {
                if (si)
                        hashclr(si->base_sha1);
-               return do_write_locked_index(istate, lock, flags);
+               ret = do_write_locked_index(istate, lock, flags);
+               goto out;
        }
 
        if (getenv("GIT_TEST_SPLIT_INDEX")) {
@@ -2515,7 +2567,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        if (new_shared_index) {
                ret = write_shared_index(istate, lock, flags);
                if (ret)
-                       return ret;
+                       goto out;
        }
 
        ret = write_split_index(istate, lock, flags);
@@ -2524,6 +2576,9 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        if (!ret && !new_shared_index)
                freshen_shared_index(sha1_to_hex(si->base_sha1), 1);
 
+out:
+       if (flags & COMMIT_LOCK)
+               rollback_lock_file(lock);
        return ret;
 }
 
index e728b15b3aeec8e8a9154d4ee4a4284cf06b5931..3f9161707e66be86eabaf7347da23a2450ffdd9f 100644 (file)
@@ -76,9 +76,11 @@ static struct used_atom {
                char color[COLOR_MAXLEN];
                struct align align;
                struct {
-                       enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option;
+                       enum {
+                               RR_REF, RR_TRACK, RR_TRACKSHORT, RR_REMOTE_NAME, RR_REMOTE_REF
+                       } option;
                        struct refname_atom refname;
-                       unsigned int nobracket : 1;
+                       unsigned int nobracket : 1, push : 1, push_remote : 1;
                } remote_ref;
                struct {
                        enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
@@ -138,6 +140,9 @@ static void remote_ref_atom_parser(const struct ref_format *format, struct used_
        struct string_list params = STRING_LIST_INIT_DUP;
        int i;
 
+       if (!strcmp(atom->name, "push") || starts_with(atom->name, "push:"))
+               atom->u.remote_ref.push = 1;
+
        if (!arg) {
                atom->u.remote_ref.option = RR_REF;
                refname_atom_parser_internal(&atom->u.remote_ref.refname,
@@ -157,7 +162,13 @@ static void remote_ref_atom_parser(const struct ref_format *format, struct used_
                        atom->u.remote_ref.option = RR_TRACKSHORT;
                else if (!strcmp(s, "nobracket"))
                        atom->u.remote_ref.nobracket = 1;
-               else {
+               else if (!strcmp(s, "remotename")) {
+                       atom->u.remote_ref.option = RR_REMOTE_NAME;
+                       atom->u.remote_ref.push_remote = 1;
+               } else if (!strcmp(s, "remoteref")) {
+                       atom->u.remote_ref.option = RR_REMOTE_REF;
+                       atom->u.remote_ref.push_remote = 1;
+               } else {
                        atom->u.remote_ref.option = RR_REF;
                        refname_atom_parser_internal(&atom->u.remote_ref.refname,
                                                     arg, atom->name);
@@ -1268,6 +1279,25 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                        *s = ">";
                else
                        *s = "<>";
+       } else if (atom->u.remote_ref.option == RR_REMOTE_NAME) {
+               int explicit;
+               const char *remote = atom->u.remote_ref.push ?
+                       pushremote_for_branch(branch, &explicit) :
+                       remote_for_branch(branch, &explicit);
+               if (explicit)
+                       *s = xstrdup(remote);
+               else
+                       *s = "";
+       } else if (atom->u.remote_ref.option == RR_REMOTE_REF) {
+               int explicit;
+               const char *merge;
+
+               merge = remote_ref_for_branch(branch, atom->u.remote_ref.push,
+                                             &explicit);
+               if (explicit)
+                       *s = xstrdup(merge);
+               else
+                       *s = "";
        } else
                die("BUG: unhandled RR_* enum");
 }
@@ -1377,16 +1407,20 @@ static void populate_value(struct ref_array_item *ref)
                        if (refname)
                                fill_remote_ref_details(atom, refname, branch, &v->s);
                        continue;
-               } else if (starts_with(name, "push")) {
+               } else if (atom->u.remote_ref.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;
+                       if (atom->u.remote_ref.push_remote)
+                               refname = NULL;
+                       else {
+                               refname = branch_get_push(branch, NULL);
+                               if (!refname)
+                                       continue;
+                       }
                        fill_remote_ref_details(atom, refname, branch, &v->s);
                        continue;
                } else if (starts_with(name, "color:")) {
index 842b2f77dc3e40a2eb296bfade00d9d2104ff9e1..5008bbf6ada3707009722e3a3ca5b37437fbfb24 100644 (file)
@@ -161,7 +161,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
                        struct object_id oid;
                        char *b;
                        int ret = dwim_log(branch, strlen(branch),
-                                          oid.hash, &b);
+                                          &oid, &b);
                        if (ret > 1)
                                free(b);
                        else if (ret == 1) {
diff --git a/refs.c b/refs.c
index c590a992fb19ce92dcf0932b58f83b65028f8316..20ba82b4343ff2ef72cea32deec8a8d7fbd6def7 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -194,21 +194,21 @@ int ref_resolves_to_object(const char *refname,
 
 char *refs_resolve_refdup(struct ref_store *refs,
                          const char *refname, int resolve_flags,
-                         unsigned char *sha1, int *flags)
+                         struct object_id *oid, int *flags)
 {
        const char *result;
 
        result = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
-                                        sha1, flags);
+                                        oid, flags);
        return xstrdup_or_null(result);
 }
 
 char *resolve_refdup(const char *refname, int resolve_flags,
-                    unsigned char *sha1, int *flags)
+                    struct object_id *oid, int *flags)
 {
        return refs_resolve_refdup(get_main_ref_store(),
                                   refname, resolve_flags,
-                                  sha1, flags);
+                                  oid, flags);
 }
 
 /* The argument to filter_refs */
@@ -219,22 +219,22 @@ struct ref_filter {
 };
 
 int refs_read_ref_full(struct ref_store *refs, const char *refname,
-                      int resolve_flags, unsigned char *sha1, int *flags)
+                      int resolve_flags, struct object_id *oid, int *flags)
 {
-       if (refs_resolve_ref_unsafe(refs, refname, resolve_flags, sha1, flags))
+       if (refs_resolve_ref_unsafe(refs, refname, resolve_flags, oid, flags))
                return 0;
        return -1;
 }
 
-int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
+int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
 {
        return refs_read_ref_full(get_main_ref_store(), refname,
-                                 resolve_flags, sha1, flags);
+                                 resolve_flags, oid, flags);
 }
 
-int read_ref(const char *refname, unsigned char *sha1)
+int read_ref(const char *refname, struct object_id *oid)
 {
-       return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
+       return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
 }
 
 int ref_exists(const char *refname)
@@ -242,6 +242,50 @@ int ref_exists(const char *refname)
        return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL);
 }
 
+static int match_ref_pattern(const char *refname,
+                            const struct string_list_item *item)
+{
+       int matched = 0;
+       if (item->util == NULL) {
+               if (!wildmatch(item->string, refname, 0))
+                       matched = 1;
+       } else {
+               const char *rest;
+               if (skip_prefix(refname, item->string, &rest) &&
+                   (!*rest || *rest == '/'))
+                       matched = 1;
+       }
+       return matched;
+}
+
+int ref_filter_match(const char *refname,
+                    const struct string_list *include_patterns,
+                    const struct string_list *exclude_patterns)
+{
+       struct string_list_item *item;
+
+       if (exclude_patterns && exclude_patterns->nr) {
+               for_each_string_list_item(item, exclude_patterns) {
+                       if (match_ref_pattern(refname, item))
+                               return 0;
+               }
+       }
+
+       if (include_patterns && include_patterns->nr) {
+               int found = 0;
+               for_each_string_list_item(item, include_patterns) {
+                       if (match_ref_pattern(refname, item)) {
+                               found = 1;
+                               break;
+                       }
+               }
+
+               if (!found)
+                       return 0;
+       }
+       return 1;
+}
+
 static int filter_refs(const char *refname, const struct object_id *oid,
                           int flags, void *data)
 {
@@ -252,12 +296,12 @@ static int filter_refs(const char *refname, const struct object_id *oid,
        return filter->fn(refname, oid, flags, filter->cb_data);
 }
 
-enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
+enum peel_status peel_object(const struct object_id *name, struct object_id *oid)
 {
-       struct object *o = lookup_unknown_object(name);
+       struct object *o = lookup_unknown_object(name->hash);
 
        if (o->type == OBJ_NONE) {
-               int type = sha1_object_info(name, NULL);
+               int type = sha1_object_info(name->hash, NULL);
                if (type < 0 || !object_as_type(o, type, 0))
                        return PEEL_INVALID;
        }
@@ -269,7 +313,7 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
        if (!o)
                return PEEL_INVALID;
 
-       hashcpy(sha1, o->oid.hash);
+       oidcpy(oid, &o->oid);
        return PEEL_PEELED;
 }
 
@@ -362,13 +406,34 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
        int flag;
 
        strbuf_addf(&buf, "%sHEAD", get_git_namespace());
-       if (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag))
+       if (!read_ref_full(buf.buf, RESOLVE_REF_READING, &oid, &flag))
                ret = fn(buf.buf, &oid, flag, cb_data);
        strbuf_release(&buf);
 
        return ret;
 }
 
+void normalize_glob_ref(struct string_list_item *item, const char *prefix,
+                       const char *pattern)
+{
+       struct strbuf normalized_pattern = STRBUF_INIT;
+
+       if (*pattern == '/')
+               BUG("pattern must not start with '/'");
+
+       if (prefix) {
+               strbuf_addstr(&normalized_pattern, prefix);
+       }
+       else if (!starts_with(pattern, "refs/"))
+               strbuf_addstr(&normalized_pattern, "refs/");
+       strbuf_addstr(&normalized_pattern, pattern);
+       strbuf_strip_suffix(&normalized_pattern, "/");
+
+       item->string = strbuf_detach(&normalized_pattern, NULL);
+       item->util = has_glob_specials(pattern) ? NULL : item->string;
+       strbuf_release(&normalized_pattern);
+}
+
 int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
        const char *prefix, void *cb_data)
 {
@@ -456,15 +521,15 @@ static char *substitute_branch_name(const char **string, int *len)
        return NULL;
 }
 
-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
+int dwim_ref(const char *str, int len, struct object_id *oid, char **ref)
 {
        char *last_branch = substitute_branch_name(&str, &len);
-       int   refs_found  = expand_ref(str, len, sha1, ref);
+       int   refs_found  = expand_ref(str, len, oid, ref);
        free(last_branch);
        return refs_found;
 }
 
-int expand_ref(const char *str, int len, unsigned char *sha1, char **ref)
+int expand_ref(const char *str, int len, struct object_id *oid, char **ref)
 {
        const char **p, *r;
        int refs_found = 0;
@@ -472,11 +537,11 @@ int expand_ref(const char *str, int len, unsigned char *sha1, char **ref)
 
        *ref = NULL;
        for (p = ref_rev_parse_rules; *p; p++) {
-               unsigned char sha1_from_ref[20];
-               unsigned char *this_result;
+               struct object_id oid_from_ref;
+               struct object_id *this_result;
                int flag;
 
-               this_result = refs_found ? sha1_from_ref : sha1;
+               this_result = refs_found ? &oid_from_ref : oid;
                strbuf_reset(&fullref);
                strbuf_addf(&fullref, *p, len, str);
                r = resolve_ref_unsafe(fullref.buf, RESOLVE_REF_READING,
@@ -496,7 +561,7 @@ int expand_ref(const char *str, int len, unsigned char *sha1, char **ref)
        return refs_found;
 }
 
-int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
+int dwim_log(const char *str, int len, struct object_id *oid, char **log)
 {
        char *last_branch = substitute_branch_name(&str, &len);
        const char **p;
@@ -505,13 +570,13 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 
        *log = NULL;
        for (p = ref_rev_parse_rules; *p; p++) {
-               unsigned char hash[20];
+               struct object_id hash;
                const char *ref, *it;
 
                strbuf_reset(&path);
                strbuf_addf(&path, *p, len, str);
                ref = resolve_ref_unsafe(path.buf, RESOLVE_REF_READING,
-                                        hash, NULL);
+                                        &hash, NULL);
                if (!ref)
                        continue;
                if (reflog_exists(path.buf))
@@ -522,7 +587,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
                        continue;
                if (!logs_found++) {
                        *log = xstrdup(it);
-                       hashcpy(sha1, hash);
+                       oidcpy(oid, &hash);
                }
                if (!warn_ambiguous_refs)
                        break;
@@ -574,8 +639,8 @@ long get_files_ref_lock_timeout_ms(void)
        return timeout_ms;
 }
 
-static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
-                          const unsigned char *old_sha1, struct strbuf *err)
+static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
+                          const struct object_id *old_oid, struct strbuf *err)
 {
        const char *filename;
        int fd;
@@ -583,7 +648,10 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
        struct strbuf buf = STRBUF_INIT;
        int ret = -1;
 
-       strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+       if (!oid)
+               return 0;
+
+       strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
 
        filename = git_path("%s", pseudoref);
        fd = hold_lock_file_for_update_timeout(&lock, filename,
@@ -595,12 +663,12 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
                goto done;
        }
 
-       if (old_sha1) {
-               unsigned char actual_old_sha1[20];
+       if (old_oid) {
+               struct object_id actual_old_oid;
 
-               if (read_ref(pseudoref, actual_old_sha1))
+               if (read_ref(pseudoref, &actual_old_oid))
                        die("could not read ref '%s'", pseudoref);
-               if (hashcmp(actual_old_sha1, old_sha1)) {
+               if (oidcmp(&actual_old_oid, old_oid)) {
                        strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
@@ -620,25 +688,25 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
        return ret;
 }
 
-static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
+static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
 {
        static struct lock_file lock;
        const char *filename;
 
        filename = git_path("%s", pseudoref);
 
-       if (old_sha1 && !is_null_sha1(old_sha1)) {
+       if (old_oid && !is_null_oid(old_oid)) {
                int fd;
-               unsigned char actual_old_sha1[20];
+               struct object_id actual_old_oid;
 
                fd = hold_lock_file_for_update_timeout(
                                &lock, filename, LOCK_DIE_ON_ERROR,
                                get_files_ref_lock_timeout_ms());
                if (fd < 0)
                        die_errno(_("Could not open '%s' for writing"), filename);
-               if (read_ref(pseudoref, actual_old_sha1))
+               if (read_ref(pseudoref, &actual_old_oid))
                        die("could not read ref '%s'", pseudoref);
-               if (hashcmp(actual_old_sha1, old_sha1)) {
+               if (oidcmp(&actual_old_oid, old_oid)) {
                        warning("Unexpected sha1 when deleting %s", pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
@@ -655,7 +723,7 @@ static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1
 
 int refs_delete_ref(struct ref_store *refs, const char *msg,
                    const char *refname,
-                   const unsigned char *old_sha1,
+                   const struct object_id *old_oid,
                    unsigned int flags)
 {
        struct ref_transaction *transaction;
@@ -663,12 +731,12 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
 
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
                assert(refs == get_main_ref_store());
-               return delete_pseudoref(refname, old_sha1);
+               return delete_pseudoref(refname, old_oid);
        }
 
        transaction = ref_store_transaction_begin(refs, &err);
        if (!transaction ||
-           ref_transaction_delete(transaction, refname, old_sha1,
+           ref_transaction_delete(transaction, refname, old_oid,
                                   flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
@@ -682,10 +750,10 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
 }
 
 int delete_ref(const char *msg, const char *refname,
-              const unsigned char *old_sha1, unsigned int flags)
+              const struct object_id *old_oid, unsigned int flags)
 {
        return refs_delete_ref(get_main_ref_store(), msg, refname,
-                              old_sha1, flags);
+                              old_oid, flags);
 }
 
 int copy_reflog_msg(char *buf, const char *msg)
@@ -734,11 +802,11 @@ struct read_ref_at_cb {
        timestamp_t at_time;
        int cnt;
        int reccnt;
-       unsigned char *sha1;
+       struct object_id *oid;
        int found_it;
 
-       unsigned char osha1[20];
-       unsigned char nsha1[20];
+       struct object_id ooid;
+       struct object_id noid;
        int tz;
        timestamp_t date;
        char **msg;
@@ -767,28 +835,28 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
                if (cb->cutoff_cnt)
                        *cb->cutoff_cnt = cb->reccnt - 1;
                /*
-                * we have not yet updated cb->[n|o]sha1 so they still
+                * we have not yet updated cb->[n|o]oid so they still
                 * hold the values for the previous record.
                 */
-               if (!is_null_sha1(cb->osha1)) {
-                       hashcpy(cb->sha1, noid->hash);
-                       if (hashcmp(cb->osha1, noid->hash))
+               if (!is_null_oid(&cb->ooid)) {
+                       oidcpy(cb->oid, noid);
+                       if (oidcmp(&cb->ooid, noid))
                                warning("Log for ref %s has gap after %s.",
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
-                       hashcpy(cb->sha1, noid->hash);
-               else if (hashcmp(noid->hash, cb->sha1))
+                       oidcpy(cb->oid, noid);
+               else if (oidcmp(noid, cb->oid))
                        warning("Log for ref %s unexpectedly ended on %s.",
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
-               hashcpy(cb->osha1, ooid->hash);
-               hashcpy(cb->nsha1, noid->hash);
+               oidcpy(&cb->ooid, ooid);
+               oidcpy(&cb->noid, noid);
                cb->found_it = 1;
                return 1;
        }
-       hashcpy(cb->osha1, ooid->hash);
-       hashcpy(cb->nsha1, noid->hash);
+       oidcpy(&cb->ooid, ooid);
+       oidcpy(&cb->noid, noid);
        if (cb->cnt > 0)
                cb->cnt--;
        return 0;
@@ -808,15 +876,15 @@ static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid
                *cb->cutoff_tz = tz;
        if (cb->cutoff_cnt)
                *cb->cutoff_cnt = cb->reccnt;
-       hashcpy(cb->sha1, ooid->hash);
-       if (is_null_sha1(cb->sha1))
-               hashcpy(cb->sha1, noid->hash);
+       oidcpy(cb->oid, ooid);
+       if (is_null_oid(cb->oid))
+               oidcpy(cb->oid, noid);
        /* We just want the first entry */
        return 1;
 }
 
 int read_ref_at(const char *refname, unsigned int flags, timestamp_t at_time, int cnt,
-               unsigned char *sha1, char **msg,
+               struct object_id *oid, char **msg,
                timestamp_t *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
        struct read_ref_at_cb cb;
@@ -829,7 +897,7 @@ int read_ref_at(const char *refname, unsigned int flags, timestamp_t at_time, in
        cb.cutoff_time = cutoff_time;
        cb.cutoff_tz = cutoff_tz;
        cb.cutoff_cnt = cutoff_cnt;
-       cb.sha1 = sha1;
+       cb.oid = oid;
 
        for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
 
@@ -894,8 +962,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
 struct ref_update *ref_transaction_add_update(
                struct ref_transaction *transaction,
                const char *refname, unsigned int flags,
-               const unsigned char *new_sha1,
-               const unsigned char *old_sha1,
+               const struct object_id *new_oid,
+               const struct object_id *old_oid,
                const char *msg)
 {
        struct ref_update *update;
@@ -903,9 +971,6 @@ struct ref_update *ref_transaction_add_update(
        if (transaction->state != REF_TRANSACTION_OPEN)
                die("BUG: update called for transaction that is not open");
 
-       if ((flags & REF_ISPRUNING) && !(flags & REF_NODEREF))
-               die("BUG: REF_ISPRUNING set without REF_NODEREF");
-
        FLEX_ALLOC_STR(update, refname, refname);
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
        transaction->updates[transaction->nr++] = update;
@@ -913,23 +978,23 @@ struct ref_update *ref_transaction_add_update(
        update->flags = flags;
 
        if (flags & REF_HAVE_NEW)
-               hashcpy(update->new_oid.hash, new_sha1);
+               oidcpy(&update->new_oid, new_oid);
        if (flags & REF_HAVE_OLD)
-               hashcpy(update->old_oid.hash, old_sha1);
+               oidcpy(&update->old_oid, old_oid);
        update->msg = xstrdup_or_null(msg);
        return update;
 }
 
 int ref_transaction_update(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *new_sha1,
-                          const unsigned char *old_sha1,
+                          const struct object_id *new_oid,
+                          const struct object_id *old_oid,
                           unsigned int flags, const char *msg,
                           struct strbuf *err)
 {
        assert(err);
 
-       if ((new_sha1 && !is_null_sha1(new_sha1)) ?
+       if ((new_oid && !is_null_oid(new_oid)) ?
            check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
            !refname_is_safe(refname)) {
                strbuf_addf(err, "refusing to update ref with bad name '%s'",
@@ -937,64 +1002,57 @@ int ref_transaction_update(struct ref_transaction *transaction,
                return -1;
        }
 
-       flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
+       if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
+               BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
 
-       flags |= (new_sha1 ? REF_HAVE_NEW : 0) | (old_sha1 ? REF_HAVE_OLD : 0);
+       flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
 
        ref_transaction_add_update(transaction, refname, flags,
-                                  new_sha1, old_sha1, msg);
+                                  new_oid, old_oid, msg);
        return 0;
 }
 
 int ref_transaction_create(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *new_sha1,
+                          const struct object_id *new_oid,
                           unsigned int flags, const char *msg,
                           struct strbuf *err)
 {
-       if (!new_sha1 || is_null_sha1(new_sha1))
-               die("BUG: create called without valid new_sha1");
-       return ref_transaction_update(transaction, refname, new_sha1,
-                                     null_sha1, flags, msg, err);
+       if (!new_oid || is_null_oid(new_oid))
+               die("BUG: create called without valid new_oid");
+       return ref_transaction_update(transaction, refname, new_oid,
+                                     &null_oid, flags, msg, err);
 }
 
 int ref_transaction_delete(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *old_sha1,
+                          const struct object_id *old_oid,
                           unsigned int flags, const char *msg,
                           struct strbuf *err)
 {
-       if (old_sha1 && is_null_sha1(old_sha1))
-               die("BUG: delete called with old_sha1 set to zeros");
+       if (old_oid && is_null_oid(old_oid))
+               die("BUG: delete called with old_oid set to zeros");
        return ref_transaction_update(transaction, refname,
-                                     null_sha1, old_sha1,
+                                     &null_oid, old_oid,
                                      flags, msg, err);
 }
 
 int ref_transaction_verify(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *old_sha1,
+                          const struct object_id *old_oid,
                           unsigned int flags,
                           struct strbuf *err)
 {
-       if (!old_sha1)
-               die("BUG: verify called with old_sha1 set to NULL");
+       if (!old_oid)
+               die("BUG: verify called with old_oid set to NULL");
        return ref_transaction_update(transaction, refname,
-                                     NULL, old_sha1,
+                                     NULL, old_oid,
                                      flags, NULL, err);
 }
 
-int update_ref_oid(const char *msg, const char *refname,
-              const struct object_id *new_oid, const struct object_id *old_oid,
-              unsigned int flags, enum action_on_err onerr)
-{
-       return update_ref(msg, refname, new_oid ? new_oid->hash : NULL,
-               old_oid ? old_oid->hash : NULL, flags, onerr);
-}
-
 int refs_update_ref(struct ref_store *refs, const char *msg,
-                   const char *refname, const unsigned char *new_sha1,
-                   const unsigned char *old_sha1, unsigned int flags,
+                   const char *refname, const struct object_id *new_oid,
+                   const struct object_id *old_oid, unsigned int flags,
                    enum action_on_err onerr)
 {
        struct ref_transaction *t = NULL;
@@ -1003,11 +1061,11 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
                assert(refs == get_main_ref_store());
-               ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
+               ret = write_pseudoref(refname, new_oid, old_oid, &err);
        } else {
                t = ref_store_transaction_begin(refs, &err);
                if (!t ||
-                   ref_transaction_update(t, refname, new_sha1, old_sha1,
+                   ref_transaction_update(t, refname, new_oid, old_oid,
                                           flags, msg, &err) ||
                    ref_transaction_commit(t, &err)) {
                        ret = 1;
@@ -1037,12 +1095,12 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 }
 
 int update_ref(const char *msg, const char *refname,
-              const unsigned char *new_sha1,
-              const unsigned char *old_sha1,
+              const struct object_id *new_oid,
+              const struct object_id *old_oid,
               unsigned int flags, enum action_on_err onerr)
 {
-       return refs_update_ref(get_main_ref_store(), msg, refname, new_sha1,
-                              old_sha1, flags, onerr);
+       return refs_update_ref(get_main_ref_store(), msg, refname, new_oid,
+                              old_oid, flags, onerr);
 }
 
 char *shorten_unambiguous_ref(const char *refname, int strict)
@@ -1254,7 +1312,7 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
        int flag;
 
        if (!refs_read_ref_full(refs, "HEAD", RESOLVE_REF_READING,
-                               oid.hash, &flag))
+                               &oid, &flag))
                return fn("HEAD", &oid, flag, cb_data);
 
        return 0;
@@ -1387,25 +1445,25 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
 }
 
 int refs_read_raw_ref(struct ref_store *ref_store,
-                     const char *refname, unsigned char *sha1,
+                     const char *refname, struct object_id *oid,
                      struct strbuf *referent, unsigned int *type)
 {
-       return ref_store->be->read_raw_ref(ref_store, refname, sha1, referent, type);
+       return ref_store->be->read_raw_ref(ref_store, refname, oid, referent, type);
 }
 
 /* This function needs to return a meaningful errno on failure */
 const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                                    const char *refname,
                                    int resolve_flags,
-                                   unsigned char *sha1, int *flags)
+                                   struct object_id *oid, int *flags)
 {
        static struct strbuf sb_refname = STRBUF_INIT;
        struct object_id unused_oid;
        int unused_flags;
        int symref_count;
 
-       if (!sha1)
-               sha1 = unused_oid.hash;
+       if (!oid)
+               oid = &unused_oid;
        if (!flags)
                flags = &unused_flags;
 
@@ -1433,7 +1491,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                unsigned int read_flags = 0;
 
                if (refs_read_raw_ref(refs, refname,
-                                     sha1, &sb_refname, &read_flags)) {
+                                     oid, &sb_refname, &read_flags)) {
                        *flags |= read_flags;
 
                        /* In reading mode, refs must eventually resolve */
@@ -1450,7 +1508,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                            errno != ENOTDIR)
                                return NULL;
 
-                       hashclr(sha1);
+                       oidclr(oid);
                        if (*flags & REF_BAD_NAME)
                                *flags |= REF_ISBROKEN;
                        return refname;
@@ -1460,7 +1518,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 
                if (!(read_flags & REF_ISSYMREF)) {
                        if (*flags & REF_BAD_NAME) {
-                               hashclr(sha1);
+                               oidclr(oid);
                                *flags |= REF_ISBROKEN;
                        }
                        return refname;
@@ -1468,7 +1526,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 
                refname = sb_refname.buf;
                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
-                       hashclr(sha1);
+                       oidclr(oid);
                        return refname;
                }
                if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
@@ -1495,14 +1553,14 @@ int refs_init_db(struct strbuf *err)
 }
 
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
-                              unsigned char *sha1, int *flags)
+                              struct object_id *oid, int *flags)
 {
        return refs_resolve_ref_unsafe(get_main_ref_store(), refname,
-                                      resolve_flags, sha1, flags);
+                                      resolve_flags, oid, flags);
 }
 
 int resolve_gitlink_ref(const char *submodule, const char *refname,
-                       unsigned char *sha1)
+                       struct object_id *oid)
 {
        struct ref_store *refs;
        int flags;
@@ -1512,8 +1570,8 @@ int resolve_gitlink_ref(const char *submodule, const char *refname,
        if (!refs)
                return -1;
 
-       if (!refs_resolve_ref_unsafe(refs, refname, 0, sha1, &flags) ||
-           is_null_sha1(sha1))
+       if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||
+           is_null_oid(oid))
                return -1;
        return 0;
 }
@@ -1701,30 +1759,30 @@ int refs_pack_refs(struct ref_store *refs, unsigned int flags)
 }
 
 int refs_peel_ref(struct ref_store *refs, const char *refname,
-                 unsigned char *sha1)
+                 struct object_id *oid)
 {
        int flag;
-       unsigned char base[20];
+       struct object_id base;
 
        if (current_ref_iter && current_ref_iter->refname == refname) {
                struct object_id peeled;
 
                if (ref_iterator_peel(current_ref_iter, &peeled))
                        return -1;
-               hashcpy(sha1, peeled.hash);
+               oidcpy(oid, &peeled);
                return 0;
        }
 
        if (refs_read_ref_full(refs, refname,
-                              RESOLVE_REF_READING, base, &flag))
+                              RESOLVE_REF_READING, &base, &flag))
                return -1;
 
-       return peel_object(base, sha1);
+       return peel_object(&base, oid);
 }
 
-int peel_ref(const char *refname, unsigned char *sha1)
+int peel_ref(const char *refname, struct object_id *oid)
 {
-       return refs_peel_ref(get_main_ref_store(), refname, sha1);
+       return refs_peel_ref(get_main_ref_store(), refname, oid);
 }
 
 int refs_create_symref(struct ref_store *refs,
@@ -1884,7 +1942,7 @@ int refs_verify_refname_available(struct ref_store *refs,
                if (skip && string_list_has_string(skip, dirname.buf))
                        continue;
 
-               if (!refs_read_raw_ref(refs, dirname.buf, oid.hash, &referent, &type)) {
+               if (!refs_read_raw_ref(refs, dirname.buf, &oid, &referent, &type)) {
                        strbuf_addf(err, "'%s' exists; cannot create '%s'",
                                    dirname.buf, refname);
                        goto cleanup;
@@ -2014,19 +2072,19 @@ int delete_reflog(const char *refname)
 }
 
 int refs_reflog_expire(struct ref_store *refs,
-                      const char *refname, const unsigned char *sha1,
+                      const char *refname, const struct object_id *oid,
                       unsigned int flags,
                       reflog_expiry_prepare_fn prepare_fn,
                       reflog_expiry_should_prune_fn should_prune_fn,
                       reflog_expiry_cleanup_fn cleanup_fn,
                       void *policy_cb_data)
 {
-       return refs->be->reflog_expire(refs, refname, sha1, flags,
+       return refs->be->reflog_expire(refs, refname, oid, flags,
                                       prepare_fn, should_prune_fn,
                                       cleanup_fn, policy_cb_data);
 }
 
-int reflog_expire(const char *refname, const unsigned char *sha1,
+int reflog_expire(const char *refname, const struct object_id *oid,
                  unsigned int flags,
                  reflog_expiry_prepare_fn prepare_fn,
                  reflog_expiry_should_prune_fn should_prune_fn,
@@ -2034,7 +2092,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
                  void *policy_cb_data)
 {
        return refs_reflog_expire(get_main_ref_store(),
-                                 refname, sha1, flags,
+                                 refname, oid, flags,
                                  prepare_fn, should_prune_fn,
                                  cleanup_fn, policy_cb_data);
 }
diff --git a/refs.h b/refs.h
index a02b628c8fe81b648f13c66b7e618d8660151e77..01be5ae32fb01298ff6c0738ac4adfe42643b682 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -14,22 +14,22 @@ struct worktree;
  * at the resolved object name.  The return value, if not NULL, is a
  * pointer into either a static buffer or the input ref.
  *
- * If sha1 is non-NULL, store the referred-to object's name in it.
+ * If oid is non-NULL, store the referred-to object's name in it.
  *
  * If the reference cannot be resolved to an object, the behavior
  * depends on the RESOLVE_REF_READING flag:
  *
  * - If RESOLVE_REF_READING is set, return NULL.
  *
- * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
+ * - If RESOLVE_REF_READING is not set, clear oid and return the name of
  *   the last reference name in the chain, which will either be a non-symbolic
  *   reference or an undefined reference.  If this is a prelude to
  *   "writing" to the ref, the return value is the name of the ref
  *   that will actually be created or changed.
  *
  * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
- * level of symbolic reference.  The value stored in sha1 for a symbolic
- * reference will always be null_sha1 in this case, and the return
+ * level of symbolic reference.  The value stored in oid for a symbolic
+ * reference will always be null_oid in this case, and the return
  * value is the reference that the symref refers to directly.
  *
  * If flags is non-NULL, set the value that it points to the
@@ -46,7 +46,7 @@ struct worktree;
  *
  * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
  * name is invalid according to git-check-ref-format(1).  If the name
- * is bad then the value stored in sha1 will be null_sha1 and the two
+ * is bad then the value stored in oid will be null_oid and the two
  * flags REF_ISBROKEN and REF_BAD_NAME will be set.
  *
  * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
@@ -62,22 +62,22 @@ struct worktree;
 const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                                    const char *refname,
                                    int resolve_flags,
-                                   unsigned char *sha1,
+                                   struct object_id *oid,
                                    int *flags);
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
-                              unsigned char *sha1, int *flags);
+                              struct object_id *oid, int *flags);
 
 char *refs_resolve_refdup(struct ref_store *refs,
                          const char *refname, int resolve_flags,
-                         unsigned char *sha1, int *flags);
+                         struct object_id *oid, int *flags);
 char *resolve_refdup(const char *refname, int resolve_flags,
-                    unsigned char *sha1, int *flags);
+                    struct object_id *oid, int *flags);
 
 int refs_read_ref_full(struct ref_store *refs, const char *refname,
-                      int resolve_flags, unsigned char *sha1, int *flags);
+                      int resolve_flags, struct object_id *oid, int *flags);
 int read_ref_full(const char *refname, int resolve_flags,
-                 unsigned char *sha1, int *flags);
-int read_ref(const char *refname, unsigned char *sha1);
+                 struct object_id *oid, int *flags);
+int read_ref(const char *refname, struct object_id *oid);
 
 /*
  * Return 0 if a reference named refname could be created without
@@ -114,23 +114,23 @@ extern int refs_init_db(struct strbuf *err);
 /*
  * If refname is a non-symbolic reference that refers to a tag object,
  * and the tag can be (recursively) dereferenced to a non-tag object,
- * store the SHA1 of the referred-to object to sha1 and return 0.  If
- * any of these conditions are not met, return a non-zero value.
+ * store the object ID of the referred-to object to oid and return 0.
+ * If any of these conditions are not met, return a non-zero value.
  * Symbolic references are considered unpeelable, even if they
  * ultimately resolve to a peelable tag.
  */
 int refs_peel_ref(struct ref_store *refs, const char *refname,
-                 unsigned char *sha1);
-int peel_ref(const char *refname, unsigned char *sha1);
+                 struct object_id *oid);
+int peel_ref(const char *refname, struct object_id *oid);
 
 /**
  * Resolve refname in the nested "gitlink" repository in the specified
  * submodule (which must be non-NULL). If the resolution is
- * successful, return 0 and set sha1 to the name of the object;
+ * successful, return 0 and set oid to the name of the object;
  * otherwise, return a non-zero value.
  */
 int resolve_gitlink_ref(const char *submodule, const char *refname,
-                       unsigned char *sha1);
+                       struct object_id *oid);
 
 /*
  * Return true iff abbrev_name is a possible abbreviation for
@@ -139,9 +139,9 @@ int resolve_gitlink_ref(const char *submodule, const char *refname,
  */
 int refname_match(const char *abbrev_name, const char *full_name);
 
-int expand_ref(const char *str, int len, unsigned char *sha1, char **ref);
-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
-int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+int expand_ref(const char *str, int len, struct object_id *oid, char **ref);
+int dwim_ref(const char *str, int len, struct object_id *oid, char **ref);
+int dwim_log(const char *str, int len, struct object_id *oid, char **ref);
 
 /*
  * A ref_transaction represents a collection of reference updates that
@@ -260,7 +260,7 @@ struct ref_transaction;
 
 /*
  * The signature for the callback function for the for_each_*()
- * functions below.  The memory pointed to by the refname and sha1
+ * functions below.  The memory pointed to by the refname and oid
  * arguments is only guaranteed to be valid for the duration of a
  * single callback invocation.
  */
@@ -312,6 +312,30 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
 int for_each_rawref(each_ref_fn fn, void *cb_data);
 
+/*
+ * Normalizes partial refs to their fully qualified form.
+ * Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
+ * <prefix> will default to 'refs/' if NULL.
+ *
+ * item.string will be set to the result.
+ * item.util will be set to NULL if <pattern> contains glob characters, or
+ * non-NULL if it doesn't.
+ */
+void normalize_glob_ref(struct string_list_item *item, const char *prefix,
+                       const char *pattern);
+
+/*
+ * Returns 0 if refname matches any of the exclude_patterns, or if it doesn't
+ * match any of the include_patterns. Returns 1 otherwise.
+ *
+ * If pattern list is NULL or empty, matching against that list is skipped.
+ * This has the effect of matching everything by default, unless the user
+ * specifies rules otherwise.
+ */
+int ref_filter_match(const char *refname,
+                    const struct string_list *include_patterns,
+                    const struct string_list *exclude_patterns);
+
 static inline const char *has_glob_specials(const char *pattern)
 {
        return strpbrk(pattern, "?*[");
@@ -335,24 +359,6 @@ void warn_dangling_symrefs(FILE *fp, const char *msg_fmt,
  */
 int refs_pack_refs(struct ref_store *refs, unsigned int flags);
 
-/*
- * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.
- * REF_NODEREF: act on the ref directly, instead of dereferencing
- *              symbolic references.
- *
- * Other flags are reserved for internal use.
- */
-#define REF_NODEREF    0x01
-#define REF_FORCE_CREATE_REFLOG 0x40
-
-/*
- * Flags that can be passed in to ref_transaction_update
- */
-#define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
-       REF_ISPRUNING |                      \
-       REF_FORCE_CREATE_REFLOG |            \
-       REF_NODEREF
-
 /*
  * Setup reflog before using. Fill in err and return -1 on failure.
  */
@@ -363,7 +369,7 @@ int safe_create_reflog(const char *refname, int force_create, struct strbuf *err
 /** Reads log for the value of ref during at_time. **/
 int read_ref_at(const char *refname, unsigned int flags,
                timestamp_t at_time, int cnt,
-               unsigned char *sha1, char **msg,
+               struct object_id *oid, char **msg,
                timestamp_t *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
 
 /** Check if a particular reflog exists */
@@ -371,19 +377,19 @@ int refs_reflog_exists(struct ref_store *refs, const char *refname);
 int reflog_exists(const char *refname);
 
 /*
- * Delete the specified reference. If old_sha1 is non-NULL, then
- * verify that the current value of the reference is old_sha1 before
- * deleting it. If old_sha1 is NULL, delete the reference if it
- * exists, regardless of its old value. It is an error for old_sha1 to
- * be NULL_SHA1. msg and flags are passed through to
+ * Delete the specified reference. If old_oid is non-NULL, then
+ * verify that the current value of the reference is old_oid before
+ * deleting it. If old_oid is NULL, delete the reference if it
+ * exists, regardless of its old value. It is an error for old_oid to
+ * be null_oid. msg and flags are passed through to
  * ref_transaction_delete().
  */
 int refs_delete_ref(struct ref_store *refs, const char *msg,
                    const char *refname,
-                   const unsigned char *old_sha1,
+                   const struct object_id *old_oid,
                    unsigned int flags);
 int delete_ref(const char *msg, const char *refname,
-              const unsigned char *old_sha1, unsigned int flags);
+              const struct object_id *old_oid, unsigned int flags);
 
 /*
  * Delete the specified references. If there are any problems, emit
@@ -480,22 +486,23 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
  *
  *     refname -- the name of the reference to be affected.
  *
- *     new_sha1 -- the SHA-1 that should be set to be the new value of
- *         the reference. Some functions allow this parameter to be
+ *     new_oid -- the object ID that should be set to be the new value
+ *         of the reference. Some functions allow this parameter to be
  *         NULL, meaning that the reference is not changed, or
- *         null_sha1, meaning that the reference should be deleted. A
+ *         null_oid, meaning that the reference should be deleted. A
  *         copy of this value is made in the transaction.
  *
- *     old_sha1 -- the SHA-1 value that the reference must have before
+ *     old_oid -- the object ID that the reference must have before
  *         the update. Some functions allow this parameter to be NULL,
  *         meaning that the old value of the reference is not checked,
- *         or null_sha1, meaning that the reference must not exist
+ *         or null_oid, meaning that the reference must not exist
  *         before the update. A copy of this value is made in the
  *         transaction.
  *
  *     flags -- flags affecting the update, passed to
- *         update_ref_lock(). Can be REF_NODEREF, which means that
- *         symbolic references should not be followed.
+ *         update_ref_lock(). Possible flags: REF_NO_DEREF,
+ *         REF_FORCE_CREATE_REFLOG. See those constants for more
+ *         information.
  *
  *     msg -- a message describing the change (for the reflog).
  *
@@ -511,14 +518,40 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
  */
 
 /*
- * Add a reference update to transaction. new_sha1 is the value that
- * the reference should have after the update, or null_sha1 if it
- * should be deleted. If new_sha1 is NULL, then the reference is not
- * changed at all. old_sha1 is the value that the reference must have
- * before the update, or null_sha1 if it must not have existed
+ * The following flags can be passed to ref_transaction_update() etc.
+ * Internally, they are stored in `ref_update::flags`, along with some
+ * internal flags.
+ */
+
+/*
+ * Act on the ref directly; i.e., without dereferencing symbolic refs.
+ * If this flag is not specified, then symbolic references are
+ * dereferenced and the update is applied to the referent.
+ */
+#define REF_NO_DEREF (1 << 0)
+
+/*
+ * Force the creation of a reflog for this reference, even if it
+ * didn't previously have a reflog.
+ */
+#define REF_FORCE_CREATE_REFLOG (1 << 1)
+
+/*
+ * Bitmask of all of the flags that are allowed to be passed in to
+ * ref_transaction_update() and friends:
+ */
+#define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
+       (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG)
+
+/*
+ * Add a reference update to transaction. `new_oid` is the value that
+ * the reference should have after the update, or `null_oid` if it
+ * should be deleted. If `new_oid` is NULL, then the reference is not
+ * changed at all. `old_oid` is the value that the reference must have
+ * before the update, or `null_oid` if it must not have existed
  * beforehand. The old value is checked after the lock is taken to
- * prevent races. If the old value doesn't agree with old_sha1, the
- * whole transaction fails. If old_sha1 is NULL, then the previous
+ * prevent races. If the old value doesn't agree with old_oid, the
+ * whole transaction fails. If old_oid is NULL, then the previous
  * value is not checked.
  *
  * See the above comment "Reference transaction updates" for more
@@ -526,15 +559,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
  */
 int ref_transaction_update(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *new_sha1,
-                          const unsigned char *old_sha1,
+                          const struct object_id *new_oid,
+                          const struct object_id *old_oid,
                           unsigned int flags, const char *msg,
                           struct strbuf *err);
 
 /*
- * Add a reference creation to transaction. new_sha1 is the value that
+ * Add a reference creation to transaction. new_oid is the value that
  * the reference should have after the update; it must not be
- * null_sha1. It is verified that the reference does not exist
+ * null_oid. It is verified that the reference does not exist
  * already.
  *
  * See the above comment "Reference transaction updates" for more
@@ -542,35 +575,35 @@ int ref_transaction_update(struct ref_transaction *transaction,
  */
 int ref_transaction_create(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *new_sha1,
+                          const struct object_id *new_oid,
                           unsigned int flags, const char *msg,
                           struct strbuf *err);
 
 /*
- * Add a reference deletion to transaction. If old_sha1 is non-NULL,
+ * Add a reference deletion to transaction. If old_oid is non-NULL,
  * then it holds the value that the reference should have had before
- * the update (which must not be null_sha1).
+ * the update (which must not be null_oid).
  *
  * See the above comment "Reference transaction updates" for more
  * information.
  */
 int ref_transaction_delete(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *old_sha1,
+                          const struct object_id *old_oid,
                           unsigned int flags, const char *msg,
                           struct strbuf *err);
 
 /*
- * Verify, within a transaction, that refname has the value old_sha1,
- * or, if old_sha1 is null_sha1, then verify that the reference
- * doesn't exist. old_sha1 must be non-NULL.
+ * Verify, within a transaction, that refname has the value old_oid,
+ * or, if old_oid is null_oid, then verify that the reference
+ * doesn't exist. old_oid must be non-NULL.
  *
  * See the above comment "Reference transaction updates" for more
  * information.
  */
 int ref_transaction_verify(struct ref_transaction *transaction,
                           const char *refname,
-                          const unsigned char *old_sha1,
+                          const struct object_id *old_oid,
                           unsigned int flags,
                           struct strbuf *err);
 
@@ -624,7 +657,7 @@ int ref_transaction_abort(struct ref_transaction *transaction,
  * It is a bug to call this function when there might be other
  * processes accessing the repository or if there are existing
  * references that might conflict with the ones being created. All
- * old_sha1 values must either be absent or NULL_SHA1.
+ * old_oid values must either be absent or null_oid.
  */
 int initial_ref_transaction_commit(struct ref_transaction *transaction,
                                   struct strbuf *err);
@@ -643,12 +676,9 @@ void ref_transaction_free(struct ref_transaction *transaction);
  * argument.
  */
 int refs_update_ref(struct ref_store *refs, const char *msg, const char *refname,
-                   const unsigned char *new_sha1, const unsigned char *old_sha1,
+                   const struct object_id *new_oid, const struct object_id *old_oid,
                    unsigned int flags, enum action_on_err onerr);
 int update_ref(const char *msg, const char *refname,
-              const unsigned char *new_sha1, const unsigned char *old_sha1,
-              unsigned int flags, enum action_on_err onerr);
-int update_ref_oid(const char *msg, const char *refname,
               const struct object_id *new_oid, const struct object_id *old_oid,
               unsigned int flags, enum action_on_err onerr);
 
@@ -706,20 +736,20 @@ typedef int reflog_expiry_should_prune_fn(struct object_id *ooid,
 typedef void reflog_expiry_cleanup_fn(void *cb_data);
 
 /*
- * Expire reflog entries for the specified reference. sha1 is the old
+ * Expire reflog entries for the specified reference. oid is the old
  * value of the reference. flags is a combination of the constants in
  * enum expire_reflog_flags. The three function pointers are described
  * above. On success, return zero.
  */
 int refs_reflog_expire(struct ref_store *refs,
                       const char *refname,
-                      const unsigned char *sha1,
+                      const struct object_id *oid,
                       unsigned int flags,
                       reflog_expiry_prepare_fn prepare_fn,
                       reflog_expiry_should_prune_fn should_prune_fn,
                       reflog_expiry_cleanup_fn cleanup_fn,
                       void *policy_cb_data);
-int reflog_expire(const char *refname, const unsigned char *sha1,
+int reflog_expire(const char *refname, const struct object_id *oid,
                  unsigned int flags,
                  reflog_expiry_prepare_fn prepare_fn,
                  reflog_expiry_should_prune_fn should_prune_fn,
index a80d60aa068e943fbefe392948df2c9dde6f3df0..f75d960e1982a2db5dc8828c6f1b10ffe64fda48 100644 (file)
 #include "../object.h"
 #include "../dir.h"
 
+/*
+ * This backend uses the following flags in `ref_update::flags` for
+ * internal bookkeeping purposes. Their numerical values must not
+ * conflict with REF_NO_DEREF, REF_FORCE_CREATE_REFLOG, REF_HAVE_NEW,
+ * REF_HAVE_OLD, or REF_IS_PRUNING, which are also stored in
+ * `ref_update::flags`.
+ */
+
+/*
+ * Used as a flag in ref_update::flags when a loose ref is being
+ * pruned. This flag must only be used when REF_NO_DEREF is set.
+ */
+#define REF_IS_PRUNING (1 << 4)
+
+/*
+ * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
+ * refs (i.e., because the reference is about to be deleted anyway).
+ */
+#define REF_DELETING (1 << 5)
+
+/*
+ * Used as a flag in ref_update::flags when the lockfile needs to be
+ * committed.
+ */
+#define REF_NEEDS_COMMIT (1 << 6)
+
+/*
+ * Used as a flag in ref_update::flags when we want to log a ref
+ * update but not actually perform it.  This is used when a symbolic
+ * ref update is split up.
+ */
+#define REF_LOG_ONLY (1 << 7)
+
+/*
+ * Used as a flag in ref_update::flags when the ref_update was via an
+ * update to HEAD.
+ */
+#define REF_UPDATE_VIA_HEAD (1 << 8)
+
+/*
+ * Used as a flag in ref_update::flags when the loose reference has
+ * been deleted.
+ */
+#define REF_DELETED_LOOSE (1 << 9)
+
 struct ref_lock {
        char *ref_name;
        struct lock_file lk;
@@ -189,13 +234,13 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
                        if (!refs_resolve_ref_unsafe(&refs->base,
                                                     refname.buf,
                                                     RESOLVE_REF_READING,
-                                                    oid.hash, &flag)) {
+                                                    &oid, &flag)) {
                                oidclr(&oid);
                                flag |= REF_ISBROKEN;
                        } else if (is_null_oid(&oid)) {
                                /*
                                 * It is so astronomically unlikely
-                                * that NULL_SHA1 is the SHA-1 of an
+                                * that null_oid is the OID of an
                                 * actual object that we consider its
                                 * appearance in a loose reference
                                 * file to be repo corruption
@@ -261,7 +306,7 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
 }
 
 static int files_read_raw_ref(struct ref_store *ref_store,
-                             const char *refname, unsigned char *sha1,
+                             const char *refname, struct object_id *oid,
                              struct strbuf *referent, unsigned int *type)
 {
        struct files_ref_store *refs =
@@ -270,6 +315,7 @@ static int files_read_raw_ref(struct ref_store *ref_store,
        struct strbuf sb_path = STRBUF_INIT;
        const char *path;
        const char *buf;
+       const char *p;
        struct stat st;
        int fd;
        int ret = -1;
@@ -304,7 +350,7 @@ static int files_read_raw_ref(struct ref_store *ref_store,
                if (errno != ENOENT)
                        goto out;
                if (refs_read_raw_ref(refs->packed_ref_store, refname,
-                                     sha1, referent, type)) {
+                                     oid, referent, type)) {
                        errno = ENOENT;
                        goto out;
                }
@@ -344,7 +390,7 @@ static int files_read_raw_ref(struct ref_store *ref_store,
                 * packed ref:
                 */
                if (refs_read_raw_ref(refs->packed_ref_store, refname,
-                                     sha1, referent, type)) {
+                                     oid, referent, type)) {
                        errno = EISDIR;
                        goto out;
                }
@@ -390,8 +436,8 @@ static int files_read_raw_ref(struct ref_store *ref_store,
         * Please note that FETCH_HEAD has additional
         * data after the sha.
         */
-       if (get_sha1_hex(buf, sha1) ||
-           (buf[40] != '\0' && !isspace(buf[40]))) {
+       if (parse_oid_hex(buf, oid, &p) ||
+           (*p != '\0' && !isspace(*p))) {
                *type |= REF_ISBROKEN;
                errno = EINVAL;
                goto out;
@@ -427,7 +473,7 @@ static void unlock_ref(struct ref_lock *lock)
  * are passed to refs_verify_refname_available() for this check.
  *
  * If mustexist is not set and the reference is not found or is
- * broken, lock the reference anyway but clear sha1.
+ * broken, lock the reference anyway but clear old_oid.
  *
  * Return 0 on success. On failure, write an error message to err and
  * return TRANSACTION_NAME_CONFLICT or TRANSACTION_GENERIC_ERROR.
@@ -545,7 +591,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
         */
 
        if (files_read_raw_ref(&refs->base, refname,
-                              lock->old_oid.hash, referent, type)) {
+                              &lock->old_oid, referent, type)) {
                if (errno == ENOENT) {
                        if (mustexist) {
                                /* Garden variety missing reference. */
@@ -769,21 +815,21 @@ static struct ref_iterator *files_ref_iterator_begin(
 }
 
 /*
- * Verify that the reference locked by lock has the value old_sha1.
- * Fail if the reference doesn't exist and mustexist is set. Return 0
- * on success. On error, write an error message to err, set errno, and
- * return a negative value.
+ * Verify that the reference locked by lock has the value old_oid
+ * (unless it is NULL).  Fail if the reference doesn't exist and
+ * mustexist is set. Return 0 on success. On error, write an error
+ * message to err, set errno, and return a negative value.
  */
 static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
-                      const unsigned char *old_sha1, int mustexist,
+                      const struct object_id *old_oid, int mustexist,
                       struct strbuf *err)
 {
        assert(err);
 
        if (refs_read_ref_full(ref_store, lock->ref_name,
                               mustexist ? RESOLVE_REF_READING : 0,
-                              lock->old_oid.hash, NULL)) {
-               if (old_sha1) {
+                              &lock->old_oid, NULL)) {
+               if (old_oid) {
                        int save_errno = errno;
                        strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
                        errno = save_errno;
@@ -793,11 +839,11 @@ static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
                        return 0;
                }
        }
-       if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
+       if (old_oid && oidcmp(&lock->old_oid, old_oid)) {
                strbuf_addf(err, "ref '%s' is at %s but expected %s",
                            lock->ref_name,
                            oid_to_hex(&lock->old_oid),
-                           sha1_to_hex(old_sha1));
+                           oid_to_hex(old_oid));
                errno = EBUSY;
                return -1;
        }
@@ -827,22 +873,22 @@ static int create_reflock(const char *path, void *cb)
  * Locks a ref returning the lock on success and NULL on failure.
  * On failure errno is set to something meaningful.
  */
-static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
-                                           const char *refname,
-                                           const unsigned char *old_sha1,
-                                           const struct string_list *extras,
-                                           const struct string_list *skip,
-                                           unsigned int flags, int *type,
-                                           struct strbuf *err)
+static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
+                                          const char *refname,
+                                          const struct object_id *old_oid,
+                                          const struct string_list *extras,
+                                          const struct string_list *skip,
+                                          unsigned int flags, int *type,
+                                          struct strbuf *err)
 {
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
        int last_errno = 0;
-       int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
+       int mustexist = (old_oid && !is_null_oid(old_oid));
        int resolve_flags = RESOLVE_REF_NO_RECURSE;
        int resolved;
 
-       files_assert_main_repository(refs, "lock_ref_sha1_basic");
+       files_assert_main_repository(refs, "lock_ref_oid_basic");
        assert(err);
 
        lock = xcalloc(1, sizeof(struct ref_lock));
@@ -855,7 +901,7 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
        files_ref_path(refs, &ref_file, refname);
        resolved = !!refs_resolve_ref_unsafe(&refs->base,
                                             refname, resolve_flags,
-                                            lock->old_oid.hash, type);
+                                            &lock->old_oid, type);
        if (!resolved && errno == EISDIR) {
                /*
                 * we are trying to lock foo but we used to
@@ -874,7 +920,7 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
                }
                resolved = !!refs_resolve_ref_unsafe(&refs->base,
                                                     refname, resolve_flags,
-                                                    lock->old_oid.hash, type);
+                                                    &lock->old_oid, type);
        }
        if (!resolved) {
                last_errno = errno;
@@ -908,7 +954,7 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
                goto error_return;
        }
 
-       if (verify_lock(&refs->base, lock, old_sha1, mustexist, err)) {
+       if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) {
                last_errno = errno;
                goto error_return;
        }
@@ -926,7 +972,7 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
 
 struct ref_to_prune {
        struct ref_to_prune *next;
-       unsigned char sha1[20];
+       struct object_id oid;
        char name[FLEX_ARRAY];
 };
 
@@ -988,22 +1034,29 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
 {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
+       int ret = -1;
 
        if (check_refname_format(r->name, 0))
                return;
 
        transaction = ref_store_transaction_begin(&refs->base, &err);
-       if (!transaction ||
-           ref_transaction_delete(transaction, r->name, r->sha1,
-                                  REF_ISPRUNING | REF_NODEREF, NULL, &err) ||
-           ref_transaction_commit(transaction, &err)) {
-               ref_transaction_free(transaction);
+       if (!transaction)
+               goto cleanup;
+       ref_transaction_add_update(
+                       transaction, r->name,
+                       REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
+                       &null_oid, &r->oid, NULL);
+       if (ref_transaction_commit(transaction, &err))
+               goto cleanup;
+
+       ret = 0;
+
+cleanup:
+       if (ret)
                error("%s", err.buf);
-               strbuf_release(&err);
-               return;
-       }
-       ref_transaction_free(transaction);
        strbuf_release(&err);
+       ref_transaction_free(transaction);
+       return;
 }
 
 /*
@@ -1079,8 +1132,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
                 * packed-refs transaction:
                 */
                if (ref_transaction_update(transaction, iter->refname,
-                                          iter->oid->hash, NULL,
-                                          REF_NODEREF, NULL, &err))
+                                          iter->oid, NULL,
+                                          REF_NO_DEREF, NULL, &err))
                        die("failure preparing to create packed reference %s: %s",
                            iter->refname, err.buf);
 
@@ -1088,7 +1141,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
                if ((flags & PACK_REFS_PRUNE)) {
                        struct ref_to_prune *n;
                        FLEX_ALLOC_STR(n, name, iter->refname);
-                       hashcpy(n->sha1, iter->oid->hash);
+                       oidcpy(&n->oid, iter->oid);
                        n->next = refs_to_prune;
                        refs_to_prune = n;
                }
@@ -1251,7 +1304,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 
        if (!refs_resolve_ref_unsafe(&refs->base, oldrefname,
                                     RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
-                               orig_oid.hash, &flag)) {
+                               &orig_oid, &flag)) {
                ret = error("refname %s not found", oldrefname);
                goto out;
        }
@@ -1283,7 +1336,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
        }
 
        if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname,
-                           orig_oid.hash, REF_NODEREF)) {
+                           &orig_oid, REF_NO_DEREF)) {
                error("unable to delete old %s", oldrefname);
                goto rollback;
        }
@@ -1297,9 +1350,9 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
         */
        if (!copy && !refs_read_ref_full(&refs->base, newrefname,
                                RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
-                               oid.hash, NULL) &&
+                               &oid, NULL) &&
            refs_delete_ref(&refs->base, NULL, newrefname,
-                           NULL, REF_NODEREF)) {
+                           NULL, REF_NO_DEREF)) {
                if (errno == EISDIR) {
                        struct strbuf path = STRBUF_INIT;
                        int result;
@@ -1323,8 +1376,8 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 
        logmoved = log;
 
-       lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
-                                  REF_NODEREF, NULL, &err);
+       lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL,
+                                 REF_NO_DEREF, NULL, &err);
        if (!lock) {
                if (copy)
                        error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
@@ -1346,8 +1399,8 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
        goto out;
 
  rollback:
-       lock = lock_ref_sha1_basic(refs, oldrefname, NULL, NULL, NULL,
-                                  REF_NODEREF, NULL, &err);
+       lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL,
+                                 REF_NO_DEREF, NULL, &err);
        if (!lock) {
                error("unable to lock %s for rollback: %s", oldrefname, err.buf);
                strbuf_release(&err);
@@ -1595,9 +1648,8 @@ static int files_log_ref_write(struct files_ref_store *refs,
 }
 
 /*
- * Write sha1 into the open lockfile, then close the lockfile. On
- * errors, rollback the lockfile, fill in *err and
- * return -1.
+ * Write oid into the open lockfile, then close the lockfile. On
+ * errors, rollback the lockfile, fill in *err and return -1.
  */
 static int write_ref_to_lockfile(struct ref_lock *lock,
                                 const struct object_id *oid, struct strbuf *err)
@@ -1721,7 +1773,7 @@ static void update_symref_reflog(struct files_ref_store *refs,
        struct object_id new_oid;
        if (logmsg &&
            !refs_read_ref_full(&refs->base, target,
-                               RESOLVE_REF_READING, new_oid.hash, NULL) &&
+                               RESOLVE_REF_READING, &new_oid, NULL) &&
            files_log_ref_write(refs, refname, &lock->old_oid,
                                &new_oid, logmsg, 0, &err)) {
                error("%s", err.buf);
@@ -1762,9 +1814,9 @@ static int files_create_symref(struct ref_store *ref_store,
        struct ref_lock *lock;
        int ret;
 
-       lock = lock_ref_sha1_basic(refs, refname, NULL,
-                                  NULL, NULL, REF_NODEREF, NULL,
-                                  &err);
+       lock = lock_ref_oid_basic(refs, refname, NULL,
+                                 NULL, NULL, REF_NO_DEREF, NULL,
+                                 &err);
        if (!lock) {
                error("%s", err.buf);
                strbuf_release(&err);
@@ -2010,7 +2062,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
 
                if (refs_read_ref_full(iter->ref_store,
                                       diter->relative_path, 0,
-                                      iter->oid.hash, &flags)) {
+                                      &iter->oid, &flags)) {
                        error("bad ref for %s", diter->path.buf);
                        continue;
                }
@@ -2124,7 +2176,7 @@ static int split_head_update(struct ref_update *update,
        struct ref_update *new_update;
 
        if ((update->flags & REF_LOG_ONLY) ||
-           (update->flags & REF_ISPRUNING) ||
+           (update->flags & REF_IS_PRUNING) ||
            (update->flags & REF_UPDATE_VIA_HEAD))
                return 0;
 
@@ -2147,8 +2199,8 @@ static int split_head_update(struct ref_update *update,
 
        new_update = ref_transaction_add_update(
                        transaction, "HEAD",
-                       update->flags | REF_LOG_ONLY | REF_NODEREF,
-                       update->new_oid.hash, update->old_oid.hash,
+                       update->flags | REF_LOG_ONLY | REF_NO_DEREF,
+                       &update->new_oid, &update->old_oid,
                        update->msg);
 
        /*
@@ -2166,8 +2218,8 @@ static int split_head_update(struct ref_update *update,
 
 /*
  * update is for a symref that points at referent and doesn't have
- * REF_NODEREF set. Split it into two updates:
- * - The original update, but with REF_LOG_ONLY and REF_NODEREF set
+ * REF_NO_DEREF set. Split it into two updates:
+ * - The original update, but with REF_LOG_ONLY and REF_NO_DEREF set
  * - A new, separate update for the referent reference
  * Note that the new update will itself be subject to splitting when
  * the iteration gets to it.
@@ -2212,17 +2264,17 @@ static int split_symref_update(struct files_ref_store *refs,
 
        new_update = ref_transaction_add_update(
                        transaction, referent, new_flags,
-                       update->new_oid.hash, update->old_oid.hash,
+                       &update->new_oid, &update->old_oid,
                        update->msg);
 
        new_update->parent_update = update;
 
        /*
         * Change the symbolic ref update to log only. Also, it
-        * doesn't need to check its old SHA-1 value, as that will be
+        * doesn't need to check its old OID value, as that will be
         * done when new_update is processed.
         */
-       update->flags |= REF_LOG_ONLY | REF_NODEREF;
+       update->flags |= REF_LOG_ONLY | REF_NO_DEREF;
        update->flags &= ~REF_HAVE_OLD;
 
        /*
@@ -2288,10 +2340,10 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
  * Prepare for carrying out update:
  * - Lock the reference referred to by update.
  * - Read the reference under lock.
- * - Check that its old SHA-1 value (if specified) is correct, and in
+ * - Check that its old OID value (if specified) is correct, and in
  *   any case record it in update->lock->old_oid for later use when
  *   writing the reflog.
- * - If it is a symref update without REF_NODEREF, split it up into a
+ * - If it is a symref update without REF_NO_DEREF, split it up into a
  *   REF_LOG_ONLY update of the symref and add a separate update for
  *   the referent to transaction.
  * - If it is an update of head_ref, add a corresponding REF_LOG_ONLY
@@ -2339,15 +2391,15 @@ static int lock_ref_for_update(struct files_ref_store *refs,
        update->backend_data = lock;
 
        if (update->type & REF_ISSYMREF) {
-               if (update->flags & REF_NODEREF) {
+               if (update->flags & REF_NO_DEREF) {
                        /*
                         * We won't be reading the referent as part of
                         * the transaction, so we have to read it here
-                        * to record and possibly check old_sha1:
+                        * to record and possibly check old_oid:
                         */
                        if (refs_read_ref_full(&refs->base,
                                               referent.buf, 0,
-                                              lock->old_oid.hash, NULL)) {
+                                              &lock->old_oid, NULL)) {
                                if (update->flags & REF_HAVE_OLD) {
                                        strbuf_addf(err, "cannot lock ref '%s': "
                                                    "error reading reference",
@@ -2363,7 +2415,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
                        /*
                         * Create a new update for the reference this
                         * symref is pointing at. Also, we will record
-                        * and verify old_sha1 for this update as part
+                        * and verify old_oid for this update as part
                         * of processing the split-off update, so we
                         * don't have to do it here.
                         */
@@ -2383,7 +2435,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
 
                /*
                 * If this update is happening indirectly because of a
-                * symref update, record the old SHA-1 in the parent
+                * symref update, record the old OID in the parent
                 * update:
                 */
                for (parent_update = update->parent_update;
@@ -2510,13 +2562,18 @@ static int files_transaction_prepare(struct ref_store *ref_store,
         * transaction. (If we end up splitting up any updates using
         * split_symref_update() or split_head_update(), those
         * functions will check that the new updates don't have the
-        * same refname as any existing ones.)
+        * same refname as any existing ones.) Also fail if any of the
+        * updates use REF_IS_PRUNING without REF_NO_DEREF.
         */
        for (i = 0; i < transaction->nr; i++) {
                struct ref_update *update = transaction->updates[i];
                struct string_list_item *item =
                        string_list_append(&affected_refnames, update->refname);
 
+               if ((update->flags & REF_IS_PRUNING) &&
+                   !(update->flags & REF_NO_DEREF))
+                       BUG("REF_IS_PRUNING set without REF_NO_DEREF");
+
                /*
                 * We store a pointer to update in item->util, but at
                 * the moment we never use the value of this field
@@ -2574,7 +2631,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 
                if (update->flags & REF_DELETING &&
                    !(update->flags & REF_LOG_ONLY) &&
-                   !(update->flags & REF_ISPRUNING)) {
+                   !(update->flags & REF_IS_PRUNING)) {
                        /*
                         * This reference has to be deleted from
                         * packed-refs if it exists there.
@@ -2593,8 +2650,8 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 
                        ref_transaction_add_update(
                                        packed_transaction, update->refname,
-                                       update->flags & ~REF_HAVE_OLD,
-                                       update->new_oid.hash, update->old_oid.hash,
+                                       REF_HAVE_NEW | REF_NO_DEREF,
+                                       &update->new_oid, NULL,
                                        NULL);
                }
        }
@@ -2707,7 +2764,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
                struct ref_update *update = transaction->updates[i];
                if (update->flags & REF_DELETING &&
                    !(update->flags & REF_LOG_ONLY) &&
-                   !(update->flags & REF_ISPRUNING)) {
+                   !(update->flags & REF_IS_PRUNING)) {
                        strbuf_reset(&sb);
                        files_reflog_path(refs, &sb, update->refname);
                        if (!unlink_or_warn(sb.buf))
@@ -2863,7 +2920,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
                 */
                ref_transaction_add_update(packed_transaction, update->refname,
                                           update->flags & ~REF_HAVE_OLD,
-                                          update->new_oid.hash, update->old_oid.hash,
+                                          &update->new_oid, &update->old_oid,
                                           NULL);
        }
 
@@ -2924,7 +2981,7 @@ static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
 }
 
 static int files_reflog_expire(struct ref_store *ref_store,
-                              const char *refname, const unsigned char *sha1,
+                              const char *refname, const struct object_id *oid,
                               unsigned int flags,
                               reflog_expiry_prepare_fn prepare_fn,
                               reflog_expiry_should_prune_fn should_prune_fn,
@@ -2941,7 +2998,6 @@ static int files_reflog_expire(struct ref_store *ref_store,
        int status = 0;
        int type;
        struct strbuf err = STRBUF_INIT;
-       struct object_id oid;
 
        memset(&cb, 0, sizeof(cb));
        cb.flags = flags;
@@ -2953,9 +3009,9 @@ static int files_reflog_expire(struct ref_store *ref_store,
         * reference itself, plus we might need to update the
         * reference if --updateref was specified:
         */
-       lock = lock_ref_sha1_basic(refs, refname, sha1,
-                                  NULL, NULL, REF_NODEREF,
-                                  &type, &err);
+       lock = lock_ref_oid_basic(refs, refname, oid,
+                                 NULL, NULL, REF_NO_DEREF,
+                                 &type, &err);
        if (!lock) {
                error("cannot lock ref '%s': %s", refname, err.buf);
                strbuf_release(&err);
@@ -2991,9 +3047,7 @@ static int files_reflog_expire(struct ref_store *ref_store,
                }
        }
 
-       hashcpy(oid.hash, sha1);
-
-       (*prepare_fn)(refname, &oid, cb.policy_cb);
+       (*prepare_fn)(refname, oid, cb.policy_cb);
        refs_for_each_reflog_ent(ref_store, refname, expire_reflog_ent, &cb);
        (*cleanup_fn)(cb.policy_cb);
 
index dab8a85d9a67870d45e160e66ab382991a181eae..023243fd5f1833f3c5f0b6fd3cd82b2e0c69644e 100644 (file)
@@ -716,7 +716,7 @@ static struct snapshot *get_snapshot(struct packed_ref_store *refs)
 }
 
 static int packed_read_raw_ref(struct ref_store *ref_store,
-                              const char *refname, unsigned char *sha1,
+                              const char *refname, struct object_id *oid,
                               struct strbuf *referent, unsigned int *type)
 {
        struct packed_ref_store *refs =
@@ -734,7 +734,7 @@ static int packed_read_raw_ref(struct ref_store *ref_store,
                return -1;
        }
 
-       if (get_sha1_hex(rec, sha1))
+       if (get_oid_hex(rec, oid))
                die_invalid_line(refs->path, rec, snapshot->eof - rec);
 
        *type = REF_ISPACKED;
@@ -744,7 +744,7 @@ static int packed_read_raw_ref(struct ref_store *ref_store,
 /*
  * This value is set in `base.flags` if the peeled value of the
  * current reference is known. In that case, `peeled` contains the
- * correct peeled value for the reference, which might be `null_sha1`
+ * correct peeled value for the reference, which might be `null_oid`
  * if the reference is not a tag or if it is broken.
  */
 #define REF_KNOWS_PEELED 0x40
@@ -880,7 +880,7 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
        } else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {
                return -1;
        } else {
-               return !!peel_object(iter->oid.hash, peeled->hash);
+               return !!peel_object(&iter->oid, peeled);
        }
 }
 
@@ -961,11 +961,11 @@ static struct ref_iterator *packed_ref_iterator_begin(
  * by the failing call to `fprintf()`.
  */
 static int write_packed_entry(FILE *fh, const char *refname,
-                             const unsigned char *sha1,
-                             const unsigned char *peeled)
+                             const struct object_id *oid,
+                             const struct object_id *peeled)
 {
-       if (fprintf(fh, "%s %s\n", sha1_to_hex(sha1), refname) < 0 ||
-           (peeled && fprintf(fh, "^%s\n", sha1_to_hex(peeled)) < 0))
+       if (fprintf(fh, "%s %s\n", oid_to_hex(oid), refname) < 0 ||
+           (peeled && fprintf(fh, "^%s\n", oid_to_hex(peeled)) < 0))
                return -1;
 
        return 0;
@@ -1203,8 +1203,8 @@ static int write_with_updates(struct packed_ref_store *refs,
                        int peel_error = ref_iterator_peel(iter, &peeled);
 
                        if (write_packed_entry(out, iter->refname,
-                                              iter->oid->hash,
-                                              peel_error ? NULL : peeled.hash))
+                                              iter->oid,
+                                              peel_error ? NULL : &peeled))
                                goto write_error;
 
                        if ((ok = ref_iterator_advance(iter)) != ITER_OK)
@@ -1220,12 +1220,12 @@ static int write_with_updates(struct packed_ref_store *refs,
                        i++;
                } else {
                        struct object_id peeled;
-                       int peel_error = peel_object(update->new_oid.hash,
-                                                    peeled.hash);
+                       int peel_error = peel_object(&update->new_oid,
+                                                    &peeled);
 
                        if (write_packed_entry(out, update->refname,
-                                              update->new_oid.hash,
-                                              peel_error ? NULL : peeled.hash))
+                                              &update->new_oid,
+                                              peel_error ? NULL : &peeled))
                                goto write_error;
 
                        i++;
@@ -1340,7 +1340,7 @@ int is_packed_transaction_needed(struct ref_store *ref_store,
                        continue;
 
                if (!refs_read_raw_ref(ref_store, update->refname,
-                                      oid.hash, &referent, &type) ||
+                                      &oid, &referent, &type) ||
                    errno != ENOENT) {
                        /*
                         * We have to actually delete that reference
@@ -1613,7 +1613,7 @@ static int packed_delete_reflog(struct ref_store *ref_store,
 }
 
 static int packed_reflog_expire(struct ref_store *ref_store,
-                               const char *refname, const unsigned char *sha1,
+                               const char *refname, const struct object_id *oid,
                                unsigned int flags,
                                reflog_expiry_prepare_fn prepare_fn,
                                reflog_expiry_should_prune_fn should_prune_fn,
index 4f850e1b5c9ed1fae746a804ceceee42e2f54b86..82c1cf90a7ef61cc8174bff5a9800af2ec8d7053 100644 (file)
@@ -260,8 +260,8 @@ int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref)
 
 /*
  * Emit a warning and return true iff ref1 and ref2 have the same name
- * and the same sha1.  Die if they have the same name but different
- * sha1s.
+ * and the same oid. Die if they have the same name but different
+ * oids.
  */
 static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
 {
@@ -493,7 +493,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
 static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
                                   struct object_id *peeled)
 {
-       return peel_object(ref_iterator->oid->hash, peeled->hash);
+       return peel_object(ref_iterator->oid, peeled);
 }
 
 static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
index 448de4bccb8f3d9531b50d5489af001ec7ce4732..dd834314bd8d5af0315dab19af50f3300a73ad90 100644 (file)
@@ -8,58 +8,22 @@
  */
 
 /*
- * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
- * refs (i.e., because the reference is about to be deleted anyway).
+ * The following flags can appear in `ref_update::flags`. Their
+ * numerical values must not conflict with those of REF_NO_DEREF and
+ * REF_FORCE_CREATE_REFLOG, which are also stored in
+ * `ref_update::flags`.
  */
-#define REF_DELETING   0x02
 
 /*
- * Used as a flag in ref_update::flags when a loose ref is being
- * pruned. This flag must only be used when REF_NODEREF is set.
+ * The reference should be updated to new_oid.
  */
-#define REF_ISPRUNING  0x04
+#define REF_HAVE_NEW (1 << 2)
 
 /*
- * Used as a flag in ref_update::flags when the reference should be
- * updated to new_sha1.
+ * The current reference's value should be checked to make sure that
+ * it agrees with old_oid.
  */
-#define REF_HAVE_NEW   0x08
-
-/*
- * Used as a flag in ref_update::flags when old_sha1 should be
- * checked.
- */
-#define REF_HAVE_OLD   0x10
-
-/*
- * Used as a flag in ref_update::flags when the lockfile needs to be
- * committed.
- */
-#define REF_NEEDS_COMMIT 0x20
-
-/*
- * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a
- * value to ref_update::flags
- */
-
-/*
- * Used as a flag in ref_update::flags when we want to log a ref
- * update but not actually perform it.  This is used when a symbolic
- * ref update is split up.
- */
-#define REF_LOG_ONLY 0x80
-
-/*
- * Internal flag, meaning that the containing ref_update was via an
- * update to HEAD.
- */
-#define REF_UPDATE_VIA_HEAD 0x100
-
-/*
- * Used as a flag in ref_update::flags when the loose reference has
- * been deleted.
- */
-#define REF_DELETED_LOOSE 0x200
+#define REF_HAVE_OLD (1 << 3)
 
 /*
  * Return the length of time to retry acquiring a loose reference lock
@@ -120,11 +84,11 @@ enum peel_status {
 /*
  * Peel the named object; i.e., if the object is a tag, resolve the
  * tag recursively until a non-tag is found.  If successful, store the
- * result to sha1 and return PEEL_PEELED.  If the object is not a tag
+ * result to oid and return PEEL_PEELED.  If the object is not a tag
  * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
- * and leave sha1 unchanged.
+ * and leave oid unchanged.
  */
-enum peel_status peel_object(const unsigned char *name, unsigned char *sha1);
+enum peel_status peel_object(const struct object_id *name, struct object_id *oid);
 
 /*
  * Copy the reflog message msg to buf, which has been allocated sufficiently
@@ -134,30 +98,29 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1);
 int copy_reflog_msg(char *buf, const char *msg);
 
 /**
- * Information needed for a single ref update. Set new_sha1 to the new
- * value or to null_sha1 to delete the ref. To check the old value
- * while the ref is locked, set (flags & REF_HAVE_OLD) and set
- * old_sha1 to the old value, or to null_sha1 to ensure the ref does
- * not exist before update.
+ * Information needed for a single ref update. Set new_oid to the new
+ * value or to null_oid to delete the ref. To check the old value
+ * while the ref is locked, set (flags & REF_HAVE_OLD) and set old_oid
+ * to the old value, or to null_oid to ensure the ref does not exist
+ * before update.
  */
 struct ref_update {
-
        /*
-        * If (flags & REF_HAVE_NEW), set the reference to this value:
+        * If (flags & REF_HAVE_NEW), set the reference to this value
+        * (or delete it, if `new_oid` is `null_oid`).
         */
        struct object_id new_oid;
 
        /*
         * If (flags & REF_HAVE_OLD), check that the reference
-        * previously had this value:
+        * previously had this value (or didn't previously exist, if
+        * `old_oid` is `null_oid`).
         */
        struct object_id old_oid;
 
        /*
-        * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
-        * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY,
-        * REF_UPDATE_VIA_HEAD, REF_NEEDS_COMMIT, and
-        * REF_DELETED_LOOSE:
+        * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
+        * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
         */
        unsigned int flags;
 
@@ -181,7 +144,7 @@ struct ref_update {
 };
 
 int refs_read_raw_ref(struct ref_store *ref_store,
-                     const char *refname, unsigned char *sha1,
+                     const char *refname, struct object_id *oid,
                      struct strbuf *referent, unsigned int *type);
 
 /*
@@ -195,15 +158,15 @@ int ref_update_reject_duplicates(struct string_list *refnames,
 /*
  * Add a ref_update with the specified properties to transaction, and
  * return a pointer to the new object. This function does not verify
- * that refname is well-formed. new_sha1 and old_sha1 are only
+ * that refname is well-formed. new_oid and old_oid are only
  * dereferenced if the REF_HAVE_NEW and REF_HAVE_OLD bits,
  * respectively, are set in flags.
  */
 struct ref_update *ref_transaction_add_update(
                struct ref_transaction *transaction,
                const char *refname, unsigned int flags,
-               const unsigned char *new_sha1,
-               const unsigned char *old_sha1,
+               const struct object_id *new_oid,
+               const struct object_id *old_oid,
                const char *msg);
 
 /*
@@ -608,7 +571,7 @@ typedef int create_reflog_fn(struct ref_store *ref_store, const char *refname,
                             int force_create, struct strbuf *err);
 typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname);
 typedef int reflog_expire_fn(struct ref_store *ref_store,
-                            const char *refname, const unsigned char *sha1,
+                            const char *refname, const struct object_id *oid,
                             unsigned int flags,
                             reflog_expiry_prepare_fn prepare_fn,
                             reflog_expiry_should_prune_fn should_prune_fn,
@@ -619,13 +582,13 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
  * Read a reference from the specified reference store, non-recursively.
  * Set type to describe the reference, and:
  *
- * - If refname is the name of a normal reference, fill in sha1
+ * - If refname is the name of a normal reference, fill in oid
  *   (leaving referent unchanged).
  *
  * - If refname is the name of a symbolic reference, write the full
  *   name of the reference to which it refers (e.g.
  *   "refs/heads/master") to referent and set the REF_ISSYMREF bit in
- *   type (leaving sha1 unchanged). The caller is responsible for
+ *   type (leaving oid unchanged). The caller is responsible for
  *   validating that referent is a valid reference name.
  *
  * WARNING: refname might be used as part of a filename, so it is
@@ -637,7 +600,7 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
  *
  * Return 0 on success. If the ref doesn't exist, set errno to ENOENT
  * and return -1. If the ref exists but is neither a symbolic ref nor
- * a sha1, it is broken; set REF_ISBROKEN in type, set errno to
+ * an object ID, it is broken; set REF_ISBROKEN in type, set errno to
  * EINVAL, and return -1. If there is another error reading the ref,
  * set errno appropriately and return -1.
  *
@@ -654,7 +617,7 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
  *   refname will still be valid and unchanged.
  */
 typedef int read_raw_ref_fn(struct ref_store *ref_store,
-                           const char *refname, unsigned char *sha1,
+                           const char *refname, struct object_id *oid,
                            struct strbuf *referent, unsigned int *type);
 
 struct ref_storage_be {
index 0ff4a312629d4ed4250a076f39a9d78e76917158..bcebb4c789567eb4017a3a0132ba55c59c427991 100644 (file)
@@ -174,7 +174,7 @@ static int cmd_import(const char *line)
        struct child_process svndump_proc = CHILD_PROCESS_INIT;
        const char *command = "svnrdump";
 
-       if (read_ref(private_ref, head_oid.hash))
+       if (read_ref(private_ref, &head_oid))
                startrev = 0;
        else {
                note_msg = read_ref_note(&head_oid);
index b220f0dfc619a6d3150cb2bcee00114f76bd0223..4e93753e1988afd4a01559951f96142c6dc2e73d 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -675,6 +675,36 @@ const char *pushremote_for_branch(struct branch *branch, int *explicit)
        return remote_for_branch(branch, explicit);
 }
 
+const char *remote_ref_for_branch(struct branch *branch, int for_push,
+                                 int *explicit)
+{
+       if (branch) {
+               if (!for_push) {
+                       if (branch->merge_nr) {
+                               if (explicit)
+                                       *explicit = 1;
+                               return branch->merge_name[0];
+                       }
+               } else {
+                       const char *dst, *remote_name =
+                               pushremote_for_branch(branch, NULL);
+                       struct remote *remote = remote_get(remote_name);
+
+                       if (remote && remote->push_refspec_nr &&
+                           (dst = apply_refspecs(remote->push,
+                                                 remote->push_refspec_nr,
+                                                 branch->refname))) {
+                               if (explicit)
+                                       *explicit = 1;
+                               return dst;
+                       }
+               }
+       }
+       if (explicit)
+               *explicit = 0;
+       return "";
+}
+
 static struct remote *remote_get_1(const char *name,
                                   const char *(*get_default)(struct branch *, int *))
 {
@@ -1629,7 +1659,7 @@ static void set_merge(struct branch *ret)
                    strcmp(ret->remote_name, "."))
                        continue;
                if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
-                            oid.hash, &ref) == 1)
+                            &oid, &ref) == 1)
                        ret->merge[i]->dst = ref;
                else
                        ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
@@ -2002,13 +2032,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
                return -1;
 
        /* Cannot stat if what we used to build on no longer exists */
-       if (read_ref(base, oid.hash))
+       if (read_ref(base, &oid))
                return -1;
        theirs = lookup_commit_reference(&oid);
        if (!theirs)
                return -1;
 
-       if (read_ref(branch->refname, oid.hash))
+       if (read_ref(branch->refname, &oid))
                return -1;
        ours = lookup_commit_reference(&oid);
        if (!ours)
@@ -2327,7 +2357,7 @@ static int remote_tracking(struct remote *remote, const char *refname,
        dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
        if (!dst)
                return -1; /* no tracking ref for refname at remote */
-       if (read_ref(dst, oid->hash))
+       if (read_ref(dst, oid))
                return -1; /* we know what the tracking ref is but we cannot read it */
        return 0;
 }
index 2ecf4c8c74ce590c9bcc917d997b3c7acf9c07c1..1f6611be214363a4be363fad959135a9d123cee0 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -223,6 +223,8 @@ struct branch {
 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);
+const char *remote_ref_for_branch(struct branch *branch, int for_push,
+                                 int *explicit);
 
 int branch_has_merge_config(struct branch *branch);
 int branch_merge_matches(struct branch *, int n, const char *);
index bb2fae5446b7ea8d2bfac87faa30c165f714d121..998413b8bb8deaddef76d93f67be4a1435c9e3a7 100644 (file)
@@ -5,7 +5,7 @@
 
 /* The main repository */
 static struct repository the_repo = {
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, 0, 0
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, NULL, 0, 0
 };
 struct repository *the_repository = &the_repo;
 
@@ -64,6 +64,11 @@ void repo_set_gitdir(struct repository *repo, const char *path)
        free(old_gitdir);
 }
 
+void repo_set_hash_algo(struct repository *repo, int hash_algo)
+{
+       repo->hash_algo = &hash_algos[hash_algo];
+}
+
 /*
  * Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
  * Return 0 upon success and a non-zero value upon failure.
@@ -136,6 +141,8 @@ int repo_init(struct repository *repo, const char *gitdir, const char *worktree)
        if (read_and_verify_repository_format(&format, repo->commondir))
                goto error;
 
+       repo_set_hash_algo(repo, format.hash_algo);
+
        if (worktree)
                repo_set_worktree(repo, worktree);
 
index 7f5e24a0a24011393e9b1b9986895ebe8b3f10ab..0329e40c7f5e72dad3ba46328a8e3d6c29ed8e58 100644 (file)
@@ -4,6 +4,7 @@
 struct config_set;
 struct index_state;
 struct submodule_cache;
+struct git_hash_algo;
 
 struct repository {
        /* Environment */
@@ -67,6 +68,9 @@ struct repository {
         */
        struct index_state *index;
 
+       /* Repository's current hash algorithm, as serialized on disk. */
+       const struct git_hash_algo *hash_algo;
+
        /* Configurations */
        /*
         * Bit used during initialization to indicate if repository state (like
@@ -86,6 +90,7 @@ extern struct repository *the_repository;
 
 extern void repo_set_gitdir(struct repository *repo, const char *path);
 extern void repo_set_worktree(struct repository *repo, const char *path);
+extern void repo_set_hash_algo(struct repository *repo, int algo);
 extern int repo_init(struct repository *repo, const char *gitdir, const char *worktree);
 extern int repo_submodule_init(struct repository *submodule,
                               struct repository *superproject,
index d167223e694e54626786740954454d4973e28752..72f2b4572ee798f9e17d6f4fbc0d12cfcd2fe221 100644 (file)
@@ -395,8 +395,16 @@ static struct commit *one_relevant_parent(const struct rev_info *revs,
  * if the whole diff is removal of old data, and otherwise
  * REV_TREE_DIFFERENT (of course if the trees are the same we
  * want REV_TREE_SAME).
- * That means that once we get to REV_TREE_DIFFERENT, we do not
- * have to look any further.
+ *
+ * The only time we care about the distinction is when
+ * remove_empty_trees is in effect, in which case we care only about
+ * whether the whole change is REV_TREE_NEW, or if there's another type
+ * of change. Which means we can stop the diff early in either of these
+ * cases:
+ *
+ *   1. We're not using remove_empty_trees at all.
+ *
+ *   2. We saw anything except REV_TREE_NEW.
  */
 static int tree_difference = REV_TREE_SAME;
 
@@ -407,10 +415,11 @@ static void file_add_remove(struct diff_options *options,
                    const char *fullpath, unsigned dirty_submodule)
 {
        int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
+       struct rev_info *revs = options->change_fn_data;
 
        tree_difference |= diff;
-       if (tree_difference == REV_TREE_DIFFERENT)
-               DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!revs->remove_empty_trees || tree_difference != REV_TREE_NEW)
+               options->flags.has_changes = 1;
 }
 
 static void file_change(struct diff_options *options,
@@ -422,7 +431,7 @@ static void file_change(struct diff_options *options,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        tree_difference = REV_TREE_DIFFERENT;
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       options->flags.has_changes = 1;
 }
 
 static int rev_compare_tree(struct rev_info *revs,
@@ -455,7 +464,7 @@ static int rev_compare_tree(struct rev_info *revs,
        }
 
        tree_difference = REV_TREE_SAME;
-       DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
+       revs->pruning.flags.has_changes = 0;
        if (diff_tree_oid(&t1->object.oid, &t2->object.oid, "",
                           &revs->pruning) < 0)
                return REV_TREE_DIFFERENT;
@@ -471,7 +480,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
                return 0;
 
        tree_difference = REV_TREE_SAME;
-       DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
+       revs->pruning.flags.has_changes = 0;
        retval = diff_tree_oid(NULL, &t1->object.oid, "", &revs->pruning);
 
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
@@ -1403,10 +1412,11 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->abbrev = DEFAULT_ABBREV;
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
-       DIFF_OPT_SET(&revs->pruning, RECURSIVE);
-       DIFF_OPT_SET(&revs->pruning, QUICK);
+       revs->pruning.flags.recursive = 1;
+       revs->pruning.flags.quick = 1;
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
+       revs->pruning.change_fn_data = revs;
        revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
        revs->dense = 1;
        revs->prefix = prefix;
@@ -1822,7 +1832,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->simplify_by_decoration = 1;
                revs->limited = 1;
                revs->prune = 1;
-               load_ref_decorations(DECORATE_SHORT_REFS);
+               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
                revs->sort_order = REV_SORT_BY_COMMIT_DATE;
                revs->topo_order = 1;
@@ -1845,6 +1855,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->dense = 0;
        } else if (!strcmp(arg, "--show-all")) {
                revs->show_all = 1;
+       } else if (!strcmp(arg, "--in-commit-order")) {
+               revs->tree_blobs_in_commit_order = 1;
        } else if (!strcmp(arg, "--remove-empty")) {
                revs->remove_empty_trees = 1;
        } else if (!strcmp(arg, "--merges")) {
@@ -1917,11 +1929,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                die("--unpacked=<packfile> no longer supported.");
        } else if (!strcmp(arg, "-r")) {
                revs->diff = 1;
-               DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
+               revs->diffopt.flags.recursive = 1;
        } else if (!strcmp(arg, "-t")) {
                revs->diff = 1;
-               DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
-               DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
+               revs->diffopt.flags.recursive = 1;
+               revs->diffopt.flags.tree_in_recursive = 1;
        } else if (!strcmp(arg, "-m")) {
                revs->ignore_merges = 0;
        } else if (!strcmp(arg, "-c")) {
@@ -2066,7 +2078,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_ERE;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
                revs->grep_filter.ignore_case = 1;
-               DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
+               revs->diffopt.flags.pickaxe_ignore_case = 1;
        } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
                revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_FIXED;
        } else if (!strcmp(arg, "--perl-regexp") || !strcmp(arg, "-P")) {
@@ -2399,7 +2411,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        /* Pickaxe, diff-filter and rename following need diffs */
        if (revs->diffopt.pickaxe ||
            revs->diffopt.filter ||
-           DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
+           revs->diffopt.flags.follow_renames)
                revs->diff = 1;
 
        if (revs->topo_order)
@@ -2408,7 +2420,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->prune_data.nr) {
                copy_pathspec(&revs->pruning.pathspec, &revs->prune_data);
                /* Can't prune commits with rename following: the paths change.. */
-               if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
+               if (!revs->diffopt.flags.follow_renames)
                        revs->prune = 1;
                if (!revs->full_diff)
                        copy_pathspec(&revs->diffopt.pathspec,
index 54761200adf2d5111b8aba097299bd2fd080a949..19dc9bdddba20dfa6ef1bdaaee0e82db3bdf8bd8 100644 (file)
@@ -4,7 +4,7 @@
 #include "parse-options.h"
 #include "grep.h"
 #include "notes.h"
-#include "commit.h"
+#include "pretty.h"
 #include "diff.h"
 
 /* Remember to update object flag allocation in object.h */
@@ -121,7 +121,8 @@ struct rev_info {
                        bisect:1,
                        ancestry_path:1,
                        first_parent_only:1,
-                       line_level_traverse:1;
+                       line_level_traverse:1,
+                       tree_blobs_in_commit_order:1;
 
        /* Diff flags */
        unsigned int    diff:1,
index 014b2165b5a2f92ff18869effafd4c6df01b33eb..31fc5ea86eb6a3fe3650b8ac50f19f8621d90b9d 100644 (file)
@@ -5,6 +5,7 @@
 #include "argv-array.h"
 #include "thread-utils.h"
 #include "strbuf.h"
+#include "string-list.h"
 
 void child_process_init(struct child_process *child)
 {
@@ -1169,11 +1170,28 @@ const char *find_hook(const char *name)
        strbuf_reset(&path);
        strbuf_git_path(&path, "hooks/%s", name);
        if (access(path.buf, X_OK) < 0) {
+               int err = errno;
+
 #ifdef STRIP_EXTENSION
                strbuf_addstr(&path, STRIP_EXTENSION);
                if (access(path.buf, X_OK) >= 0)
                        return path.buf;
+               if (errno == EACCES)
+                       err = errno;
 #endif
+
+               if (err == EACCES && advice_ignored_hook) {
+                       static struct string_list advise_given = STRING_LIST_INIT_DUP;
+
+                       if (!string_list_lookup(&advise_given, name)) {
+                               string_list_insert(&advise_given, name);
+                               advise(_("The '%s' hook was ignored because "
+                                        "it's not set as executable.\n"
+                                        "You can disable this warning with "
+                                        "`git config advice.ignoredHook false`."),
+                                      path.buf);
+                       }
+               }
                return NULL;
        }
        return path.buf;
index 5e09b5d34fbcb0516ffdaf6a841a6a8ff47f4ea5..894d12ad725c2f8cdbabbc87c7db558f5a872a4e 100644 (file)
@@ -347,7 +347,7 @@ static int read_oneliner(struct strbuf *buf,
 
 static struct tree *empty_tree(void)
 {
-       return lookup_tree(&empty_tree_oid);
+       return lookup_tree(the_hash_algo->empty_tree);
 }
 
 static int error_dirty_index(struct replay_opts *opts)
@@ -393,7 +393,7 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
-                                  to->hash, unborn ? null_sha1 : from->hash,
+                                  to, unborn ? &null_oid : from,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@ -449,6 +449,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        o.branch2 = next ? next_label : "(empty tree)";
        if (is_rebase_i(opts))
                o.buffer_output = 2;
+       o.show_rename_progress = 1;
 
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
@@ -463,6 +464,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        if (is_rebase_i(opts) && clean <= 0)
                fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
+       diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
        if (clean < 0)
                return clean;
 
@@ -490,7 +492,7 @@ static int is_index_unchanged(void)
        struct object_id head_oid;
        struct commit *head_commit;
 
-       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
                return error(_("could not resolve HEAD commit\n"));
 
        head_commit = lookup_commit(&head_oid);
@@ -706,7 +708,7 @@ static int is_original_commit_empty(struct commit *commit)
                                oid_to_hex(&parent->object.oid));
                ptree_oid = &parent->tree->object.oid;
        } else {
-               ptree_oid = &empty_tree_oid; /* commit is root */
+               ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
 
        return !oidcmp(ptree_oid, &commit->tree->object.oid);
@@ -795,6 +797,13 @@ static const char *command_to_string(const enum todo_command command)
        die("Unknown command: %d", command);
 }
 
+static char command_to_char(const enum todo_command command)
+{
+       if (command < TODO_COMMENT && todo_command_info[command].c)
+               return todo_command_info[command].c;
+       return comment_line_char;
+}
+
 static int is_noop(const enum todo_command command)
 {
        return TODO_NOOP <= command;
@@ -959,8 +968,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        } else {
                unborn = get_oid("HEAD", &head);
                if (unborn)
-                       oidcpy(&head, &empty_tree_oid);
-               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
+                       oidcpy(&head, the_hash_algo->empty_tree);
+               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
+                                      NULL, 0))
                        return error_dirty_index(opts);
        }
        discard_cache();
@@ -1116,12 +1126,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
         * write it at all.
         */
        if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
-           update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL,
-                      REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
+           update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
+                      REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
        if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
-           update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL,
-                      REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
+           update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL,
+                      REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
 
        if (res) {
@@ -1185,7 +1195,6 @@ static int read_and_refresh_cache(struct replay_opts *opts)
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
        if (the_index.cache_changed && index_fd >= 0) {
                if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
-                       rollback_lock_file(&index_lock);
                        return error(_("git %s: failed to refresh the index"),
                                _(action_name(opts)));
                }
@@ -1268,6 +1277,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        bol += padding;
 
        if (item->command == TODO_EXEC) {
+               item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
@@ -1631,7 +1641,7 @@ static int rollback_single_pick(void)
        if (!file_exists(git_path_cherry_pick_head()) &&
            !file_exists(git_path_revert_head()))
                return error(_("no cherry-pick or revert in progress"));
-       if (read_ref_full("HEAD", 0, head_oid.hash, NULL))
+       if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
        if (is_null_oid(&head_oid))
                return error(_("cannot abort from a branch yet to be born"));
@@ -2130,8 +2140,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                        }
                        msg = reflog_message(opts, "finish", "%s onto %s",
                                head_ref.buf, buf.buf);
-                       if (update_ref(msg, head_ref.buf, head.hash, orig.hash,
-                                       REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
+                       if (update_ref(msg, head_ref.buf, &head, &orig,
+                                      REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                                goto cleanup_head_ref;
@@ -2285,7 +2295,7 @@ int sequencer_continue(struct replay_opts *opts)
                        if (res)
                                goto release_todo_list;
                }
-               if (index_differs_from("HEAD", 0, 0)) {
+               if (index_differs_from("HEAD", NULL, 0)) {
                        res = error_dirty_index(opts);
                        goto release_todo_list;
                }
@@ -2443,14 +2453,16 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
        strbuf_release(&sob);
 }
 
-int sequencer_make_script(int keep_empty, FILE *out,
-               int argc, const char **argv)
+int sequencer_make_script(FILE *out, int argc, const char **argv,
+                         unsigned flags)
 {
        char *format = NULL;
        struct pretty_print_context pp = {0};
        struct strbuf buf = STRBUF_INIT;
        struct rev_info revs;
        struct commit *commit;
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+       const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
@@ -2483,7 +2495,8 @@ int sequencer_make_script(int keep_empty, FILE *out,
                strbuf_reset(&buf);
                if (!keep_empty && is_original_commit_empty(commit))
                        strbuf_addf(&buf, "%c ", comment_line_char);
-               strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
+               strbuf_addf(&buf, "%s %s ", insn,
+                           oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, &buf);
                strbuf_addch(&buf, '\n');
                fputs(buf.buf, out);
@@ -2492,61 +2505,90 @@ int sequencer_make_script(int keep_empty, FILE *out,
        return 0;
 }
 
-
-int transform_todo_ids(int shorten_ids)
+/*
+ * Add commands after pick and (series of) squash/fixup commands
+ * in the todo list.
+ */
+int sequencer_add_exec_commands(const char *commands)
 {
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
-       int fd, res, i;
-       FILE *out;
+       struct todo_item *item;
+       struct strbuf *buf = &todo_list.buf;
+       size_t offset = 0, commands_len = strlen(commands);
+       int i, first;
 
-       strbuf_reset(&todo_list.buf);
-       fd = open(todo_file, O_RDONLY);
-       if (fd < 0)
-               return error_errno(_("could not open '%s'"), todo_file);
-       if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
-               close(fd);
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error(_("could not read '%s'."), todo_file);
-       }
-       close(fd);
 
-       res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
-       if (res) {
+       if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
                return error(_("unusable todo list: '%s'"), todo_file);
        }
 
-       out = fopen(todo_file, "w");
-       if (!out) {
+       first = 1;
+       /* insert <commands> before every pick except the first one */
+       for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
+               if (item->command == TODO_PICK && !first) {
+                       strbuf_insert(buf, item->offset_in_buf + offset,
+                                     commands, commands_len);
+                       offset += commands_len;
+               }
+               first = 0;
+       }
+
+       /* append final <commands> */
+       strbuf_add(buf, commands, commands_len);
+
+       i = write_message(buf->buf, buf->len, todo_file, 0);
+       todo_list_release(&todo_list);
+       return i;
+}
+
+int transform_todos(unsigned flags)
+{
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       struct strbuf buf = STRBUF_INIT;
+       struct todo_item *item;
+       int i;
+
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+               return error(_("could not read '%s'."), todo_file);
+
+       if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
-               return error(_("unable to open '%s' for writing"), todo_file);
+               return error(_("unusable todo list: '%s'"), todo_file);
        }
-       for (i = 0; i < todo_list.nr; i++) {
-               struct todo_item *item = todo_list.items + i;
-               int bol = item->offset_in_buf;
-               const char *p = todo_list.buf.buf + bol;
-               int eol = i + 1 < todo_list.nr ?
-                       todo_list.items[i + 1].offset_in_buf :
-                       todo_list.buf.len;
-
-               if (item->command >= TODO_EXEC && item->command != TODO_DROP)
-                       fwrite(p, eol - bol, 1, out);
-               else {
-                       const char *id = shorten_ids ?
-                               short_commit_name(item->commit) :
-                               oid_to_hex(&item->commit->object.oid);
-                       int len;
-
-                       p += strspn(p, " \t"); /* left-trim command */
-                       len = strcspn(p, " \t"); /* length of command */
-
-                       fprintf(out, "%.*s %s %.*s\n",
-                               len, p, id, item->arg_len, item->arg);
+
+       for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
+               /* if the item is not a command write it and continue */
+               if (item->command >= TODO_COMMENT) {
+                       strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg);
+                       continue;
                }
+
+               /* add command to the buffer */
+               if (flags & TODO_LIST_ABBREVIATE_CMDS)
+                       strbuf_addch(&buf, command_to_char(item->command));
+               else
+                       strbuf_addstr(&buf, command_to_string(item->command));
+
+               /* add commit id */
+               if (item->commit) {
+                       const char *oid = flags & TODO_LIST_SHORTEN_IDS ?
+                                         short_commit_name(item->commit) :
+                                         oid_to_hex(&item->commit->object.oid);
+
+                       strbuf_addf(&buf, " %s", oid);
+               }
+               /* add all the rest */
+               strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg);
        }
-       fclose(out);
+
+       i = write_message(buf.buf, buf.len, todo_file, 0);
        todo_list_release(&todo_list);
-       return 0;
+       return i;
 }
 
 enum check_level {
index 6f3d3df82c0ade64b7b125acd49bf3f5e15c53af..81f6d7d393fd1a502e3763cd1717b87ffa243717 100644 (file)
@@ -45,10 +45,14 @@ int sequencer_continue(struct replay_opts *opts);
 int sequencer_rollback(struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-int sequencer_make_script(int keep_empty, FILE *out,
-               int argc, const char **argv);
+#define TODO_LIST_KEEP_EMPTY (1U << 0)
+#define TODO_LIST_SHORTEN_IDS (1U << 1)
+#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
+int sequencer_make_script(FILE *out, int argc, const char **argv,
+                         unsigned flags);
 
-int transform_todo_ids(int shorten_ids);
+int sequencer_add_exec_commands(const char *command);
+int transform_todos(unsigned flags);
 int check_todo_list(void);
 int skip_unnecessary_picks(void);
 int rearrange_squash(void);
diff --git a/setup.c b/setup.c
index 94768512b7913c4e46b90fc8226c183cbd7239d7..8cc34186ce1f918ce5a9c8fc22ea81b7c645ff17 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -434,16 +434,15 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
        return 0;
 }
 
-static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
+static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)
 {
        struct strbuf sb = STRBUF_INIT;
        struct strbuf err = STRBUF_INIT;
-       struct repository_format candidate;
        int has_common;
 
        has_common = get_common_dir(&sb, gitdir);
        strbuf_addstr(&sb, "/config");
-       read_repository_format(&candidate, sb.buf);
+       read_repository_format(candidate, sb.buf);
        strbuf_release(&sb);
 
        /*
@@ -451,10 +450,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
         * we treat a missing config as a silent "ok", even when nongit_ok
         * is unset.
         */
-       if (candidate.version < 0)
+       if (candidate->version < 0)
                return 0;
 
-       if (verify_repository_format(&candidate, &err) < 0) {
+       if (verify_repository_format(candidate, &err) < 0) {
                if (nongit_ok) {
                        warning("%s", err.buf);
                        strbuf_release(&err);
@@ -464,21 +463,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
                die("%s", err.buf);
        }
 
-       repository_format_precious_objects = candidate.precious_objects;
-       string_list_clear(&candidate.unknown_extensions, 0);
+       repository_format_precious_objects = candidate->precious_objects;
+       string_list_clear(&candidate->unknown_extensions, 0);
        if (!has_common) {
-               if (candidate.is_bare != -1) {
-                       is_bare_repository_cfg = candidate.is_bare;
+               if (candidate->is_bare != -1) {
+                       is_bare_repository_cfg = candidate->is_bare;
                        if (is_bare_repository_cfg == 1)
                                inside_work_tree = -1;
                }
-               if (candidate.work_tree) {
+               if (candidate->work_tree) {
                        free(git_work_tree_cfg);
-                       git_work_tree_cfg = candidate.work_tree;
+                       git_work_tree_cfg = candidate->work_tree;
                        inside_work_tree = -1;
                }
        } else {
-               free(candidate.work_tree);
+               free(candidate->work_tree);
        }
 
        return 0;
@@ -489,6 +488,7 @@ int read_repository_format(struct repository_format *format, const char *path)
        memset(format, 0, sizeof(*format));
        format->version = -1;
        format->is_bare = -1;
+       format->hash_algo = GIT_HASH_SHA1;
        string_list_init(&format->unknown_extensions, 1);
        git_config_from_file(check_repo_format, path, format);
        return format->version;
@@ -625,6 +625,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 
 static const char *setup_explicit_git_dir(const char *gitdirenv,
                                          struct strbuf *cwd,
+                                         struct repository_format *repo_fmt,
                                          int *nongit_ok)
 {
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
@@ -650,7 +651,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                die("Not a git repository: '%s'", gitdirenv);
        }
 
-       if (check_repository_format_gently(gitdirenv, nongit_ok)) {
+       if (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) {
                free(gitfile);
                return NULL;
        }
@@ -723,9 +724,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 
 static const char *setup_discovered_git_dir(const char *gitdir,
                                            struct strbuf *cwd, int offset,
+                                           struct repository_format *repo_fmt,
                                            int *nongit_ok)
 {
-       if (check_repository_format_gently(gitdir, nongit_ok))
+       if (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))
                return NULL;
 
        /* --work-tree is set without --git-dir; use discovered one */
@@ -737,7 +739,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
                        gitdir = to_free = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok);
+               ret = setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok);
                free(to_free);
                return ret;
        }
@@ -769,11 +771,12 @@ static const char *setup_discovered_git_dir(const char *gitdir,
 
 /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
 static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
+                                     struct repository_format *repo_fmt,
                                      int *nongit_ok)
 {
        int root_len;
 
-       if (check_repository_format_gently(".", nongit_ok))
+       if (check_repository_format_gently(".", repo_fmt, nongit_ok))
                return NULL;
 
        setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
@@ -785,7 +788,7 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
                gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
+               return setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok);
        }
 
        inside_git_dir = 1;
@@ -923,7 +926,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
         * - ../.git
         * - ../.git/
         * - ../ (bare)
-        * - ../../.git/
+        * - ../../.git
         *   etc.
         */
        one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
@@ -1026,6 +1029,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
        static struct strbuf cwd = STRBUF_INIT;
        struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
        const char *prefix;
+       struct repository_format repo_fmt;
 
        /*
         * We may have read an incomplete configuration before
@@ -1053,18 +1057,18 @@ const char *setup_git_directory_gently(int *nongit_ok)
                prefix = NULL;
                break;
        case GIT_DIR_EXPLICIT:
-               prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
+               prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_DISCOVERED:
                if (dir.len < cwd.len && chdir(dir.buf))
                        die(_("Cannot change to '%s'"), dir.buf);
                prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
-                                                 nongit_ok);
+                                                 &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_BARE:
                if (dir.len < cwd.len && chdir(dir.buf))
                        die(_("Cannot change to '%s'"), dir.buf);
-               prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
+               prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_HIT_CEILING:
                prefix = setup_nongit(cwd.buf, nongit_ok);
@@ -1110,6 +1114,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
                        repo_set_gitdir(the_repository, gitdir);
                        setup_git_env();
                }
+               if (startup_info->have_repository)
+                       repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
        }
 
        strbuf_release(&dir);
@@ -1171,7 +1177,8 @@ int git_config_perm(const char *var, const char *value)
 
 void check_repository_format(void)
 {
-       check_repository_format_gently(get_git_dir(), NULL);
+       struct repository_format repo_fmt;
+       check_repository_format_gently(get_git_dir(), &repo_fmt, NULL);
        startup_info->have_repository = 1;
 }
 
index 36f85455e6748efb7c2d593c7a619fa13150600b..3da70ac650a8cdeca6ef8a6a424a7740d38267d5 100644 (file)
@@ -39,6 +39,64 @@ const struct object_id empty_blob_oid = {
        EMPTY_BLOB_SHA1_BIN_LITERAL
 };
 
+static void git_hash_sha1_init(void *ctx)
+{
+       git_SHA1_Init((git_SHA_CTX *)ctx);
+}
+
+static void git_hash_sha1_update(void *ctx, const void *data, size_t len)
+{
+       git_SHA1_Update((git_SHA_CTX *)ctx, data, len);
+}
+
+static void git_hash_sha1_final(unsigned char *hash, void *ctx)
+{
+       git_SHA1_Final(hash, (git_SHA_CTX *)ctx);
+}
+
+static void git_hash_unknown_init(void *ctx)
+{
+       die("trying to init unknown hash");
+}
+
+static void git_hash_unknown_update(void *ctx, const void *data, size_t len)
+{
+       die("trying to update unknown hash");
+}
+
+static void git_hash_unknown_final(unsigned char *hash, void *ctx)
+{
+       die("trying to finalize unknown hash");
+}
+
+const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
+       {
+               NULL,
+               0x00000000,
+               0,
+               0,
+               0,
+               git_hash_unknown_init,
+               git_hash_unknown_update,
+               git_hash_unknown_final,
+               NULL,
+               NULL,
+       },
+       {
+               "sha-1",
+               /* "sha1", big-endian */
+               0x73686131,
+               sizeof(git_SHA_CTX),
+               GIT_SHA1_RAWSZ,
+               GIT_SHA1_HEXSZ,
+               git_hash_sha1_init,
+               git_hash_sha1_update,
+               git_hash_sha1_final,
+               &empty_tree_oid,
+               &empty_blob_oid,
+       },
+};
+
 /*
  * This is meant to hold a *small* number of objects that you would
  * want read_sha1_file() to be able to return, but yet you do not want
@@ -74,6 +132,18 @@ static struct cached_object *find_cached_object(const unsigned char *sha1)
        return NULL;
 }
 
+
+static enum safe_crlf get_safe_crlf(unsigned flags)
+{
+       if (flags & HASH_RENORMALIZE)
+               return SAFE_CRLF_RENORMALIZE;
+       else if (flags & HASH_WRITE_OBJECT)
+               return safe_crlf;
+       else
+               return SAFE_CRLF_FALSE;
+}
+
+
 int mkdir_in_gitdir(const char *path)
 {
        if (mkdir(path, 0777)) {
@@ -459,19 +529,19 @@ struct alternate_object_database *alloc_alt_odb(const char *dir)
 
 void add_to_alternates_file(const char *reference)
 {
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       struct lock_file lock = LOCK_INIT;
        char *alts = git_pathdup("objects/info/alternates");
        FILE *in, *out;
+       int found = 0;
 
-       hold_lock_file_for_update(lock, alts, LOCK_DIE_ON_ERROR);
-       out = fdopen_lock_file(lock, "w");
+       hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR);
+       out = fdopen_lock_file(&lock, "w");
        if (!out)
                die_errno("unable to fdopen alternates lockfile");
 
        in = fopen(alts, "r");
        if (in) {
                struct strbuf line = STRBUF_INIT;
-               int found = 0;
 
                while (strbuf_getline(&line, in) != EOF) {
                        if (!strcmp(reference, line.buf)) {
@@ -483,18 +553,15 @@ void add_to_alternates_file(const char *reference)
 
                strbuf_release(&line);
                fclose(in);
-
-               if (found) {
-                       rollback_lock_file(lock);
-                       lock = NULL;
-               }
        }
        else if (errno != ENOENT)
                die_errno("unable to read alternates file");
 
-       if (lock) {
+       if (found) {
+               rollback_lock_file(&lock);
+       } else {
                fprintf_or_die(out, "%s\n", reference);
-               if (commit_lock_file(lock))
+               if (commit_lock_file(&lock))
                        die_errno("unable to move new alternates file into place");
                if (alt_odb_tail)
                        link_alt_odb_entries(reference, '\n', NULL, 0);
@@ -1155,6 +1222,9 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
                                    lookup_replace_object(sha1) :
                                    sha1;
 
+       if (is_null_sha1(real))
+               return -1;
+
        if (!oi)
                oi = &blank_oi;
 
@@ -1666,7 +1736,7 @@ static void check_tag(const void *buf, size_t size)
                die("corrupt tag");
 }
 
-static int index_mem(unsigned char *sha1, void *buf, size_t size,
+static int index_mem(struct object_id *oid, void *buf, size_t size,
                     enum object_type type,
                     const char *path, unsigned flags)
 {
@@ -1682,7 +1752,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
        if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf = STRBUF_INIT;
                if (convert_to_git(&the_index, path, buf, size, &nbuf,
-                                  write_object ? safe_crlf : SAFE_CRLF_FALSE)) {
+                                  get_safe_crlf(flags))) {
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
@@ -1697,15 +1767,15 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
        }
 
        if (write_object)
-               ret = write_sha1_file(buf, size, typename(type), sha1);
+               ret = write_sha1_file(buf, size, typename(type), oid->hash);
        else
-               ret = hash_sha1_file(buf, size, typename(type), sha1);
+               ret = hash_sha1_file(buf, size, typename(type), oid->hash);
        if (re_allocated)
                free(buf);
        return ret;
 }
 
-static int index_stream_convert_blob(unsigned char *sha1, int fd,
+static int index_stream_convert_blob(struct object_id *oid, int fd,
                                     const char *path, unsigned flags)
 {
        int ret;
@@ -1716,26 +1786,26 @@ static int index_stream_convert_blob(unsigned char *sha1, int fd,
        assert(would_convert_to_git_filter_fd(path));
 
        convert_to_git_filter_fd(&the_index, path, fd, &sbuf,
-                                write_object ? safe_crlf : SAFE_CRLF_FALSE);
+                                get_safe_crlf(flags));
 
        if (write_object)
                ret = write_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
-                                     sha1);
+                                     oid->hash);
        else
                ret = hash_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
-                                    sha1);
+                                    oid->hash);
        strbuf_release(&sbuf);
        return ret;
 }
 
-static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
+static int index_pipe(struct object_id *oid, int fd, enum object_type type,
                      const char *path, unsigned flags)
 {
        struct strbuf sbuf = STRBUF_INIT;
        int ret;
 
        if (strbuf_read(&sbuf, fd, 4096) >= 0)
-               ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags);
+               ret = index_mem(oid, sbuf.buf, sbuf.len, type, path, flags);
        else
                ret = -1;
        strbuf_release(&sbuf);
@@ -1744,14 +1814,14 @@ static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
 
 #define SMALL_FILE_SIZE (32*1024)
 
-static int index_core(unsigned char *sha1, int fd, size_t size,
+static int index_core(struct object_id *oid, int fd, size_t size,
                      enum object_type type, const char *path,
                      unsigned flags)
 {
        int ret;
 
        if (!size) {
-               ret = index_mem(sha1, "", size, type, path, flags);
+               ret = index_mem(oid, "", size, type, path, flags);
        } else if (size <= SMALL_FILE_SIZE) {
                char *buf = xmalloc(size);
                ssize_t read_result = read_in_full(fd, buf, size);
@@ -1762,11 +1832,11 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
                        ret = error("short read while indexing %s",
                                    path ? path : "<unknown>");
                else
-                       ret = index_mem(sha1, buf, size, type, path, flags);
+                       ret = index_mem(oid, buf, size, type, path, flags);
                free(buf);
        } else {
                void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-               ret = index_mem(sha1, buf, size, type, path, flags);
+               ret = index_mem(oid, buf, size, type, path, flags);
                munmap(buf, size);
        }
        return ret;
@@ -1804,12 +1874,12 @@ int index_fd(struct object_id *oid, int fd, struct stat *st,
         * die() for large files.
         */
        if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(path))
-               ret = index_stream_convert_blob(oid->hash, fd, path, flags);
+               ret = index_stream_convert_blob(oid, fd, path, flags);
        else if (!S_ISREG(st->st_mode))
-               ret = index_pipe(oid->hash, fd, type, path, flags);
+               ret = index_pipe(oid, fd, type, path, flags);
        else if (st->st_size <= big_file_threshold || type != OBJ_BLOB ||
                 (path && would_convert_to_git(&the_index, path)))
-               ret = index_core(oid->hash, fd, xsize_t(st->st_size), type, path,
+               ret = index_core(oid, fd, xsize_t(st->st_size), type, path,
                                 flags);
        else
                ret = index_stream(oid, fd, xsize_t(st->st_size), type, path,
@@ -1843,7 +1913,7 @@ int index_path(struct object_id *oid, const char *path, struct stat *st, unsigne
                strbuf_release(&sb);
                break;
        case S_IFDIR:
-               return resolve_gitlink_ref(path, "HEAD", oid->hash);
+               return resolve_gitlink_ref(path, "HEAD", oid);
        default:
                return error("%s: unsupported file type", path);
        }
@@ -1886,6 +1956,7 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
        DIR *dir;
        struct dirent *de;
        int r = 0;
+       struct object_id oid;
 
        if (subdir_nr > 0xff)
                BUG("invalid loose object subdirectory: %x", subdir_nr);
@@ -1893,7 +1964,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
        origlen = path->len;
        strbuf_complete(path, '/');
        strbuf_addf(path, "%02x", subdir_nr);
-       baselen = path->len;
 
        dir = opendir(path->buf);
        if (!dir) {
@@ -1903,27 +1973,27 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
                return r;
        }
 
+       oid.hash[0] = subdir_nr;
+       strbuf_addch(path, '/');
+       baselen = path->len;
+
        while ((de = readdir(dir))) {
+               size_t namelen;
                if (is_dot_or_dotdot(de->d_name))
                        continue;
 
+               namelen = strlen(de->d_name);
                strbuf_setlen(path, baselen);
-               strbuf_addf(path, "/%s", de->d_name);
-
-               if (strlen(de->d_name) == GIT_SHA1_HEXSZ - 2)  {
-                       char hex[GIT_MAX_HEXSZ+1];
-                       struct object_id oid;
-
-                       xsnprintf(hex, sizeof(hex), "%02x%s",
-                                 subdir_nr, de->d_name);
-                       if (!get_oid_hex(hex, &oid)) {
-                               if (obj_cb) {
-                                       r = obj_cb(&oid, path->buf, data);
-                                       if (r)
-                                               break;
-                               }
-                               continue;
+               strbuf_add(path, de->d_name, namelen);
+               if (namelen == GIT_SHA1_HEXSZ - 2 &&
+                   !hex_to_bytes(oid.hash + 1, de->d_name,
+                                 GIT_SHA1_RAWSZ - 1)) {
+                       if (obj_cb) {
+                               r = obj_cb(&oid, path->buf, data);
+                               if (r)
+                                       break;
                        }
+                       continue;
                }
 
                if (cruft_cb) {
@@ -1934,7 +2004,7 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
        }
        closedir(dir);
 
-       strbuf_setlen(path, baselen);
+       strbuf_setlen(path, baselen - 1);
        if (!r && subdir_cb)
                r = subdir_cb(subdir_nr, path->buf, data);
 
index 603e667faa6b935ed5c44fc7d67188088bdfaa84..611c7d24ddee678470ba74cb3c2ca669a778b44a 100644 (file)
@@ -153,7 +153,9 @@ static void unique_in_pack(struct packed_git *p,
        uint32_t num, last, i, first = 0;
        const struct object_id *current = NULL;
 
-       open_pack_index(p);
+       if (open_pack_index(p) || !p->num_objects)
+               return;
+
        num = p->num_objects;
        last = num;
        while (first < last) {
@@ -474,10 +476,104 @@ static unsigned msb(unsigned long val)
        return r;
 }
 
-int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len)
+struct min_abbrev_data {
+       unsigned int init_len;
+       unsigned int cur_len;
+       char *hex;
+       const unsigned char *hash;
+};
+
+static inline char get_hex_char_from_oid(const struct object_id *oid,
+                                        unsigned int pos)
+{
+       static const char hex[] = "0123456789abcdef";
+
+       if ((pos & 1) == 0)
+               return hex[oid->hash[pos >> 1] >> 4];
+       else
+               return hex[oid->hash[pos >> 1] & 0xf];
+}
+
+static int extend_abbrev_len(const struct object_id *oid, void *cb_data)
+{
+       struct min_abbrev_data *mad = cb_data;
+
+       unsigned int i = mad->init_len;
+       while (mad->hex[i] && mad->hex[i] == get_hex_char_from_oid(oid, i))
+               i++;
+
+       if (i < GIT_MAX_RAWSZ && i >= mad->cur_len)
+               mad->cur_len = i + 1;
+
+       return 0;
+}
+
+static void find_abbrev_len_for_pack(struct packed_git *p,
+                                    struct min_abbrev_data *mad)
+{
+       int match = 0;
+       uint32_t num, last, first = 0;
+       struct object_id oid;
+
+       if (open_pack_index(p) || !p->num_objects)
+               return;
+
+       num = p->num_objects;
+       last = num;
+       while (first < last) {
+               uint32_t mid = first + (last - first) / 2;
+               const unsigned char *current;
+               int cmp;
+
+               current = nth_packed_object_sha1(p, mid);
+               cmp = hashcmp(mad->hash, current);
+               if (!cmp) {
+                       match = 1;
+                       first = mid;
+                       break;
+               }
+               if (cmp > 0) {
+                       first = mid + 1;
+                       continue;
+               }
+               last = mid;
+       }
+
+       /*
+        * first is now the position in the packfile where we would insert
+        * mad->hash if it does not exist (or the position of mad->hash if
+        * it does exist). Hence, we consider a maximum of three objects
+        * nearby for the abbreviation length.
+        */
+       mad->init_len = 0;
+       if (!match) {
+               nth_packed_object_oid(&oid, p, first);
+               extend_abbrev_len(&oid, mad);
+       } else if (first < num - 1) {
+               nth_packed_object_oid(&oid, p, first + 1);
+               extend_abbrev_len(&oid, mad);
+       }
+       if (first > 0) {
+               nth_packed_object_oid(&oid, p, first - 1);
+               extend_abbrev_len(&oid, mad);
+       }
+       mad->init_len = mad->cur_len;
+}
+
+static void find_abbrev_len_packed(struct min_abbrev_data *mad)
 {
-       int status, exists;
+       struct packed_git *p;
+
+       prepare_packed_git();
+       for (p = packed_git; p; p = p->next)
+               find_abbrev_len_for_pack(p, mad);
+}
 
+int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len)
+{
+       struct disambiguate_state ds;
+       struct min_abbrev_data mad;
+       struct object_id oid_ret;
        if (len < 0) {
                unsigned long count = approximate_object_count();
                /*
@@ -503,19 +599,26 @@ int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len)
        sha1_to_hex_r(hex, sha1);
        if (len == GIT_SHA1_HEXSZ || !len)
                return GIT_SHA1_HEXSZ;
-       exists = has_sha1_file(sha1);
-       while (len < GIT_SHA1_HEXSZ) {
-               struct object_id oid_ret;
-               status = get_short_oid(hex, len, &oid_ret, GET_OID_QUIETLY);
-               if (exists
-                   ? !status
-                   : status == SHORT_NAME_NOT_FOUND) {
-                       hex[len] = 0;
-                       return len;
-               }
-               len++;
-       }
-       return len;
+
+       mad.init_len = len;
+       mad.cur_len = len;
+       mad.hex = hex;
+       mad.hash = sha1;
+
+       find_abbrev_len_packed(&mad);
+
+       if (init_object_disambiguation(hex, mad.cur_len, &ds) < 0)
+               return -1;
+
+       ds.fn = extend_abbrev_len;
+       ds.always_call_fn = 1;
+       ds.cb_data = (void *)&mad;
+
+       find_short_object_filename(&ds);
+       (void)finish_object_disambiguation(&ds, &oid_ret);
+
+       hex[mad.cur_len] = 0;
+       return mad.cur_len;
 }
 
 const char *find_unique_abbrev(const unsigned char *sha1, int len)
@@ -603,7 +706,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 
        if (len == GIT_SHA1_HEXSZ && !get_oid_hex(str, oid)) {
                if (warn_ambiguous_refs && warn_on_object_refname_ambiguity) {
-                       refs_found = dwim_ref(str, len, tmp_oid.hash, &real_ref);
+                       refs_found = dwim_ref(str, len, &tmp_oid, &real_ref);
                        if (refs_found > 0) {
                                warning(warn_msg, len, str);
                                if (advice_object_name_warning)
@@ -654,11 +757,11 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 
        if (!len && reflog_len)
                /* allow "@{...}" to mean the current branch reflog */
-               refs_found = dwim_ref("HEAD", 4, oid->hash, &real_ref);
+               refs_found = dwim_ref("HEAD", 4, oid, &real_ref);
        else if (reflog_len)
-               refs_found = dwim_log(str, len, oid->hash, &real_ref);
+               refs_found = dwim_log(str, len, oid, &real_ref);
        else
-               refs_found = dwim_ref(str, len, oid->hash, &real_ref);
+               refs_found = dwim_ref(str, len, oid, &real_ref);
 
        if (!refs_found)
                return -1;
@@ -697,7 +800,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
                                return -1;
                        }
                }
-               if (read_ref_at(real_ref, flags, at_time, nth, oid->hash, NULL,
+               if (read_ref_at(real_ref, flags, at_time, nth, oid, NULL,
                                &co_time, &co_tz, &co_cnt)) {
                        if (!len) {
                                if (starts_with(real_ref, "refs/heads/")) {
@@ -1335,9 +1438,19 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
                strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
        else
                strbuf_addstr(sb, name);
-       if (name[0] == '-')
-               return -1;
+
+       /*
+        * This splice must be done even if we end up rejecting the
+        * name; builtin/branch.c::copy_or_rename_branch() still wants
+        * to see what the name expanded to so that "branch -m" can be
+        * used as a tool to correct earlier mistakes.
+        */
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+
+       if (*name == '-' ||
+           !strcmp(sb->buf, "refs/heads/HEAD"))
+               return -1;
+
        return check_refname_format(sb->buf, 0);
 }
 
index 1e4d684d6c5dcdeee94f6eb64cfa6e2f17d44674..6d7f943e4384fa90001fbb73cc5b63e2c0cfa639 100644 (file)
 
 int recv_sideband(const char *me, int in_stream, int out)
 {
-       const char *term, *suffix;
+       const char *suffix;
        char buf[LARGE_PACKET_MAX + 1];
        struct strbuf outbuf = STRBUF_INIT;
        int retval = 0;
 
-       term = getenv("TERM");
-       if (isatty(2) && term && strcmp(term, "dumb"))
+       if (isatty(2) && !is_terminal_dumb())
                suffix = ANSI_SUFFIX;
        else
                suffix = DUMB_SUFFIX;
index 323c49ceb35cb053434248df869578e649ccadc0..8007be8fbafdc3f4542e27cfc9344044f71d43dc 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -11,6 +11,28 @@ int starts_with(const char *str, const char *prefix)
                        return 0;
 }
 
+int skip_to_optional_arg_default(const char *str, const char *prefix,
+                                const char **arg, const char *def)
+{
+       const char *p;
+
+       if (!skip_prefix(str, prefix, &p))
+               return 0;
+
+       if (!*p) {
+               if (arg)
+                       *arg = def;
+               return 1;
+       }
+
+       if (*p != '=')
+               return 0;
+
+       if (arg)
+               *arg = p + 1;
+       return 1;
+}
+
 /*
  * Used as the default ->buf value, so that people can always assume
  * buf is non NULL and ->buf is NUL terminated even for a freshly
@@ -386,12 +408,15 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 
 ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint)
 {
+       size_t oldalloc = sb->alloc;
        ssize_t cnt;
 
        strbuf_grow(sb, hint ? hint : 8192);
        cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
        if (cnt > 0)
                strbuf_setlen(sb, sb->len + cnt);
+       else if (oldalloc == 0)
+               strbuf_release(sb);
        return cnt;
 }
 
index 0a74acb2369ac26cb56b05a953171330c3abe652..14c8c10d66b9aaa2d8f0c109cf0dd668701cb2eb 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -480,15 +480,6 @@ extern int strbuf_normalize_path(struct strbuf *sb);
  */
 extern void strbuf_stripspace(struct strbuf *buf, int skip_comments);
 
-/**
- * Temporary alias until all topic branches have switched to use
- * strbuf_stripspace directly.
- */
-static inline void stripspace(struct strbuf *buf, int skip_comments)
-{
-       strbuf_stripspace(buf, skip_comments);
-}
-
 static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix)
 {
        if (strip_suffix_mem(sb->buf, &sb->len, suffix)) {
index e3845831f6cbd25097ea54b3e2246faa8a32e192..a5503a5d177e90e009be9240bfddd68c9ead475b 100644 (file)
@@ -22,6 +22,9 @@ struct submodule {
        int recommend_shallow;
 };
 
+#define SUBMODULE_INIT { NULL, NULL, NULL, RECURSE_SUBMODULES_NONE, \
+       NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, {0}, -1 };
+
 struct submodule_cache;
 struct repository;
 
index 63e7094e1620c2c3eb7dda0a8c38a833ae2d1c5a..29677043174f8d77d6774bc2533b653d03adf990 100644 (file)
@@ -1,3 +1,5 @@
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
+
 #include "cache.h"
 #include "repository.h"
 #include "config.h"
@@ -21,7 +23,7 @@
 #include "parse-options.h"
 
 static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
-static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
+static struct string_list changed_submodule_names = STRING_LIST_INIT_DUP;
 static int initialized_fetch_ref_tips;
 static struct oid_array ref_tips_before_fetch;
 static struct oid_array ref_tips_after_fetch;
@@ -55,14 +57,15 @@ int is_gitmodules_unmerged(const struct index_state *istate)
  * future version when we learn to stage the changes we do ourselves without
  * staging any previous modifications.
  */
-int is_staging_gitmodules_ok(const struct index_state *istate)
+int is_staging_gitmodules_ok(struct index_state *istate)
 {
        int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 
        if ((pos >= 0) && (pos < istate->cache_nr)) {
                struct stat st;
                if (lstat(GITMODULES_FILE, &st) == 0 &&
-                   ce_match_stat(istate->cache[pos], &st, 0) & DATA_CHANGED)
+                   ie_match_stat(istate, istate->cache[pos], &st,
+                                 CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED)
                        return 0;
        }
 
@@ -143,9 +146,9 @@ int remove_path_from_gitmodules(const char *path)
        return 0;
 }
 
-void stage_updated_gitmodules(void)
+void stage_updated_gitmodules(struct index_state *istate)
 {
-       if (add_file_to_cache(GITMODULES_FILE, 0))
+       if (add_file_to_index(istate, GITMODULES_FILE, 0))
                die(_("staging updated .gitmodules failed"));
 }
 
@@ -183,7 +186,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                if (ignore)
                        handle_ignore_submodules_arg(diffopt, ignore);
                else if (is_gitmodules_unmerged(&the_index))
-                       DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
+                       diffopt->flags.ignore_submodules = 1;
        }
 }
 
@@ -402,16 +405,16 @@ const char *submodule_strategy_to_string(const struct submodule_update_strategy
 void handle_ignore_submodules_arg(struct diff_options *diffopt,
                                  const char *arg)
 {
-       DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES);
-       DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
-       DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES);
+       diffopt->flags.ignore_submodules = 0;
+       diffopt->flags.ignore_untracked_in_submodules = 0;
+       diffopt->flags.ignore_dirty_submodules = 0;
 
        if (!strcmp(arg, "all"))
-               DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
+               diffopt->flags.ignore_submodules = 1;
        else if (!strcmp(arg, "untracked"))
-               DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+               diffopt->flags.ignore_untracked_in_submodules = 1;
        else if (!strcmp(arg, "dirty"))
-               DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
+               diffopt->flags.ignore_dirty_submodules = 1;
        else if (strcmp(arg, "none"))
                die("bad --ignore-submodules argument: %s", arg);
 }
@@ -587,7 +590,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
                unsigned dirty_submodule)
 {
-       const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
+       const struct object_id *old = the_hash_algo->empty_tree, *new = the_hash_algo->empty_tree;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
        struct child_process cp = CHILD_PROCESS_INIT;
@@ -616,7 +619,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
        argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ?
                         "always" : "never");
 
-       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+       if (o->flags.reverse_diff) {
                argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
                                 o->b_prefix, path);
                argv_array_pushf(&cp.args, "--dst-prefix=%s%s/",
@@ -674,11 +677,11 @@ const struct submodule *submodule_from_ce(const struct cache_entry *ce)
 }
 
 static struct oid_array *submodule_commits(struct string_list *submodules,
-                                          const char *path)
+                                          const char *name)
 {
        struct string_list_item *item;
 
-       item = string_list_insert(submodules, path);
+       item = string_list_insert(submodules, name);
        if (item->util)
                return (struct oid_array *) item->util;
 
@@ -687,39 +690,67 @@ static struct oid_array *submodule_commits(struct string_list *submodules,
        return (struct oid_array *) item->util;
 }
 
+struct collect_changed_submodules_cb_data {
+       struct string_list *changed;
+       const struct object_id *commit_oid;
+};
+
+/*
+ * this would normally be two functions: default_name_from_path() and
+ * path_from_default_name(). Since the default name is the same as
+ * the submodule path we can get away with just one function which only
+ * checks whether there is a submodule in the working directory at that
+ * location.
+ */
+static const char *default_name_or_path(const char *path_or_name)
+{
+       int error_code;
+
+       if (!is_submodule_populated_gently(path_or_name, &error_code))
+               return NULL;
+
+       return path_or_name;
+}
+
 static void collect_changed_submodules_cb(struct diff_queue_struct *q,
                                          struct diff_options *options,
                                          void *data)
 {
+       struct collect_changed_submodules_cb_data *me = data;
+       struct string_list *changed = me->changed;
+       const struct object_id *commit_oid = me->commit_oid;
        int i;
-       struct string_list *changed = data;
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                struct oid_array *commits;
+               const struct submodule *submodule;
+               const char *name;
+
                if (!S_ISGITLINK(p->two->mode))
                        continue;
 
-               if (S_ISGITLINK(p->one->mode)) {
-                       /*
-                        * NEEDSWORK: We should honor the name configured in
-                        * the .gitmodules file of the commit we are examining
-                        * here to be able to correctly follow submodules
-                        * being moved around.
-                        */
-                       commits = submodule_commits(changed, p->two->path);
-                       oid_array_append(commits, &p->two->oid);
-               } else {
-                       /* Submodule is new or was moved here */
-                       /*
-                        * NEEDSWORK: When the .git directories of submodules
-                        * live inside the superprojects .git directory some
-                        * day we should fetch new submodules directly into
-                        * that location too when config or options request
-                        * that so they can be checked out from there.
-                        */
-                       continue;
+               submodule = submodule_from_path(commit_oid, p->two->path);
+               if (submodule)
+                       name = submodule->name;
+               else {
+                       name = default_name_or_path(p->two->path);
+                       /* make sure name does not collide with existing one */
+                       submodule = submodule_from_name(commit_oid, name);
+                       if (submodule) {
+                               warning("Submodule in commit %s at path: "
+                                       "'%s' collides with a submodule named "
+                                       "the same. Skipping it.",
+                                       oid_to_hex(commit_oid), name);
+                               name = NULL;
+                       }
                }
+
+               if (!name)
+                       continue;
+
+               commits = submodule_commits(changed, name);
+               oid_array_append(commits, &p->two->oid);
        }
 }
 
@@ -742,11 +773,14 @@ static void collect_changed_submodules(struct string_list *changed,
 
        while ((commit = get_revision(&rev))) {
                struct rev_info diff_rev;
+               struct collect_changed_submodules_cb_data data;
+               data.changed = changed;
+               data.commit_oid = &commit->object.oid;
 
                init_revisions(&diff_rev, NULL);
                diff_rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
                diff_rev.diffopt.format_callback = collect_changed_submodules_cb;
-               diff_rev.diffopt.format_callback_data = changed;
+               diff_rev.diffopt.format_callback_data = &data;
                diff_tree_combined_merge(commit, 1, &diff_rev);
        }
 
@@ -894,7 +928,7 @@ int find_unpushed_submodules(struct oid_array *commits,
                const char *remotes_name, struct string_list *needs_pushing)
 {
        struct string_list submodules = STRING_LIST_INIT_DUP;
-       struct string_list_item *submodule;
+       struct string_list_item *name;
        struct argv_array argv = ARGV_ARRAY_INIT;
 
        /* argv.argv[0] will be ignored by setup_revisions */
@@ -905,9 +939,19 @@ int find_unpushed_submodules(struct oid_array *commits,
 
        collect_changed_submodules(&submodules, &argv);
 
-       for_each_string_list_item(submodule, &submodules) {
-               struct oid_array *commits = submodule->util;
-               const char *path = submodule->string;
+       for_each_string_list_item(name, &submodules) {
+               struct oid_array *commits = name->util;
+               const struct submodule *submodule;
+               const char *path = NULL;
+
+               submodule = submodule_from_name(&null_oid, name->string);
+               if (submodule)
+                       path = submodule->path;
+               else
+                       path = default_name_or_path(name->string);
+
+               if (!path)
+                       continue;
 
                if (submodule_needs_pushing(path, commits))
                        string_list_insert(needs_pushing, path);
@@ -1016,7 +1060,7 @@ int push_unpushed_submodules(struct oid_array *commits,
                char *head;
                struct object_id head_oid;
 
-               head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
+               head = resolve_refdup("HEAD", 0, &head_oid, NULL);
                if (!head)
                        die(_("Failed to resolve HEAD as a valid ref."));
 
@@ -1065,7 +1109,7 @@ static void calculate_changed_submodule_paths(void)
 {
        struct argv_array argv = ARGV_ARRAY_INIT;
        struct string_list changed_submodules = STRING_LIST_INIT_DUP;
-       const struct string_list_item *item;
+       const struct string_list_item *name;
 
        /* No need to check if there are no submodules configured */
        if (!submodule_from_path(NULL, NULL))
@@ -1080,16 +1124,26 @@ static void calculate_changed_submodule_paths(void)
 
        /*
         * Collect all submodules (whether checked out or not) for which new
-        * commits have been recorded upstream in "changed_submodule_paths".
+        * commits have been recorded upstream in "changed_submodule_names".
         */
        collect_changed_submodules(&changed_submodules, &argv);
 
-       for_each_string_list_item(item, &changed_submodules) {
-               struct oid_array *commits = item->util;
-               const char *path = item->string;
+       for_each_string_list_item(name, &changed_submodules) {
+               struct oid_array *commits = name->util;
+               const struct submodule *submodule;
+               const char *path = NULL;
+
+               submodule = submodule_from_name(&null_oid, name->string);
+               if (submodule)
+                       path = submodule->path;
+               else
+                       path = default_name_or_path(name->string);
+
+               if (!path)
+                       continue;
 
                if (!submodule_has_commits(path, commits))
-                       string_list_append(&changed_submodule_paths, path);
+                       string_list_append(&changed_submodule_names, name->string);
        }
 
        free_submodules_oids(&changed_submodules);
@@ -1127,7 +1181,7 @@ int submodule_touches_in_range(struct object_id *excl_oid,
 struct submodule_parallel_fetch {
        int count;
        struct argv_array args;
-       const char *work_tree;
+       struct repository *r;
        const char *prefix;
        int command_line_option;
        int default_option;
@@ -1136,65 +1190,76 @@ struct submodule_parallel_fetch {
 };
 #define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
 
+static int get_fetch_recurse_config(const struct submodule *submodule,
+                                   struct submodule_parallel_fetch *spf)
+{
+       if (spf->command_line_option != RECURSE_SUBMODULES_DEFAULT)
+               return spf->command_line_option;
+
+       if (submodule) {
+               char *key;
+               const char *value;
+
+               int fetch_recurse = submodule->fetch_recurse;
+               key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
+               if (!repo_config_get_string_const(spf->r, key, &value)) {
+                       fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
+               }
+               free(key);
+
+               if (fetch_recurse != RECURSE_SUBMODULES_NONE)
+                       /* local config overrules everything except commandline */
+                       return fetch_recurse;
+       }
+
+       return spf->default_option;
+}
+
 static int get_next_submodule(struct child_process *cp,
                              struct strbuf *err, void *data, void **task_cb)
 {
        int ret = 0;
        struct submodule_parallel_fetch *spf = data;
 
-       for (; spf->count < active_nr; spf->count++) {
+       for (; spf->count < spf->r->index->cache_nr; spf->count++) {
                struct strbuf submodule_path = STRBUF_INIT;
                struct strbuf submodule_git_dir = STRBUF_INIT;
                struct strbuf submodule_prefix = STRBUF_INIT;
-               const struct cache_entry *ce = active_cache[spf->count];
+               const struct cache_entry *ce = spf->r->index->cache[spf->count];
                const char *git_dir, *default_argv;
                const struct submodule *submodule;
+               struct submodule default_submodule = SUBMODULE_INIT;
 
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
 
-               submodule = submodule_from_path(&null_oid, ce->name);
-
-               default_argv = "yes";
-               if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) {
-                       int fetch_recurse = RECURSE_SUBMODULES_NONE;
-
-                       if (submodule) {
-                               char *key;
-                               const char *value;
-
-                               fetch_recurse = submodule->fetch_recurse;
-                               key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
-                               if (!repo_config_get_string_const(the_repository, key, &value)) {
-                                       fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
-                               }
-                               free(key);
+               submodule = submodule_from_cache(spf->r, &null_oid, ce->name);
+               if (!submodule) {
+                       const char *name = default_name_or_path(ce->name);
+                       if (name) {
+                               default_submodule.path = default_submodule.name = name;
+                               submodule = &default_submodule;
                        }
+               }
 
-                       if (fetch_recurse != RECURSE_SUBMODULES_NONE) {
-                               if (fetch_recurse == RECURSE_SUBMODULES_OFF)
-                                       continue;
-                               if (fetch_recurse == RECURSE_SUBMODULES_ON_DEMAND) {
-                                       if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
-                                               continue;
-                                       default_argv = "on-demand";
-                               }
-                       } else {
-                               if (spf->default_option == RECURSE_SUBMODULES_OFF)
-                                       continue;
-                               if (spf->default_option == RECURSE_SUBMODULES_ON_DEMAND) {
-                                       if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
-                                               continue;
-                                       default_argv = "on-demand";
-                               }
-                       }
-               } else if (spf->command_line_option == RECURSE_SUBMODULES_ON_DEMAND) {
-                       if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+               switch (get_fetch_recurse_config(submodule, spf))
+               {
+               default:
+               case RECURSE_SUBMODULES_DEFAULT:
+               case RECURSE_SUBMODULES_ON_DEMAND:
+                       if (!submodule || !unsorted_string_list_lookup(&changed_submodule_names,
+                                                        submodule->name))
                                continue;
                        default_argv = "on-demand";
+                       break;
+               case RECURSE_SUBMODULES_ON:
+                       default_argv = "yes";
+                       break;
+               case RECURSE_SUBMODULES_OFF:
+                       continue;
                }
 
-               strbuf_addf(&submodule_path, "%s/%s", spf->work_tree, ce->name);
+               strbuf_repo_worktree_path(&submodule_path, spf->r, "%s", ce->name);
                strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
                strbuf_addf(&submodule_prefix, "%s%s/", spf->prefix, ce->name);
                git_dir = read_gitfile(submodule_git_dir.buf);
@@ -1247,7 +1312,8 @@ static int fetch_finish(int retvalue, struct strbuf *err,
        return 0;
 }
 
-int fetch_populated_submodules(const struct argv_array *options,
+int fetch_populated_submodules(struct repository *r,
+                              const struct argv_array *options,
                               const char *prefix, int command_line_option,
                               int default_option,
                               int quiet, int max_parallel_jobs)
@@ -1255,16 +1321,16 @@ int fetch_populated_submodules(const struct argv_array *options,
        int i;
        struct submodule_parallel_fetch spf = SPF_INIT;
 
-       spf.work_tree = get_git_work_tree();
+       spf.r = r;
        spf.command_line_option = command_line_option;
        spf.default_option = default_option;
        spf.quiet = quiet;
        spf.prefix = prefix;
 
-       if (!spf.work_tree)
+       if (!r->worktree)
                goto out;
 
-       if (read_cache() < 0)
+       if (repo_read_index(r) < 0)
                die("index file corrupt");
 
        argv_array_push(&spf.args, "fetch");
@@ -1282,7 +1348,7 @@ int fetch_populated_submodules(const struct argv_array *options,
 
        argv_array_clear(&spf.args);
 out:
-       string_list_clear(&changed_submodule_paths, 1);
+       string_list_clear(&changed_submodule_names, 1);
        return spf.result;
 }
 
@@ -1608,7 +1674,8 @@ int submodule_move_head(const char *path,
                        cp.dir = path;
 
                        prepare_submodule_repo_env(&cp.env_array);
-                       argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL);
+                       argv_array_pushl(&cp.args, "update-ref", "HEAD",
+                                        "--no-deref", new, NULL);
 
                        if (run_command(&cp)) {
                                ret = -1;
index f0da0277a45b49369d89cebdd4d30f44bef23d3d..b9b7ef0030a732bb81745641413f9f013588c91d 100644 (file)
@@ -34,10 +34,10 @@ struct submodule_update_strategy {
 #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
 
 extern int is_gitmodules_unmerged(const struct index_state *istate);
-extern int is_staging_gitmodules_ok(const struct index_state *istate);
+extern int is_staging_gitmodules_ok(struct index_state *istate);
 extern int update_path_in_gitmodules(const char *oldpath, const char *newpath);
 extern int remove_path_from_gitmodules(const char *path);
-extern void stage_updated_gitmodules(void);
+extern void stage_updated_gitmodules(struct index_state *istate);
 extern void set_diffopt_flags_from_submodule_config(struct diff_options *,
                const char *path);
 extern int git_default_submodule_config(const char *var, const char *value, void *cb);
@@ -76,10 +76,12 @@ extern int should_update_submodules(void);
  */
 extern const struct submodule *submodule_from_ce(const struct cache_entry *ce);
 extern void check_for_new_submodule_commits(struct object_id *oid);
-extern int fetch_populated_submodules(const struct argv_array *options,
-                              const char *prefix, int command_line_option,
-                              int default_option,
-                              int quiet, int max_parallel_jobs);
+extern int fetch_populated_submodules(struct repository *r,
+                                     const struct argv_array *options,
+                                     const char *prefix,
+                                     int command_line_option,
+                                     int default_option,
+                                     int quiet, int max_parallel_jobs);
 extern unsigned is_submodule_modified(const char *path, int ignore_untracked);
 extern int submodule_uses_gitfile(const char *path);
 
index 1bb06c36f22d9fa0fb01fb8dfad02ffa9f338ebd..96317a35f4df9944cd3b9f64db0db3132d69da6e 100644 (file)
@@ -8,6 +8,7 @@
 
 #GIT_TEST_OPTS = --verbose --debug
 SHELL_PATH ?= $(SHELL)
+TEST_SHELL_PATH ?= $(SHELL_PATH)
 PERL_PATH ?= /usr/bin/perl
 TAR ?= $(TAR)
 RM ?= rm -f
@@ -23,6 +24,7 @@ endif
 
 # Shell quote;
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY))
 
@@ -42,11 +44,11 @@ failed:
        test -z "$$failed" || $(MAKE) $$failed
 
 prove: pre-clean $(TEST_LINT)
-       @echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+       @echo "*** prove ***"; $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
        $(MAKE) clean-except-prove-cache
 
 $(T):
-       @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+       @echo "*** $@ ***"; '$(TEST_SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
 
 pre-clean:
        $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
index 4b079e4494d9324f0b03dfe8f714b3717292c0b6..b3f7b449c366c2d7d7afd12139a54b7a588f9747 100644 (file)
--- a/t/README
+++ b/t/README
@@ -332,13 +332,10 @@ Writing Tests
 -------------
 
 The test script is written as a shell script.  It should start
-with the standard "#!/bin/sh" with copyright notices, and an
+with the standard "#!/bin/sh", and an
 assignment to variable 'test_description', like this:
 
        #!/bin/sh
-       #
-       # Copyright (c) 2005 Junio C Hamano
-       #
 
        test_description='xxx test (option --frotz)
 
@@ -677,6 +674,11 @@ library for your script to use.
    <expected> file.  This behaves like "cmp" but produces more
    helpful output when the test is run with "-v" option.
 
+ - test_cmp_rev <expected> <actual>
+
+   Check whether the <expected> rev points to the same commit as the
+   <actual> rev.
+
  - test_line_count (= | -lt | -ge | ...) <length> <file>
 
    Check whether a file has the length it is expected to.
@@ -808,6 +810,18 @@ use these, and "test_set_prereq" for how to define your own.
    Git was compiled with support for PCRE. Wrap any tests
    that use git-grep --perl-regexp or git-grep -P in these.
 
+ - LIBPCRE1
+
+   Git was compiled with PCRE v1 support via
+   USE_LIBPCRE1=YesPlease. Wrap any PCRE using tests that for some
+   reason need v1 of the PCRE library instead of v2 in these.
+
+ - LIBPCRE2
+
+   Git was compiled with PCRE v2 support via
+   USE_LIBPCRE2=YesPlease. Wrap any PCRE using tests that for some
+   reason need v2 of the PCRE library instead of v1 in these.
+
  - CASE_INSENSITIVE_FS
 
    Test is run on a case insensitive file system.
index 7c9d28a8348341c148f150c3371d09dd98693b53..2bad28af92413dbbf7de7ed8c1ba27a69ef2523f 100644 (file)
@@ -1,38 +1,5 @@
-/test-chmtime
-/test-ctype
-/test-config
-/test-date
-/test-delta
-/test-dump-cache-tree
-/test-dump-split-index
-/test-dump-untracked-cache
-/test-fake-ssh
-/test-scrap-cache-tree
-/test-genrandom
-/test-hashmap
-/test-index-version
-/test-lazy-init-name-hash
-/test-line-buffer
-/test-match-trees
-/test-mergesort
-/test-mktemp
-/test-online-cpus
-/test-parse-options
-/test-path-utils
-/test-prio-queue
-/test-read-cache
-/test-ref-store
-/test-regex
-/test-revision-walking
-/test-run-command
-/test-sha1
-/test-sha1-array
-/test-sigchain
-/test-strcmp-offset
-/test-string-list
-/test-submodule-config
-/test-subprocess
-/test-svn-fe
-/test-urlmatch-normalization
-/test-wildmatch
-/test-write-cache
+*
+!*.sh
+!*.[ch]
+!*.gitignore
+
index f414a3ac670fb2d77645f5d7301b6aa8f603379e..ac8368797073adfdf203f60d93df55a988443dbd 100644 (file)
@@ -5,6 +5,7 @@ static const char *usage_msg = "\n"
 "  test-date show:<format> [time_t]...\n"
 "  test-date parse [date]...\n"
 "  test-date approxidate [date]...\n"
+"  test-date timestamp [date]...\n"
 "  test-date is64bit\n"
 "  test-date time_t-is64bit\n";
 
@@ -71,6 +72,15 @@ static void parse_approxidate(const char **argv, struct timeval *now)
        }
 }
 
+static void parse_approx_timestamp(const char **argv, struct timeval *now)
+{
+       for (; *argv; argv++) {
+               timestamp_t t;
+               t = approxidate_relative(*argv, now);
+               printf("%s -> %"PRItime"\n", *argv, t);
+       }
+}
+
 int cmd_main(int argc, const char **argv)
 {
        struct timeval now;
@@ -95,6 +105,8 @@ int cmd_main(int argc, const char **argv)
                parse_dates(argv+1, &now);
        else if (!strcmp(*argv, "approxidate"))
                parse_approxidate(argv+1, &now);
+       else if (!strcmp(*argv, "timestamp"))
+               parse_approx_timestamp(argv+1, &now);
        else if (!strcmp(*argv, "is64bit"))
                return sizeof(timestamp_t) == 8 ? 0 : 1;
        else if (!strcmp(*argv, "time_t-is64bit"))
diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c
new file mode 100644 (file)
index 0000000..bd1a857
--- /dev/null
@@ -0,0 +1,164 @@
+#include "git-compat-util.h"
+
+#if defined(GIT_WINDOWS_NATIVE)
+
+static int cmd_sync(void)
+{
+       char Buffer[MAX_PATH];
+       DWORD dwRet;
+       char szVolumeAccessPath[] = "\\\\.\\X:";
+       HANDLE hVolWrite;
+       int success = 0;
+
+       dwRet = GetCurrentDirectory(MAX_PATH, Buffer);
+       if ((0 == dwRet) || (dwRet > MAX_PATH))
+               return error("Error getting current directory");
+
+       if ((Buffer[0] < 'A') || (Buffer[0] > 'Z'))
+               return error("Invalid drive letter '%c'", Buffer[0]);
+
+       szVolumeAccessPath[4] = Buffer[0];
+       hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE,
+               FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+       if (INVALID_HANDLE_VALUE == hVolWrite)
+               return error("Unable to open volume for writing, need admin access");
+
+       success = FlushFileBuffers(hVolWrite);
+       if (!success)
+               error("Unable to flush volume");
+
+       CloseHandle(hVolWrite);
+
+       return !success;
+}
+
+#define STATUS_SUCCESS                 (0x00000000L)
+#define STATUS_PRIVILEGE_NOT_HELD      (0xC0000061L)
+
+typedef enum _SYSTEM_INFORMATION_CLASS {
+       SystemMemoryListInformation = 80,
+} SYSTEM_INFORMATION_CLASS;
+
+typedef enum _SYSTEM_MEMORY_LIST_COMMAND {
+       MemoryCaptureAccessedBits,
+       MemoryCaptureAndResetAccessedBits,
+       MemoryEmptyWorkingSets,
+       MemoryFlushModifiedList,
+       MemoryPurgeStandbyList,
+       MemoryPurgeLowPriorityStandbyList,
+       MemoryCommandMax
+} SYSTEM_MEMORY_LIST_COMMAND;
+
+static BOOL GetPrivilege(HANDLE TokenHandle, LPCSTR lpName, int flags)
+{
+       BOOL bResult;
+       DWORD dwBufferLength;
+       LUID luid;
+       TOKEN_PRIVILEGES tpPreviousState;
+       TOKEN_PRIVILEGES tpNewState;
+
+       dwBufferLength = 16;
+       bResult = LookupPrivilegeValueA(0, lpName, &luid);
+       if (bResult) {
+               tpNewState.PrivilegeCount = 1;
+               tpNewState.Privileges[0].Luid = luid;
+               tpNewState.Privileges[0].Attributes = 0;
+               bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpNewState,
+                       (DWORD)((LPBYTE)&(tpNewState.Privileges[1]) - (LPBYTE)&tpNewState),
+                       &tpPreviousState, &dwBufferLength);
+               if (bResult) {
+                       tpPreviousState.PrivilegeCount = 1;
+                       tpPreviousState.Privileges[0].Luid = luid;
+                       tpPreviousState.Privileges[0].Attributes = flags != 0 ? 2 : 0;
+                       bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpPreviousState,
+                               dwBufferLength, 0, 0);
+               }
+       }
+       return bResult;
+}
+
+static int cmd_dropcaches(void)
+{
+       HANDLE hProcess = GetCurrentProcess();
+       HANDLE hToken;
+       HMODULE ntdll;
+       DWORD(WINAPI *NtSetSystemInformation)(INT, PVOID, ULONG);
+       SYSTEM_MEMORY_LIST_COMMAND command;
+       int status;
+
+       if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
+               return error("Can't open current process token");
+
+       if (!GetPrivilege(hToken, "SeProfileSingleProcessPrivilege", 1))
+               return error("Can't get SeProfileSingleProcessPrivilege");
+
+       CloseHandle(hToken);
+
+       ntdll = LoadLibrary("ntdll.dll");
+       if (!ntdll)
+               return error("Can't load ntdll.dll, wrong Windows version?");
+
+       NtSetSystemInformation =
+               (DWORD(WINAPI *)(INT, PVOID, ULONG))GetProcAddress(ntdll, "NtSetSystemInformation");
+       if (!NtSetSystemInformation)
+               return error("Can't get function addresses, wrong Windows version?");
+
+       command = MemoryPurgeStandbyList;
+       status = NtSetSystemInformation(
+               SystemMemoryListInformation,
+               &command,
+               sizeof(SYSTEM_MEMORY_LIST_COMMAND)
+       );
+       if (status == STATUS_PRIVILEGE_NOT_HELD)
+               error("Insufficient privileges to purge the standby list, need admin access");
+       else if (status != STATUS_SUCCESS)
+               error("Unable to execute the memory list command %d", status);
+
+       FreeLibrary(ntdll);
+
+       return status;
+}
+
+#elif defined(__linux__)
+
+static int cmd_sync(void)
+{
+       return system("sync");
+}
+
+static int cmd_dropcaches(void)
+{
+       return system("echo 3 | sudo tee /proc/sys/vm/drop_caches");
+}
+
+#elif defined(__APPLE__)
+
+static int cmd_sync(void)
+{
+       return system("sync");
+}
+
+static int cmd_dropcaches(void)
+{
+       return system("sudo purge");
+}
+
+#else
+
+static int cmd_sync(void)
+{
+       return 0;
+}
+
+static int cmd_dropcaches(void)
+{
+       return error("drop caches not implemented on this platform");
+}
+
+#endif
+
+int cmd_main(int argc, const char **argv)
+{
+       cmd_sync();
+       return cmd_dropcaches();
+}
diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c
new file mode 100644 (file)
index 0000000..ad45270
--- /dev/null
@@ -0,0 +1,21 @@
+#include "cache.h"
+
+int cmd_main(int ac, const char **av)
+{
+       struct index_state *istate = &the_index;
+       int i;
+
+       setup_git_directory();
+       if (do_read_index(istate, get_index_file(), 0) < 0)
+               die("unable to read index file");
+       if (!istate->fsmonitor_last_update) {
+               printf("no fsmonitor\n");
+               return 0;
+       }
+       printf("fsmonitor last update %"PRIuMAX"\n", (uintmax_t)istate->fsmonitor_last_update);
+
+       for (i = 0; i < istate->cache_nr; i++)
+               printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-");
+
+       return 0;
+}
diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c
new file mode 100644 (file)
index 0000000..90dc97a
--- /dev/null
@@ -0,0 +1,74 @@
+#include "cache.h"
+#include "object.h"
+#include "decorate.h"
+
+int cmd_main(int argc, const char **argv)
+{
+       struct decoration n;
+       struct object_id one_oid = { {1} };
+       struct object_id two_oid = { {2} };
+       struct object_id three_oid = { {3} };
+       struct object *one, *two, *three;
+
+       int decoration_a, decoration_b;
+
+       void *ret;
+
+       int i, objects_noticed = 0;
+
+       /*
+        * The struct must be zero-initialized.
+        */
+       memset(&n, 0, sizeof(n));
+
+       /*
+        * Add 2 objects, one with a non-NULL decoration and one with a NULL
+        * decoration.
+        */
+       one = lookup_unknown_object(one_oid.hash);
+       two = lookup_unknown_object(two_oid.hash);
+       ret = add_decoration(&n, one, &decoration_a);
+       if (ret)
+               die("BUG: when adding a brand-new object, NULL should be returned");
+       ret = add_decoration(&n, two, NULL);
+       if (ret)
+               die("BUG: when adding a brand-new object, NULL should be returned");
+
+       /*
+        * When re-adding an already existing object, the old decoration is
+        * returned.
+        */
+       ret = add_decoration(&n, one, NULL);
+       if (ret != &decoration_a)
+               die("BUG: when readding an already existing object, existing decoration should be returned");
+       ret = add_decoration(&n, two, &decoration_b);
+       if (ret)
+               die("BUG: when readding an already existing object, existing decoration should be returned");
+
+       /*
+        * Lookup returns the added declarations, or NULL if the object was
+        * never added.
+        */
+       ret = lookup_decoration(&n, one);
+       if (ret)
+               die("BUG: lookup should return added declaration");
+       ret = lookup_decoration(&n, two);
+       if (ret != &decoration_b)
+               die("BUG: lookup should return added declaration");
+       three = lookup_unknown_object(three_oid.hash);
+       ret = lookup_decoration(&n, three);
+       if (ret)
+               die("BUG: lookup for unknown object should return NULL");
+
+       /*
+        * The user can also loop through all entries.
+        */
+       for (i = 0; i < n.size; i++) {
+               if (n.entries[i].base)
+                       objects_noticed++;
+       }
+       if (objects_noticed != 2)
+               die("BUG: should have 2 objects");
+
+       return 0;
+}
index 6ec2670044d205533594947547fce8255de5aa4c..7120634b04733bb8abe1f0622f0e1e9c8280b643 100644 (file)
@@ -72,12 +72,12 @@ static int cmd_pack_refs(struct ref_store *refs, const char **argv)
 static int cmd_peel_ref(struct ref_store *refs, const char **argv)
 {
        const char *refname = notnull(*argv++, "refname");
-       unsigned char sha1[20];
+       struct object_id oid;
        int ret;
 
-       ret = refs_peel_ref(refs, refname, sha1);
+       ret = refs_peel_ref(refs, refname, &oid);
        if (!ret)
-               puts(sha1_to_hex(sha1));
+               puts(oid_to_hex(&oid));
        return ret;
 }
 
@@ -127,15 +127,15 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
 
 static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        const char *refname = notnull(*argv++, "refname");
        int resolve_flags = arg_flags(*argv++, "resolve-flags");
        int flags;
        const char *ref;
 
        ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
-                                     sha1, &flags);
-       printf("%s %s 0x%x\n", sha1_to_hex(sha1), ref ? ref : "(null)", flags);
+                                     &oid, &flags);
+       printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags);
        return ref ? 0 : 1;
 }
 
@@ -218,12 +218,12 @@ static int cmd_delete_ref(struct ref_store *refs, const char **argv)
        const char *refname = notnull(*argv++, "refname");
        const char *sha1_buf = notnull(*argv++, "old-sha1");
        unsigned int flags = arg_flags(*argv++, "flags");
-       unsigned char old_sha1[20];
+       struct object_id old_oid;
 
-       if (get_sha1_hex(sha1_buf, old_sha1))
+       if (get_oid_hex(sha1_buf, &old_oid))
                die("not sha-1");
 
-       return refs_delete_ref(refs, msg, refname, old_sha1, flags);
+       return refs_delete_ref(refs, msg, refname, &old_oid, flags);
 }
 
 static int cmd_update_ref(struct ref_store *refs, const char **argv)
@@ -233,15 +233,15 @@ static int cmd_update_ref(struct ref_store *refs, const char **argv)
        const char *new_sha1_buf = notnull(*argv++, "old-sha1");
        const char *old_sha1_buf = notnull(*argv++, "old-sha1");
        unsigned int flags = arg_flags(*argv++, "flags");
-       unsigned char old_sha1[20];
-       unsigned char new_sha1[20];
+       struct object_id old_oid;
+       struct object_id new_oid;
 
-       if (get_sha1_hex(old_sha1_buf, old_sha1) ||
-           get_sha1_hex(new_sha1_buf, new_sha1))
+       if (get_oid_hex(old_sha1_buf, &old_oid) ||
+           get_oid_hex(new_sha1_buf, &new_oid))
                die("not sha-1");
 
        return refs_update_ref(refs, msg, refname,
-                              new_sha1, old_sha1,
+                              &new_oid, &old_oid,
                               flags, UPDATE_REFS_DIE_ON_ERR);
 }
 
diff --git a/t/interop/i5700-protocol-transition.sh b/t/interop/i5700-protocol-transition.sh
new file mode 100755 (executable)
index 0000000..97e8e58
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+VERSION_A=.
+VERSION_B=v2.0.0
+
+: ${LIB_GIT_DAEMON_PORT:=5700}
+LIB_GIT_DAEMON_COMMAND='git.b daemon'
+
+test_description='clone and fetch by client who is trying to use a new protocol'
+. ./interop-lib.sh
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+
+start_git_daemon --export-all
+
+repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo
+
+test_expect_success "create repo served by $VERSION_B" '
+       git.b init "$repo" &&
+       git.b -C "$repo" commit --allow-empty -m one
+'
+
+test_expect_success "git:// clone with $VERSION_A and protocol v1" '
+       GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone "$GIT_DAEMON_URL/repo" child 2>log &&
+       git.a -C child log -1 --format=%s >actual &&
+       git.b -C "$repo" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+       grep "version=1" log
+'
+
+test_expect_success "git:// fetch with $VERSION_A and protocol v1" '
+       git.b -C "$repo" commit --allow-empty -m two &&
+       git.b -C "$repo" log -1 --format=%s >expect &&
+
+       GIT_TRACE_PACKET=1 git.a -C child -c protocol.version=1 fetch 2>log &&
+       git.a -C child log -1 --format=%s FETCH_HEAD >actual &&
+
+       test_cmp expect actual &&
+       grep "version=1" log &&
+       ! grep "version 1" log
+'
+
+stop_git_daemon
+
+test_expect_success "create repo served by $VERSION_B" '
+       git.b init parent &&
+       git.b -C parent commit --allow-empty -m one
+'
+
+test_expect_success "file:// clone with $VERSION_A and protocol v1" '
+       GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone --upload-pack="git.b upload-pack" parent child2 2>log &&
+       git.a -C child2 log -1 --format=%s >actual &&
+       git.b -C parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+       ! grep "version 1" log
+'
+
+test_expect_success "file:// fetch with $VERSION_A and protocol v1" '
+       git.b -C parent commit --allow-empty -m two &&
+       git.b -C parent log -1 --format=%s >expect &&
+
+       GIT_TRACE_PACKET=1 git.a -C child2 -c protocol.version=1 fetch --upload-pack="git.b upload-pack" 2>log &&
+       git.a -C child2 log -1 --format=%s FETCH_HEAD >actual &&
+
+       test_cmp expect actual &&
+       ! grep "version 1" log
+'
+
+test_done
index 688313ed5cc40e22b2412c57f98df06e4f3ac0ea..4c1f81f1678d83ab8b9ee7704d400bdd47ea70f7 100644 (file)
@@ -17,8 +17,8 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree
 svn >/dev/null 2>&1
 if test $? -ne 1
 then
-    skip_all='skipping git svn tests, svn not found'
-    test_done
+       skip_all='skipping git svn tests, svn not found'
+       test_done
 fi
 
 svnrepo=$PWD/svnrepo
@@ -110,18 +110,20 @@ EOF
 }
 
 require_svnserve () {
-    if test -z "$SVNSERVE_PORT"
-    then
-       skip_all='skipping svnserve test. (set $SVNSERVE_PORT to enable)'
-        test_done
-    fi
+       test_tristate GIT_TEST_SVNSERVE
+       if ! test "$GIT_TEST_SVNSERVE" = true
+       then
+               skip_all='skipping svnserve test. (set $GIT_TEST_SVNSERVE to enable)'
+               test_done
+       fi
 }
 
 start_svnserve () {
-    svnserve --listen-port $SVNSERVE_PORT \
-             --root "$rawsvnrepo" \
-             --listen-once \
-             --listen-host 127.0.0.1 &
+       SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}}
+       svnserve --listen-port $SVNSERVE_PORT \
+                --root "$rawsvnrepo" \
+                --listen-once \
+                --listen-host 127.0.0.1 &
 }
 
 prepare_a_utf8_locale () {
index 0642ae7e6ef0fbbd9e7eacdebd061d258c8b4222..df19436314b7c6d16833044c5c53ba90eae62225 100644 (file)
@@ -67,6 +67,9 @@ LockFile accept.lock
 <IfModule !mod_unixd.c>
        LoadModule unixd_module modules/mod_unixd.so
 </IfModule>
+<IfModule !mod_setenvif.c>
+       LoadModule setenvif_module modules/mod_setenvif.so
+</IfModule>
 </IfVersion>
 
 PassEnv GIT_VALGRIND
@@ -76,6 +79,10 @@ PassEnv ASAN_OPTIONS
 PassEnv GIT_TRACE
 PassEnv GIT_CONFIG_NOSYSTEM
 
+<IfVersion >= 2.4>
+       SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
+</IfVersion>
+
 Alias /dumb/ www/
 Alias /auth/dumb/ www/auth/dumb/
 
index 2d26f86800906ab1783edf10e7196adadd5f4af7..38dadd2c2902f3621d54168d32a54d7863497968 100755 (executable)
@@ -306,9 +306,9 @@ test_submodule_content () {
 # to protect the history!
 #
 
-# Test that submodule contents are currently not updated when switching
-# between commits that change a submodule.
-test_submodule_switch () {
+# Internal function; use test_submodule_switch() or
+# test_submodule_forced_switch() instead.
+test_submodule_switch_common() {
        command="$1"
        ######################### Appearing submodule #########################
        # Switching to a commit letting a submodule appear creates empty dir ...
@@ -332,7 +332,7 @@ test_submodule_switch () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... and doesn't care if it already exists ...
+       # ... and doesn't care if it already exists.
        test_expect_$RESULT "$command: added submodule leaves existing empty directory alone" '
                prolog &&
                reset_work_tree_to no_submodule &&
@@ -347,19 +347,6 @@ test_submodule_switch () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... unless there is an untracked file in its place.
-       test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" '
-               prolog &&
-               reset_work_tree_to no_submodule &&
-               (
-                       cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       >sub1 &&
-                       test_must_fail $command add_sub1 &&
-                       test_superproject_content origin/no_submodule &&
-                       test_must_be_empty sub1
-               )
-       '
        # Replacing a tracked file with a submodule produces an empty
        # directory ...
        test_expect_$RESULT "$command: replace tracked file with submodule creates empty directory" '
@@ -441,6 +428,11 @@ test_submodule_switch () {
                # submodule files with the newly checked out ones in the
                # directory of the same name while it shouldn't.
                RESULT="failure"
+       elif test "$KNOWN_FAILURE_FORCED_SWITCH_TESTS" = 1
+       then
+               # All existing tests that use test_submodule_forced_switch()
+               # require this.
+               RESULT="failure"
        else
                RESULT="success"
        fi
@@ -522,7 +514,6 @@ test_submodule_switch () {
                        test_submodule_content sub1 origin/modify_sub1
                )
        '
-
        # Updating a submodule to an invalid sha1 doesn't update the
        # submodule's work tree, subsequent update will fail
        test_expect_$RESULT "$command: modified submodule does not update submodule work tree to invalid commit" '
@@ -555,235 +546,269 @@ test_submodule_switch () {
        '
 }
 
-# Test that submodule contents are currently not updated when switching
-# between commits that change a submodule, but throwing away local changes in
+# Declares and invokes several tests that, in various situations, checks that
+# the provided transition function:
+#  - succeeds in updating the worktree and index of a superproject to a target
+#    commit, or fails atomically (depending on the test situation)
+#  - if succeeds, the contents of submodule directories are unchanged
+#  - if succeeds, once "git submodule update" is invoked, the contents of
+#    submodule directories are updated
+#
+# If the command under test is known to not work with submodules in certain
+# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
+# below to 1.
+#
+# Use as follows:
+#
+# my_func () {
+#   target=$1
+#   # Do something here that updates the worktree and index to match target,
+#   # but not any submodule directories.
+# }
+# test_submodule_switch "my_func"
+test_submodule_switch () {
+       command="$1"
+       test_submodule_switch_common "$command"
+
+       # An empty directory does not prevent the creation of a submodule of
+       # the same name, but a file does.
+       test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" '
+               prolog &&
+               reset_work_tree_to no_submodule &&
+               (
+                       cd submodule_update &&
+                       git branch -t add_sub1 origin/add_sub1 &&
+                       >sub1 &&
+                       test_must_fail $command add_sub1 &&
+                       test_superproject_content origin/no_submodule &&
+                       test_must_be_empty sub1
+               )
+       '
+}
+
+# Same as test_submodule_switch(), except that throwing away local changes in
 # the superproject is allowed.
 test_submodule_forced_switch () {
        command="$1"
-       ######################### Appearing submodule #########################
-       # Switching to a commit letting a submodule appear creates empty dir ...
-       test_expect_success "$command: added submodule creates empty directory" '
+       KNOWN_FAILURE_FORCED_SWITCH_TESTS=1
+       test_submodule_switch_common "$command"
+
+       # When forced, a file in the superproject does not prevent creating a
+       # submodule of the same name.
+       test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
                prolog &&
                reset_work_tree_to no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
+                       >sub1 &&
                        $command add_sub1 &&
                        test_superproject_content origin/add_sub1 &&
-                       test_dir_is_empty sub1 &&
-                       git submodule update --init --recursive &&
-                       test_submodule_content sub1 origin/add_sub1
+                       test_dir_is_empty sub1
                )
        '
-       # ... and doesn't care if it already exists ...
-       test_expect_success "$command: added submodule leaves existing empty directory alone" '
+}
+
+# Test that submodule contents are correctly updated when switching
+# between commits that change a submodule.
+# Test that the following transitions are correctly handled:
+# (These tests are also above in the case where we expect no change
+#  in the submodule)
+# - Updated submodule
+# - New submodule
+# - Removed submodule
+# - Directory containing tracked files replaced by submodule
+# - Submodule replaced by tracked files in directory
+# - Submodule replaced by tracked file with the same name
+# - tracked file replaced by submodule
+#
+# New test cases
+# - Removing a submodule with a git directory absorbs the submodules
+#   git directory first into the superproject.
+
+# Internal function; use test_submodule_switch_recursing_with_args() or
+# test_submodule_forced_switch_recursing_with_args() instead.
+test_submodule_recursing_with_args_common() {
+       command="$1"
+
+       ######################### Appearing submodule #########################
+       # Switching to a commit letting a submodule appear checks it out ...
+       test_expect_success "$command: added submodule is checked out" '
                prolog &&
-               reset_work_tree_to no_submodule &&
+               reset_work_tree_to_interested no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
-                       mkdir sub1 &&
                        $command add_sub1 &&
                        test_superproject_content origin/add_sub1 &&
-                       test_dir_is_empty sub1 &&
-                       git submodule update --init --recursive &&
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... unless there is an untracked file in its place.
-       test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
+       # ... ignoring an empty existing directory.
+       test_expect_success "$command: added submodule is checked out in empty dir" '
                prolog &&
-               reset_work_tree_to no_submodule &&
+               reset_work_tree_to_interested no_submodule &&
                (
                        cd submodule_update &&
+                       mkdir sub1 &&
                        git branch -t add_sub1 origin/add_sub1 &&
-                       >sub1 &&
                        $command add_sub1 &&
                        test_superproject_content origin/add_sub1 &&
-                       test_dir_is_empty sub1
+                       test_submodule_content sub1 origin/add_sub1
                )
        '
-       # Replacing a tracked file with a submodule produces an empty
-       # directory ...
-       test_expect_success "$command: replace tracked file with submodule creates empty directory" '
+       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
                prolog &&
-               reset_work_tree_to replace_sub1_with_file &&
+               reset_work_tree_to_interested add_sub1 &&
+               (
+                       cd submodule_update &&
+                       git -C sub1 checkout -b keep_branch &&
+                       git -C sub1 rev-parse HEAD >expect &&
+                       git branch -t check-keep origin/modify_sub1 &&
+                       $command check-keep &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1 &&
+                       git -C sub1 rev-parse keep_branch >actual &&
+                       test_cmp expect actual &&
+                       test_must_fail git -C sub1 symbolic-ref HEAD
+               )
+       '
+
+       # Replacing a tracked file with a submodule produces a checked out submodule
+       test_expect_success "$command: replace tracked file with submodule checks out submodule" '
+               prolog &&
+               reset_work_tree_to_interested replace_sub1_with_file &&
                (
                        cd submodule_update &&
                        git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
                        $command replace_file_with_sub1 &&
                        test_superproject_content origin/replace_file_with_sub1 &&
-                       test_dir_is_empty sub1 &&
-                       git submodule update --init --recursive &&
                        test_submodule_content sub1 origin/replace_file_with_sub1
                )
        '
-       # ... as does removing a directory with tracked files with a
-       # submodule.
+       # ... as does removing a directory with tracked files with a submodule.
        test_expect_success "$command: replace directory with submodule" '
                prolog &&
-               reset_work_tree_to replace_sub1_with_directory &&
+               reset_work_tree_to_interested replace_sub1_with_directory &&
                (
                        cd submodule_update &&
                        git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
                        $command replace_directory_with_sub1 &&
                        test_superproject_content origin/replace_directory_with_sub1 &&
-                       test_dir_is_empty sub1 &&
-                       git submodule update --init --recursive &&
                        test_submodule_content sub1 origin/replace_directory_with_sub1
                )
        '
 
        ######################## Disappearing submodule #######################
-       # Removing a submodule doesn't remove its work tree ...
-       test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
+       # Removing a submodule removes its work tree ...
+       test_expect_success "$command: removed submodule removes submodules working tree" '
                prolog &&
-               reset_work_tree_to add_sub1 &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
                        git branch -t remove_sub1 origin/remove_sub1 &&
                        $command remove_sub1 &&
                        test_superproject_content origin/remove_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       ! test -e sub1
                )
        '
-       # ... especially when it contains a .git directory.
-       test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
+       # ... absorbing a .git directory along the way.
+       test_expect_success "$command: removed submodule absorbs submodules .git directory" '
                prolog &&
-               reset_work_tree_to add_sub1 &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
                        git branch -t remove_sub1 origin/remove_sub1 &&
                        replace_gitfile_with_git_dir sub1 &&
+                       rm -rf .git/modules &&
                        $command remove_sub1 &&
                        test_superproject_content origin/remove_sub1 &&
-                       test_git_directory_is_unchanged sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-       # Replacing a submodule with files in a directory must fail as the
-       # submodule work tree isn't removed ...
-       test_expect_failure "$command: replace submodule with a directory must fail" '
-               prolog &&
-               reset_work_tree_to add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-                       test_must_fail $command replace_sub1_with_directory &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-       # ... especially when it contains a .git directory.
-       test_expect_failure "$command: replace submodule containing a .git directory with a directory must fail" '
-               prolog &&
-               reset_work_tree_to add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       test_must_fail $command replace_sub1_with_directory &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_git_directory_is_unchanged sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       ! test -e sub1 &&
+                       test_git_directory_exists sub1
                )
        '
-       # Replacing it with a file must fail as it could throw away any local
-       # work tree changes ...
-       test_expect_failure "$command: replace submodule with a file must fail" '
+
+       # Replacing it with a file ...
+       test_expect_success "$command: replace submodule with a file" '
                prolog &&
-               reset_work_tree_to add_sub1 &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
                        git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       test_must_fail $command replace_sub1_with_file &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       $command replace_sub1_with_file &&
+                       test_superproject_content origin/replace_sub1_with_file &&
+                       test -f sub1
                )
        '
-       # ... or even destroy unpushed parts of submodule history if that
-       # still uses a .git directory.
-       test_expect_failure "$command: replace submodule containing a .git directory with a file must fail" '
+       RESULTDS=success
+       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
+       then
+               RESULTDS=failure
+       fi
+       # ... must check its local work tree for untracked files
+       test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" '
                prolog &&
-               reset_work_tree_to add_sub1 &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
                        git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       replace_gitfile_with_git_dir sub1 &&
+                       : >sub1/untrackedfile &&
                        test_must_fail $command replace_sub1_with_file &&
                        test_superproject_content origin/add_sub1 &&
-                       test_git_directory_is_unchanged sub1 &&
                        test_submodule_content sub1 origin/add_sub1
+                       test -f sub1/untracked_file
                )
        '
 
        ########################## Modified submodule #########################
-       # Updating a submodule sha1 doesn't update the submodule's work tree
-       test_expect_success "$command: modified submodule does not update submodule work tree" '
+       # Updating a submodule sha1 updates the submodule's work tree
+       test_expect_success "$command: modified submodule updates submodule work tree" '
                prolog &&
-               reset_work_tree_to add_sub1 &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
                        git branch -t modify_sub1 origin/modify_sub1 &&
                        $command modify_sub1 &&
                        test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1 &&
-                       git submodule update &&
                        test_submodule_content sub1 origin/modify_sub1
                )
        '
        # Updating a submodule to an invalid sha1 doesn't update the
-       # submodule's work tree, subsequent update will fail
-       test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
+       # superproject nor the submodule's work tree.
+       test_expect_success "$command: updating to a missing submodule commit fails" '
                prolog &&
-               reset_work_tree_to add_sub1 &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
                        git branch -t invalid_sub1 origin/invalid_sub1 &&
-                       $command invalid_sub1 &&
-                       test_superproject_content origin/invalid_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1 &&
-                       test_must_fail git submodule update &&
+                       test_must_fail $command invalid_sub1 &&
+                       test_superproject_content origin/add_sub1 &&
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # Updating a submodule from an invalid sha1 doesn't update the
-       # submodule's work tree, subsequent update will succeed
-       test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" '
-               prolog &&
-               reset_work_tree_to invalid_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t valid_sub1 origin/valid_sub1 &&
-                       $command valid_sub1 &&
-                       test_superproject_content origin/valid_sub1 &&
-                       test_dir_is_empty sub1 &&
-                       git submodule update --init --recursive &&
-                       test_submodule_content sub1 origin/valid_sub1
-               )
-       '
 }
 
-# Test that submodule contents are correctly updated when switching
-# between commits that change a submodule.
-# Test that the following transitions are correctly handled:
-# (These tests are also above in the case where we expect no change
-#  in the submodule)
-# - Updated submodule
-# - New submodule
-# - Removed submodule
-# - Directory containing tracked files replaced by submodule
-# - Submodule replaced by tracked files in directory
-# - Submodule replaced by tracked file with the same name
-# - tracked file replaced by submodule
+# Declares and invokes several tests that, in various situations, checks that
+# the provided Git command, when invoked with --recurse-submodules:
+#  - succeeds in updating the worktree and index of a superproject to a target
+#    commit, or fails atomically (depending on the test situation)
+#  - if succeeds, the contents of submodule directories are updated
 #
-# New test cases
-# - Removing a submodule with a git directory absorbs the submodules
-#   git directory first into the superproject.
-
+# Specify the Git command so that "git $GIT_COMMAND --recurse-submodules"
+# works.
+#
+# If the command under test is known to not work with submodules in certain
+# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
+# below to 1.
+#
+# Use as follows:
+#
+# test_submodule_switch_recursing_with_args "$GIT_COMMAND"
 test_submodule_switch_recursing_with_args () {
        cmd_args="$1"
        command="git $cmd_args --recurse-submodules"
+       test_submodule_recursing_with_args_common "$command"
+
        RESULTDS=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
@@ -794,33 +819,8 @@ test_submodule_switch_recursing_with_args () {
        then
                RESULTOI=failure
        fi
-       ######################### Appearing submodule #########################
-       # Switching to a commit letting a submodule appear checks it out ...
-       test_expect_success "$command: added submodule is checked out" '
-               prolog &&
-               reset_work_tree_to_interested no_submodule &&
-               (
-                       cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-       # ... ignoring an empty existing directory ...
-       test_expect_success "$command: added submodule is checked out in empty dir" '
-               prolog &&
-               reset_work_tree_to_interested no_submodule &&
-               (
-                       cd submodule_update &&
-                       mkdir sub1 &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-       # ... unless there is an untracked file in its place.
+       # Switching to a commit letting a submodule appear cannot override an
+       # untracked file.
        test_expect_success "$command: added submodule doesn't remove untracked file with same name" '
                prolog &&
                reset_work_tree_to_interested no_submodule &&
@@ -848,59 +848,7 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # Replacing a tracked file with a submodule produces a checked out submodule
-       test_expect_success "$command: replace tracked file with submodule checks out submodule" '
-               prolog &&
-               reset_work_tree_to_interested replace_sub1_with_file &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
-                       $command replace_file_with_sub1 &&
-                       test_superproject_content origin/replace_file_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_file_with_sub1
-               )
-       '
-       # ... as does removing a directory with tracked files with a submodule.
-       test_expect_success "$command: replace directory with submodule" '
-               prolog &&
-               reset_work_tree_to_interested replace_sub1_with_directory &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
-                       $command replace_directory_with_sub1 &&
-                       test_superproject_content origin/replace_directory_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_directory_with_sub1
-               )
-       '
 
-       ######################## Disappearing submodule #######################
-       # Removing a submodule removes its work tree ...
-       test_expect_success "$command: removed submodule removes submodules working tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       ! test -e sub1
-               )
-       '
-       # ... absorbing a .git directory along the way.
-       test_expect_success "$command: removed submodule absorbs submodules .git directory" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       rm -rf .git/modules &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       ! test -e sub1 &&
-                       test_git_directory_exists sub1
-               )
-       '
        # Replacing a submodule with files in a directory must succeeds
        # when the submodule is clean
        test_expect_$RESULTDS "$command: replace submodule with a directory" '
@@ -929,33 +877,6 @@ test_submodule_switch_recursing_with_args () {
                )
        '
 
-       # Replacing it with a file ...
-       test_expect_success "$command: replace submodule with a file" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       $command replace_sub1_with_file &&
-                       test_superproject_content origin/replace_sub1_with_file &&
-                       test -f sub1
-               )
-       '
-
-       # ... must check its local work tree for untracked files
-       test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       : >sub1/untrackedfile &&
-                       test_must_fail $command replace_sub1_with_file &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-
        # ... and ignored files are ignored
        test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" '
                test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" &&
@@ -971,20 +892,6 @@ test_submodule_switch_recursing_with_args () {
                )
        '
 
-       ########################## Modified submodule #########################
-       # Updating a submodule sha1 updates the submodule's work tree
-       test_expect_success "$command: modified submodule updates submodule work tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       $command modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1
-               )
-       '
-
        test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
                prolog &&
                reset_work_tree_to_interested add_sub1 &&
@@ -997,20 +904,6 @@ test_submodule_switch_recursing_with_args () {
                )
        '
 
-       # Updating a submodule to an invalid sha1 doesn't update the
-       # superproject nor the submodule's work tree.
-       test_expect_success "$command: updating to a missing submodule commit fails" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t invalid_sub1 origin/invalid_sub1 &&
-                       test_must_fail $command invalid_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-
        # recursing deeper than one level doesn't work yet.
        test_expect_success "$command: modified submodule updates submodule recursively" '
                prolog &&
@@ -1026,44 +919,20 @@ test_submodule_switch_recursing_with_args () {
        '
 }
 
-# Test that submodule contents are updated when switching between commits
-# that change a submodule, but throwing away local changes in
-# the superproject as well as the submodule is allowed.
+# Same as test_submodule_switch_recursing_with_args(), except that throwing
+# away local changes in the superproject is allowed.
 test_submodule_forced_switch_recursing_with_args () {
        cmd_args="$1"
        command="git $cmd_args --recurse-submodules"
+       test_submodule_recursing_with_args_common "$command"
+
        RESULT=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
                RESULT=failure
        fi
-       ######################### Appearing submodule #########################
-       # Switching to a commit letting a submodule appear creates empty dir ...
-       test_expect_success "$command: added submodule is checked out" '
-               prolog &&
-               reset_work_tree_to_interested no_submodule &&
-               (
-                       cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-       # ... and doesn't care if it already exists ...
-       test_expect_success "$command: added submodule ignores empty directory" '
-               prolog &&
-               reset_work_tree_to_interested no_submodule &&
-               (
-                       cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       mkdir sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
-       # ... not caring about an untracked file either
+       # Switching to a commit letting a submodule appear does not care about
+       # an untracked file.
        test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
                prolog &&
                reset_work_tree_to_interested no_submodule &&
@@ -1076,60 +945,7 @@ test_submodule_forced_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # Replacing a tracked file with a submodule checks out the submodule
-       test_expect_success "$command: replace tracked file with submodule populates the submodule" '
-               prolog &&
-               reset_work_tree_to_interested replace_sub1_with_file &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
-                       $command replace_file_with_sub1 &&
-                       test_superproject_content origin/replace_file_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_file_with_sub1
-               )
-       '
-       # ... as does removing a directory with tracked files with a
-       # submodule.
-       test_expect_success "$command: replace directory with submodule" '
-               prolog &&
-               reset_work_tree_to_interested replace_sub1_with_directory &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
-                       $command replace_directory_with_sub1 &&
-                       test_superproject_content origin/replace_directory_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_directory_with_sub1
-               )
-       '
 
-       ######################## Disappearing submodule #######################
-       # Removing a submodule doesn't remove its work tree ...
-       test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       ! test -e sub1
-               )
-       '
-       # ... especially when it contains a .git directory.
-       test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       rm -rf .git/modules/sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       test_git_directory_exists sub1 &&
-                       ! test -e sub1
-               )
-       '
        # Replacing a submodule with files in a directory ...
        test_expect_success "$command: replace submodule with a directory" '
                prolog &&
@@ -1156,17 +972,6 @@ test_submodule_forced_switch_recursing_with_args () {
                        test_git_directory_exists sub1
                )
        '
-       # Replacing it with a file
-       test_expect_success "$command: replace submodule with a file" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       $command replace_sub1_with_file &&
-                       test_superproject_content origin/replace_sub1_with_file
-               )
-       '
 
        # ... even if the submodule contains ignored files
        test_expect_success "$command: replace submodule with a file ignoring ignored files" '
@@ -1181,46 +986,6 @@ test_submodule_forced_switch_recursing_with_args () {
                )
        '
 
-       # ... but stops for untracked files that would be lost
-       test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       : >sub1/untracked_file &&
-                       test_must_fail $command replace_sub1_with_file &&
-                       test_superproject_content origin/add_sub1 &&
-                       test -f sub1/untracked_file
-               )
-       '
-
-       ########################## Modified submodule #########################
-       # Updating a submodule sha1 updates the submodule's work tree
-       test_expect_success "$command: modified submodule updates submodule work tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       $command modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1
-               )
-       '
-       # Updating a submodule to an invalid sha1 doesn't update the
-       # submodule's work tree, subsequent update will fail
-       test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t invalid_sub1 origin/invalid_sub1 &&
-                       test_must_fail $command invalid_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
        # Updating a submodule from an invalid sha1 updates
        test_expect_success "$command: modified submodule does update submodule work tree from invalid commit" '
                prolog &&
index 1dbc85b21407dcfc83d9dc6a56e8e0f21c748c0c..e40120848837c1ef78cf75d1724c69226823db15 100755 (executable)
@@ -69,12 +69,17 @@ sub format_times {
        @tests = glob "p????-*.sh";
 }
 
+my $resultsdir = "test-results";
+if ($ENV{GIT_PERF_SUBSECTION} ne "") {
+       $resultsdir .= "/" . $ENV{GIT_PERF_SUBSECTION};
+}
+
 my @subtests;
 my %shorttests;
 for my $t (@tests) {
        $t =~ s{(?:.*/)?(p(\d+)-[^/]+)\.sh$}{$1} or die "bad test name: $t";
        my $n = $2;
-       my $fname = "test-results/$t.subtests";
+       my $fname = "$resultsdir/$t.subtests";
        open my $fp, "<", $fname or die "cannot open $fname: $!";
        for (<$fp>) {
                chomp;
@@ -98,7 +103,7 @@ sub read_descr {
 my %descrs;
 my $descrlen = 4; # "Test"
 for my $t (@subtests) {
-       $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr");
+       $descrs{$t} = $shorttests{$t}.": ".read_descr("$resultsdir/$t.descr");
        $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen;
 }
 
@@ -138,7 +143,7 @@ sub have_slash {
        my $firstr;
        for my $i (0..$#dirs) {
                my $d = $dirs[$i];
-               $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")];
+               $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")];
                my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
                my $w = length format_times($r,$u,$s,$firstr);
                $colwidth[$i] = $w if $w > $colwidth[$i];
diff --git a/t/perf/lib-pack.sh b/t/perf/lib-pack.sh
new file mode 100644 (file)
index 0000000..d3865db
--- /dev/null
@@ -0,0 +1,25 @@
+# Helpers for dealing with large numbers of packs.
+
+# create $1 nonsense packs, each with a single blob
+create_packs () {
+       perl -le '
+               my ($n) = @ARGV;
+               for (1..$n) {
+                       print "blob";
+                       print "data <<EOF";
+                       print "$_";
+                       print "EOF";
+                       print "checkpoint"
+               }
+       ' "$@" |
+       git fast-import
+}
+
+# create a large number of packs, disabling any gc which might
+# cause us to repack them
+setup_many_packs () {
+       git config gc.auto 0 &&
+       git config gc.autopacklimit 0 &&
+       git config fastimport.unpacklimit 0 &&
+       create_packs 500
+}
index b7ff68d4fa3de5559af672795191a0b81deaa4f8..392bcc0e51f8ad907916851016157b12ea134835 100755 (executable)
@@ -31,4 +31,12 @@ test_perf 'git log -L (renames on)' '
        git log -M -L 1:"$file" >/dev/null
 '
 
+test_perf 'git log --oneline --raw --parents' '
+       git log --oneline --raw --parents >/dev/null
+'
+
+test_perf 'git log --oneline --raw --parents -1000' '
+       git log --oneline --raw --parents -1000 >/dev/null
+'
+
 test_done
index a5dc39f86af65d6f6b200b95c4f3c66d5f482ec1..d0e0e019ea364cb1cef3863d256aa5596e01eb56 100755 (executable)
@@ -20,6 +20,7 @@ start to show a noticeable performance problem on my machine, but without
 taking too long to set up and run the tests.
 '
 . ./perf-lib.sh
+. "$TEST_DIRECTORY/perf/lib-pack.sh"
 
 # make a long nonsense history on branch $1, consisting of $2 commits, each
 # with a unique file pointing to the blob at $2.
@@ -44,26 +45,6 @@ create_tags () {
        git update-ref --stdin
 }
 
-# create $1 nonsense packs, each with a single blob
-create_packs () {
-       perl -le '
-               my ($n) = @ARGV;
-               for (1..$n) {
-                       print "blob";
-                       print "data <<EOF";
-                       print "$_";
-                       print "EOF";
-               }
-       ' "$@" |
-       git fast-import &&
-
-       git cat-file --batch-all-objects --batch-check='%(objectname)' |
-       while read sha1
-       do
-               echo $sha1 | git pack-objects .git/objects/pack/pack
-       done
-}
-
 test_expect_success 'create parent and child' '
        git init parent &&
        git -C parent commit --allow-empty -m base &&
@@ -84,9 +65,7 @@ test_expect_success 'populate parent tags' '
 test_expect_success 'create child packs' '
        (
                cd child &&
-               git config gc.auto 0 &&
-               git config gc.autopacklimit 0 &&
-               create_packs 500
+               setup_many_packs
        )
 '
 
diff --git a/t/perf/p5551-fetch-rescan.sh b/t/perf/p5551-fetch-rescan.sh
new file mode 100755 (executable)
index 0000000..b99dc23
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='fetch performance with many packs
+
+It is common for fetch to consider objects that we might not have, and it is an
+easy mistake for the code to use a function like `parse_object` that might
+give the correct _answer_ on such an object, but do so slowly (due to
+re-scanning the pack directory for lookup failures).
+
+The resulting performance drop can be hard to notice in a real repository, but
+becomes quite large in a repository with a large number of packs. So this
+test creates a more pathological case, since any mistakes would produce a more
+noticeable slowdown.
+'
+. ./perf-lib.sh
+. "$TEST_DIRECTORY"/perf/lib-pack.sh
+
+test_expect_success 'create parent and child' '
+       git init parent &&
+       git clone parent child
+'
+
+
+test_expect_success 'create refs in the parent' '
+       (
+               cd parent &&
+               git commit --allow-empty -m foo &&
+               head=$(git rev-parse HEAD) &&
+               test_seq 1000 |
+               sed "s,.*,update refs/heads/& $head," |
+               $MODERN_GIT update-ref --stdin
+       )
+'
+
+test_expect_success 'create many packs in the child' '
+       (
+               cd child &&
+               setup_many_packs
+       )
+'
+
+test_perf 'fetch' '
+       # start at the same state for each iteration
+       obj=$($MODERN_GIT -C parent rev-parse HEAD) &&
+       (
+               cd child &&
+               $MODERN_GIT for-each-ref --format="delete %(refname)" refs/remotes |
+               $MODERN_GIT update-ref --stdin &&
+               rm -vf .git/objects/$(echo $obj | sed "s|^..|&/|") &&
+
+               git fetch
+       )
+'
+
+test_done
diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh
new file mode 100755 (executable)
index 0000000..65e145c
--- /dev/null
@@ -0,0 +1,183 @@
+#!/bin/sh
+
+test_description="Test core.fsmonitor"
+
+. ./perf-lib.sh
+
+#
+# Performance test for the fsmonitor feature which enables git to talk to a
+# file system change monitor and avoid having to scan the working directory
+# for new or modified files.
+#
+# By default, the performance test will utilize the Watchman file system
+# monitor if it is installed.  If Watchman is not installed, it will use a
+# dummy integration script that does not report any new or modified files.
+# The dummy script has very little overhead which provides optimistic results.
+#
+# The performance test will also use the untracked cache feature if it is
+# available as fsmonitor uses it to speed up scanning for untracked files.
+#
+# There are 3 environment variables that can be used to alter the default
+# behavior of the performance test:
+#
+# GIT_PERF_7519_UNTRACKED_CACHE: used to configure core.untrackedCache
+# GIT_PERF_7519_SPLIT_INDEX: used to configure core.splitIndex
+# GIT_PERF_7519_FSMONITOR: used to configure core.fsMonitor
+#
+# The big win for using fsmonitor is the elimination of the need to scan the
+# working directory looking for changed and untracked files. If the file
+# information is all cached in RAM, the benefits are reduced.
+#
+# GIT_PERF_7519_DROP_CACHE: if set, the OS caches are dropped between tests
+#
+
+test_perf_large_repo
+test_checkout_worktree
+
+test_lazy_prereq UNTRACKED_CACHE '
+       { git update-index --test-untracked-cache; ret=$?; } &&
+       test $ret -ne 1
+'
+
+test_lazy_prereq WATCHMAN '
+       command -v watchman
+'
+
+if test_have_prereq WATCHMAN
+then
+       # Convert unix style paths to escaped Windows style paths for Watchman
+       case "$(uname -s)" in
+       MSYS_NT*)
+         GIT_WORK_TREE="$(cygpath -aw "$PWD" | sed 's,\\,/,g')"
+         ;;
+       *)
+         GIT_WORK_TREE="$PWD"
+         ;;
+       esac
+fi
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"
+then
+       # When using GIT_PERF_7519_DROP_CACHE, GIT_PERF_REPEAT_COUNT must be 1 to
+       # generate valid results. Otherwise the caching that happens for the nth
+       # run will negate the validity of the comparisons.
+       if test "$GIT_PERF_REPEAT_COUNT" -ne 1
+       then
+               echo "warning: Setting GIT_PERF_REPEAT_COUNT=1" >&2
+               GIT_PERF_REPEAT_COUNT=1
+       fi
+fi
+
+test_expect_success "setup for fsmonitor" '
+       # set untrackedCache depending on the environment
+       if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"
+       then
+               git config core.untrackedCache "$GIT_PERF_7519_UNTRACKED_CACHE"
+       else
+               if test_have_prereq UNTRACKED_CACHE
+               then
+                       git config core.untrackedCache true
+               else
+                       git config core.untrackedCache false
+               fi
+       fi &&
+
+       # set core.splitindex depending on the environment
+       if test -n "$GIT_PERF_7519_SPLIT_INDEX"
+       then
+               git config core.splitIndex "$GIT_PERF_7519_SPLIT_INDEX"
+       fi &&
+
+       # set INTEGRATION_SCRIPT depending on the environment
+       if test -n "$GIT_PERF_7519_FSMONITOR"
+       then
+               INTEGRATION_SCRIPT="$GIT_PERF_7519_FSMONITOR"
+       else
+               #
+               # Choose integration script based on existence of Watchman.
+               # If Watchman exists, watch the work tree and attempt a query.
+               # If everything succeeds, use Watchman integration script,
+               # else fall back to an empty integration script.
+               #
+               mkdir .git/hooks &&
+               if test_have_prereq WATCHMAN
+               then
+                       INTEGRATION_SCRIPT=".git/hooks/fsmonitor-watchman" &&
+                       cp "$TEST_DIRECTORY/../templates/hooks--fsmonitor-watchman.sample" "$INTEGRATION_SCRIPT" &&
+                       watchman watch "$GIT_WORK_TREE" &&
+                       watchman watch-list | grep -q -F "$GIT_WORK_TREE"
+               else
+                       INTEGRATION_SCRIPT=".git/hooks/fsmonitor-empty" &&
+                       write_script "$INTEGRATION_SCRIPT"<<-\EOF
+                       EOF
+               fi
+       fi &&
+
+       git config core.fsmonitor "$INTEGRATION_SCRIPT" &&
+       git update-index --fsmonitor
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+       test-drop-caches
+fi
+
+test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" '
+       git status
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+       test-drop-caches
+fi
+
+test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" '
+       git status -uno
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+       test-drop-caches
+fi
+
+test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" '
+       git status -uall
+'
+
+test_expect_success "setup without fsmonitor" '
+       unset INTEGRATION_SCRIPT &&
+       git config --unset core.fsmonitor &&
+       git update-index --no-fsmonitor
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+       test-drop-caches
+fi
+
+test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" '
+       git status
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+       test-drop-caches
+fi
+
+test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" '
+       git status -uno
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+       test-drop-caches
+fi
+
+test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" '
+       git status -uall
+'
+
+if test_have_prereq WATCHMAN
+then
+       watchman watch-del "$GIT_WORK_TREE" >/dev/null 2>&1 &&
+
+       # Work around Watchman bug on Windows where it holds on to handles
+       # preventing the removal of the trash directory
+       watchman shutdown-server >/dev/null 2>&1
+fi
+
+test_done
index b50211b2591d19904fa3c995b01aea2121db476a..e4c343a6b795b6f990fa278788ad65a0a5cdc4e6 100644 (file)
@@ -56,12 +56,10 @@ MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git
 export MODERN_GIT
 
 perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results
+test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION"
 mkdir -p "$perf_results_dir"
 rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests
 
-if test -z "$GIT_PERF_REPEAT_COUNT"; then
-       GIT_PERF_REPEAT_COUNT=3
-fi
 die_if_build_dir_not_repo () {
        if ! ( cd "$TEST_DIRECTORY/.." &&
                    git rev-parse --build-dir >/dev/null 2>&1 ); then
index beb4acc0e428d20280a649c428da8b0b3aa5b4c6..43e4de49ef2bea9ae09b502f28fbc0913e294571 100755 (executable)
@@ -2,9 +2,14 @@
 
 case "$1" in
        --help)
-               echo "usage: $0 [other_git_tree...] [--] [test_scripts]"
+               echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]"
                exit 0
                ;;
+       --config)
+               shift
+               GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1")
+               export GIT_PERF_CONFIG_FILE
+               shift ;;
 esac
 
 die () {
@@ -29,8 +34,10 @@ unpack_git_rev () {
        (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) |
        (cd build/$rev && tar x)
 }
+
 build_git_rev () {
        rev=$1
+       name="$2"
        for config in config.mak config.mak.autogen config.status
        do
                if test -e "../../$config"
@@ -38,7 +45,7 @@ build_git_rev () {
                        cp "../../$config" "build/$rev/"
                fi
        done
-       echo "=== Building $rev ==="
+       echo "=== Building $rev ($name) ==="
        (
                cd build/$rev &&
                if test -n "$GIT_PERF_MAKE_COMMAND"
@@ -65,7 +72,7 @@ run_dirs_helper () {
                if [ ! -d build/$rev ]; then
                        unpack_git_rev $rev
                fi
-               build_git_rev $rev
+               build_git_rev $rev "$mydir"
                mydir=build/$rev
        fi
        if test "$mydir" = .; then
@@ -87,14 +94,78 @@ run_dirs () {
        done
 }
 
-GIT_PERF_AGGREGATING_LATER=t
-export GIT_PERF_AGGREGATING_LATER
+get_subsections () {
+       section="$1"
+       test -z "$GIT_PERF_CONFIG_FILE" && return
+       git config -f "$GIT_PERF_CONFIG_FILE" --name-only --get-regex "$section\..*\.[^.]+" |
+       sed -e "s/$section\.\(.*\)\..*/\1/" | sort | uniq
+}
+
+get_var_from_env_or_config () {
+       env_var="$1"
+       conf_sec="$2"
+       conf_var="$3"
+       # $4 can be set to a default value
+
+       # Do nothing if the env variable is already set
+       eval "test -z \"\${$env_var+x}\"" || return
+
+       test -z "$GIT_PERF_CONFIG_FILE" && return
+
+       # Check if the variable is in the config file
+       if test -n "$GIT_PERF_SUBSECTION"
+       then
+               var="$conf_sec.$GIT_PERF_SUBSECTION.$conf_var"
+               conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") &&
+               eval "$env_var=\"$conf_value\"" && return
+       fi
+       var="$conf_sec.$conf_var"
+       conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") &&
+       eval "$env_var=\"$conf_value\"" && return
+
+       test -n "${4+x}" && eval "$env_var=\"$4\""
+}
+
+run_subsection () {
+       get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" 3
+       export GIT_PERF_REPEAT_COUNT
+
+       get_var_from_env_or_config "GIT_PERF_DIRS_OR_REVS" "perf" "dirsOrRevs"
+       set -- $GIT_PERF_DIRS_OR_REVS "$@"
+
+       get_var_from_env_or_config "GIT_PERF_MAKE_COMMAND" "perf" "makeCommand"
+       get_var_from_env_or_config "GIT_PERF_MAKE_OPTS" "perf" "makeOpts"
+
+       GIT_PERF_AGGREGATING_LATER=t
+       export GIT_PERF_AGGREGATING_LATER
+
+       if test $# = 0 -o "$1" = -- -o -f "$1"; then
+               set -- . "$@"
+       fi
+
+       run_dirs "$@"
+       ./aggregate.perl "$@"
+}
 
 cd "$(dirname $0)"
 . ../../GIT-BUILD-OPTIONS
 
-if test $# = 0 -o "$1" = -- -o -f "$1"; then
-       set -- . "$@"
+mkdir -p test-results
+get_subsections "perf" >test-results/run_subsections.names
+
+if test $(wc -l <test-results/run_subsections.names) -eq 0
+then
+       (
+               run_subsection "$@"
+       )
+else
+       while read -r subsec
+       do
+               (
+                       GIT_PERF_SUBSECTION="$subsec"
+                       export GIT_PERF_SUBSECTION
+                       echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========"
+                       run_subsection "$@"
+               )
+       done <test-results/run_subsections.names
 fi
-run_dirs "$@"
-./aggregate.perl "$@"
index 1aa5093f36ea61a75cb11de5a65ed11b2b3b4e65..7fd87dd5444040ad7273be51c29a56dea738a861 100755 (executable)
@@ -20,6 +20,31 @@ modification *should* take notice and update the test vectors here.
 
 . ./test-lib.sh
 
+try_local_x () {
+       local x="local" &&
+       echo "$x"
+}
+
+# This test is an experiment to check whether any Git users are using
+# Shells that don't support the "local" keyword. "local" is not
+# POSIX-standard, but it is very widely supported by POSIX-compliant
+# shells, and if it doesn't cause problems for people, we would like
+# to be able to use it in Git code.
+#
+# For now, this is the only test that requires "local". If your shell
+# fails this test, you can ignore the failure, but please report the
+# problem to the Git mailing list <git@vger.kernel.org>, as it might
+# convince us to continue avoiding the use of "local".
+test_expect_success 'verify that the running shell supports "local"' '
+       x="notlocal" &&
+       echo "local" >expected1 &&
+       try_local_x >actual1 &&
+       test_cmp expected1 actual1 &&
+       echo "notlocal" >expected2 &&
+       echo "$x" >actual2 &&
+       test_cmp expected2 actual2
+'
+
 ################################################################
 # git init has been done in an empty repository.
 # make sure it is empty.
index ad685d92f8c7aa138cb29b0ef46e5d09e246f42c..f1678851de954dd93ab0e2fc7912813ad5f09960 100644 (file)
 #     to the "list_available_blobs" response.
 #
 
+use 5.008;
+use lib (split(/:/, $ENV{GITPERLLIB}));
 use strict;
 use warnings;
 use IO::File;
+use Git::Packet;
 
 my $MAX_PACKET_CONTENT_SIZE = 65516;
 my $log_file                = shift @ARGV;
@@ -55,89 +58,30 @@ sub rot13 {
        return $str;
 }
 
-sub packet_bin_read {
-       my $buffer;
-       my $bytes_read = read STDIN, $buffer, 4;
-       if ( $bytes_read == 0 ) {
-               # EOF - Git stopped talking to us!
-               print $debug "STOP\n";
-               exit();
-       }
-       elsif ( $bytes_read != 4 ) {
-               die "invalid packet: '$buffer'";
-       }
-       my $pkt_size = hex($buffer);
-       if ( $pkt_size == 0 ) {
-               return ( 1, "" );
-       }
-       elsif ( $pkt_size > 4 ) {
-               my $content_size = $pkt_size - 4;
-               $bytes_read = read STDIN, $buffer, $content_size;
-               if ( $bytes_read != $content_size ) {
-                       die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
-               }
-               return ( 0, $buffer );
-       }
-       else {
-               die "invalid packet size: $pkt_size";
-       }
-}
-
-sub packet_txt_read {
-       my ( $res, $buf ) = packet_bin_read();
-       unless ( $buf eq '' or $buf =~ s/\n$// ) {
-               die "A non-binary line MUST be terminated by an LF.";
-       }
-       return ( $res, $buf );
-}
-
-sub packet_bin_write {
-       my $buf = shift;
-       print STDOUT sprintf( "%04x", length($buf) + 4 );
-       print STDOUT $buf;
-       STDOUT->flush();
-}
-
-sub packet_txt_write {
-       packet_bin_write( $_[0] . "\n" );
-}
-
-sub packet_flush {
-       print STDOUT sprintf( "%04x", 0 );
-       STDOUT->flush();
-}
-
 print $debug "START\n";
 $debug->flush();
 
-( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize";
-( packet_txt_read() eq ( 0, "version=2" ) )         || die "bad version";
-( packet_bin_read() eq ( 1, "" ) )                  || die "bad version end";
+packet_initialize("git-filter", 2);
 
-packet_txt_write("git-filter-server");
-packet_txt_write("version=2");
-packet_flush();
+my %remote_caps = packet_read_and_check_capabilities("clean", "smudge", "delay");
+packet_check_and_write_capabilities(\%remote_caps, @capabilities);
 
-( packet_txt_read() eq ( 0, "capability=clean" ) )  || die "bad capability";
-( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability";
-( packet_txt_read() eq ( 0, "capability=delay" ) )  || die "bad capability";
-( packet_bin_read() eq ( 1, "" ) )                  || die "bad capability end";
-
-foreach (@capabilities) {
-       packet_txt_write( "capability=" . $_ );
-}
-packet_flush();
 print $debug "init handshake complete\n";
 $debug->flush();
 
 while (1) {
-       my ( $command ) = packet_txt_read() =~ /^command=(.+)$/;
+       my ( $res, $command ) = packet_key_val_read("command");
+       if ( $res == -1 ) {
+               print $debug "STOP\n";
+               exit();
+       }
        print $debug "IN: $command";
        $debug->flush();
 
        if ( $command eq "list_available_blobs" ) {
                # Flush
-               packet_bin_read();
+               packet_compare_lists([1, ""], packet_bin_read()) ||
+                       die "bad list_available_blobs end";
 
                foreach my $pathname ( sort keys %DELAY ) {
                        if ( $DELAY{$pathname}{"requested"} >= 1 ) {
@@ -161,16 +105,14 @@ sub packet_flush {
                $debug->flush();
                packet_txt_write("status=success");
                packet_flush();
-       }
-       else {
-               my ( $pathname ) = packet_txt_read() =~ /^pathname=(.+)$/;
+       } else {
+               my ( $res, $pathname ) = packet_key_val_read("pathname");
+               if ( $res == -1 ) {
+                       die "unexpected EOF while expecting pathname";
+               }
                print $debug " $pathname";
                $debug->flush();
 
-               if ( $pathname eq "" ) {
-                       die "bad pathname '$pathname'";
-               }
-
                # Read until flush
                my ( $done, $buffer ) = packet_txt_read();
                while ( $buffer ne '' ) {
@@ -184,6 +126,9 @@ sub packet_flush {
 
                        ( $done, $buffer ) = packet_txt_read();
                }
+               if ( $done == -1 ) {
+                       die "unexpected EOF after pathname '$pathname'";
+               }
 
                my $input = "";
                {
@@ -194,6 +139,9 @@ sub packet_flush {
                                ( $done, $buffer ) = packet_bin_read();
                                $input .= $buffer;
                        }
+                       if ( $done == -1 ) {
+                               die "unexpected EOF while reading input for '$pathname'";
+                       }                       
                        print $debug " " . length($input) . " [OK] -- ";
                        $debug->flush();
                }
@@ -201,17 +149,13 @@ sub packet_flush {
                my $output;
                if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) {
                        $output = $DELAY{$pathname}{"output"}
-               }
-               elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
+               } elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
                        $output = "";
-               }
-               elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
+               } elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
                        $output = rot13($input);
-               }
-               elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
+               } elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
                        $output = rot13($input);
-               }
-               else {
+               } else {
                        die "bad command '$command'";
                }
 
@@ -220,25 +164,21 @@ sub packet_flush {
                        $debug->flush();
                        packet_txt_write("status=error");
                        packet_flush();
-               }
-               elsif ( $pathname eq "abort.r" ) {
+               } elsif ( $pathname eq "abort.r" ) {
                        print $debug "[ABORT]\n";
                        $debug->flush();
                        packet_txt_write("status=abort");
                        packet_flush();
-               }
-               elsif ( $command eq "smudge" and
+               } elsif ( $command eq "smudge" and
                        exists $DELAY{$pathname} and
-                       $DELAY{$pathname}{"requested"} == 1
-               ) {
+                       $DELAY{$pathname}{"requested"} == 1 ) {
                        print $debug "[DELAYED]\n";
                        $debug->flush();
                        packet_txt_write("status=delayed");
                        packet_flush();
                        $DELAY{$pathname}{"requested"} = 2;
                        $DELAY{$pathname}{"output"} = $output;
-               }
-               else {
+               } else {
                        packet_txt_write("status=success");
                        packet_flush();
 
@@ -258,8 +198,7 @@ sub packet_flush {
                                print $debug ".";
                                if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
                                        $output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
-                               }
-                               else {
+                               } else {
                                        $output = "";
                                }
                        }
diff --git a/t/t0025-crlf-renormalize.sh b/t/t0025-crlf-renormalize.sh
new file mode 100755 (executable)
index 0000000..9d9e02a
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+test_description='CRLF renormalization'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       git config core.autocrlf false &&
+       printf "LINEONE\nLINETWO\nLINETHREE\n" >LF.txt &&
+       printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >CRLF.txt &&
+       printf "LINEONE\r\nLINETWO\nLINETHREE\n" >CRLF_mix_LF.txt &&
+       git add . &&
+       git commit -m initial
+'
+
+test_expect_success 'renormalize CRLF in repo' '
+       echo "*.txt text=auto" >.gitattributes &&
+       git add --renormalize "*.txt" &&
+       cat >expect <<-\EOF &&
+       i/lf w/crlf attr/text=auto CRLF.txt
+       i/lf w/lf attr/text=auto LF.txt
+       i/lf w/mixed attr/text=auto CRLF_mix_LF.txt
+       EOF
+       git ls-files --eol |
+       sed -e "s/      / /g" -e "s/  */ /g" |
+       sort >actual &&
+       test_cmp expect actual
+'
+
+test_done
index deb3ae7813052d01b6dab92586e2c37d313ef8ff..beb5927f77f7f21456eef2835ee67402e487a899 100755 (executable)
@@ -43,19 +43,31 @@ create_gitattributes () {
        } >.gitattributes
 }
 
-create_NNO_files () {
+# Create 2 sets of files:
+# The NNO files are "Not NOrmalized in the repo. We use CRLF_mix_LF and store
+#   it under different names for the different test cases, see ${pfx}
+#   Depending on .gitattributes they are normalized at the next commit (or not)
+# The MIX files have different contents in the repo.
+#   Depending on its contents, the "new safer autocrlf" may kick in.
+create_NNO_MIX_files () {
        for crlf in false true input
        do
                for attr in "" auto text -text
                do
                        for aeol in "" lf crlf
                        do
-                               pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf}
+                               pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} &&
                                cp CRLF_mix_LF ${pfx}_LF.txt &&
                                cp CRLF_mix_LF ${pfx}_CRLF.txt &&
                                cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt &&
                                cp CRLF_mix_LF ${pfx}_LF_mix_CR.txt &&
-                               cp CRLF_mix_LF ${pfx}_CRLF_nul.txt
+                               cp CRLF_mix_LF ${pfx}_CRLF_nul.txt &&
+                               pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} &&
+                               cp LF          ${pfx}_LF.txt &&
+                               cp CRLF        ${pfx}_CRLF.txt &&
+                               cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt &&
+                               cp LF_mix_CR   ${pfx}_LF_mix_CR.txt &&
+                               cp CRLF_nul    ${pfx}_CRLF_nul.txt
                        done
                done
        done
@@ -136,6 +148,49 @@ commit_chk_wrnNNO () {
        '
 }
 
+# Commit a file with mixed line endings on top of different files
+# in the index. Check for warnings
+commit_MIX_chkwrn () {
+       attr=$1 ; shift
+       aeol=$1 ; shift
+       crlf=$1 ; shift
+       lfwarn=$1 ; shift
+       crlfwarn=$1 ; shift
+       lfmixcrlf=$1 ; shift
+       lfmixcr=$1 ; shift
+       crlfnul=$1 ; shift
+       pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf}
+       #Commit file with CLRF_mix_LF on top of existing file
+       create_gitattributes "$attr" $aeol &&
+       for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+       do
+               fname=${pfx}_$f.txt &&
+               cp CRLF_mix_LF $fname &&
+               printf Z >>"$fname" &&
+               git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err"
+       done
+
+       test_expect_success "commit file with mixed EOL onto LF crlf=$crlf attr=$attr" '
+               check_warning "$lfwarn" ${pfx}_LF.err
+       '
+       test_expect_success "commit file with mixed EOL onto CLRF attr=$attr aeol=$aeol crlf=$crlf" '
+               check_warning "$crlfwarn" ${pfx}_CRLF.err
+       '
+
+       test_expect_success "commit file with mixed EOL onto CRLF_mix_LF attr=$attr aeol=$aeol crlf=$crlf" '
+               check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err
+       '
+
+       test_expect_success "commit file with mixed EOL onto LF_mix_cr attr=$attr aeol=$aeol crlf=$crlf " '
+               check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err
+       '
+
+       test_expect_success "commit file with mixed EOL onto CRLF_nul attr=$attr aeol=$aeol crlf=$crlf" '
+               check_warning "$crlfnul" ${pfx}_CRLF_nul.err
+       '
+}
+
+
 stats_ascii () {
        case "$1" in
        LF)
@@ -315,7 +370,7 @@ test_expect_success 'setup master' '
        echo >.gitattributes &&
        git checkout -b master &&
        git add .gitattributes &&
-       git commit -m "add .gitattributes" "" &&
+       git commit -m "add .gitattributes" . &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\nLINETWO\nLINETHREE"     >LF &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\r\nLINETHREE" >CRLF &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\r\nLINETWO\nLINETHREE"   >CRLF_mix_LF &&
@@ -323,8 +378,8 @@ test_expect_success 'setup master' '
        printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\rLINETHREE"   >CRLF_mix_CR &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONEQ\r\nLINETWO\r\nLINETHREE" | q_to_nul >CRLF_nul &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONEQ\nLINETWO\nLINETHREE" | q_to_nul >LF_nul &&
-       create_NNO_files CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF &&
-       git -c core.autocrlf=false add NNO_*.txt &&
+       create_NNO_MIX_files &&
+       git -c core.autocrlf=false add NNO_*.txt MIX_*.txt &&
        git commit -m "mixed line endings" &&
        test_tick
 '
@@ -385,6 +440,18 @@ test_expect_success 'commit files attr=crlf' '
        commit_check_warn input "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" ""
 '
 
+# Commit "CRLFmixLF" on top of these files already in the repo:
+#                                         mixed     mixed     mixed       mixed       mixed
+#                                         onto      onto      onto        onto        onto
+#                 attr                    LF        CRLF      CRLFmixLF   LF_mix_CR   CRLFNUL
+commit_MIX_chkwrn ""      ""      false   ""        ""        ""          ""          ""
+commit_MIX_chkwrn ""      ""      true    "LF_CRLF" ""        ""          "LF_CRLF"   "LF_CRLF"
+commit_MIX_chkwrn ""      ""      input   "CRLF_LF" ""        ""          "CRLF_LF"   "CRLF_LF"
+
+commit_MIX_chkwrn "auto"  ""      false   "$WAMIX"  ""        ""          "$WAMIX"    "$WAMIX"
+commit_MIX_chkwrn "auto"  ""      true    "LF_CRLF" ""        ""          "LF_CRLF"   "LF_CRLF"
+commit_MIX_chkwrn "auto"  ""      input   "CRLF_LF" ""        ""          "CRLF_LF"   "CRLF_LF"
+
 #                 attr                    LF        CRLF      CRLFmixLF   LF_mix_CR   CRLFNUL
 commit_chk_wrnNNO ""      ""      false   ""        ""        ""          ""          ""
 commit_chk_wrnNNO ""      ""      true    LF_CRLF   ""        ""          ""          ""
index 364a537000bbbdd43047bd3f9c52c950a96dcbda..cbeb9bebeea67c3654c279b2c12fed4825840d8f 100755 (executable)
@@ -901,6 +901,36 @@ test_expect_success 'get --path barfs on boolean variable' '
        test_must_fail git config --get --path path.bool
 '
 
+test_expect_success 'get --expiry-date' '
+       rel="3.weeks.5.days.00:00" &&
+       rel_out="$rel ->" &&
+       cat >.git/config <<-\EOF &&
+       [date]
+       valid1 = "3.weeks.5.days 00:00"
+       valid2 = "Fri Jun 4 15:46:55 2010"
+       valid3 = "2017/11/11 11:11:11PM"
+       valid4 = "2017/11/10 09:08:07 PM"
+       valid5 = "never"
+       invalid1 = "abc"
+       EOF
+       cat >expect <<-EOF &&
+       $(test-date timestamp $rel)
+       1275666415
+       1510441871
+       1510348087
+       0
+       EOF
+       {
+               echo "$rel_out $(git config --expiry-date date.valid1)"
+               git config --expiry-date date.valid2 &&
+               git config --expiry-date date.valid3 &&
+               git config --expiry-date date.valid4 &&
+               git config --expiry-date date.valid5
+       } >actual &&
+       test_cmp expect actual &&
+       test_must_fail git config --expiry-date date.invalid1
+'
+
 cat > expect << EOF
 [quote]
        leading = " test"
index e88349c8a0483b97ea38013de61d799003fd85b5..c7878a60edfdcf24f3c45367f58e62b0ce6dab71 100755 (executable)
@@ -331,4 +331,47 @@ test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '
        grep "fatal: invalid ref format: ~a" err
 '
 
+test_expect_success 'branch rejects HEAD as a branch name' '
+       test_must_fail git branch HEAD HEAD^ &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'checkout -b rejects HEAD as a branch name' '
+       test_must_fail git checkout -B HEAD HEAD^ &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'update-ref can operate on refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git show-ref refs/heads/HEAD &&
+       git update-ref -d refs/heads/HEAD &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -d can remove refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git branch -d HEAD &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -m can rename refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git branch -m HEAD tail &&
+       test_must_fail git show-ref refs/heads/HEAD &&
+       git show-ref refs/heads/tail
+'
+
+test_expect_success 'branch -d can remove refs/heads/-dash' '
+       git update-ref refs/heads/-dash HEAD^ &&
+       git branch -d -- -dash &&
+       test_must_fail git show-ref refs/heads/-dash
+'
+
+test_expect_success 'branch -m can rename refs/heads/-dash' '
+       git update-ref refs/heads/-dash HEAD^ &&
+       git branch -m -- -dash dash &&
+       test_must_fail git show-ref refs/heads/-dash &&
+       git show-ref refs/heads/dash
+'
+
 test_done
index 22f69a410ba8194bbdff70471f5b3e746af116da..af9b847761f3c25ddbda8d1e1e978d1a323fa561 100755 (executable)
@@ -6,6 +6,7 @@ test_description='split index mode tests'
 
 # We need total control of index splitting here
 sane_unset GIT_TEST_SPLIT_INDEX
+sane_unset GIT_FSMONITOR_TEST
 
 test_expect_success 'enable split index' '
        git config splitIndex.maxPercentChange 100 &&
index fbb4ee9bb42dbcfdfcdb79511523a8fa2ac8b5d9..bb4f2e0c631f1de7421f50b9fa64f11276fe9645 100755 (executable)
@@ -186,4 +186,127 @@ test_expect_success 'no advice given for explicit detached head state' '
        test_cmp expect.no-advice actual
 '
 
+# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (new format)
+test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not asked to' "
+
+       # The first detach operation is more chatty than the following ones.
+       cat >1st_detach <<-'EOF' &&
+       Note: checking out 'HEAD^'.
+
+       You are in 'detached HEAD' state. You can look around, make experimental
+       changes and commit them, and you can discard any commits you make in this
+       state without impacting any branches by performing another checkout.
+
+       If you want to create a new branch to retain commits you create, you may
+       do so (now or later) by using -b with the checkout command again. Example:
+
+         git checkout -b <new-branch-name>
+
+       HEAD is now at 7c7cd714e262 three
+       EOF
+
+       # The remaining ones just show info about previous and current HEADs.
+       cat >2nd_detach <<-'EOF' &&
+       Previous HEAD position was 7c7cd714e262 three
+       HEAD is now at 139b20d8e6c5 two
+       EOF
+
+       cat >3rd_detach <<-'EOF' &&
+       Previous HEAD position was 139b20d8e6c5 two
+       HEAD is now at d79ce1670bdc one
+       EOF
+
+       reset &&
+       check_not_detached &&
+
+       # Various ways of *not* asking for ellipses
+
+       sane_unset GIT_PRINT_SHA1_ELLIPSIS &&
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 1st_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS="no" git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 2nd_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS= git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 3rd_detach actual &&
+
+       sane_unset GIT_PRINT_SHA1_ELLIPSIS &&
+
+       # We only have four commits, but we can re-use them
+       reset &&
+       check_not_detached &&
+
+       # Make no mention of the env var at all
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 1st_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS='nope' &&
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 2nd_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS=nein &&
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 3rd_detach actual &&
+
+       true
+"
+
+# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (old format)
+test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked to' "
+
+       # The first detach operation is more chatty than the following ones.
+       cat >1st_detach <<-'EOF' &&
+       Note: checking out 'HEAD^'.
+
+       You are in 'detached HEAD' state. You can look around, make experimental
+       changes and commit them, and you can discard any commits you make in this
+       state without impacting any branches by performing another checkout.
+
+       If you want to create a new branch to retain commits you create, you may
+       do so (now or later) by using -b with the checkout command again. Example:
+
+         git checkout -b <new-branch-name>
+
+       HEAD is now at 7c7cd714e262... three
+       EOF
+
+       # The remaining ones just show info about previous and current HEADs.
+       cat >2nd_detach <<-'EOF' &&
+       Previous HEAD position was 7c7cd714e262... three
+       HEAD is now at 139b20d8e6c5... two
+       EOF
+
+       cat >3rd_detach <<-'EOF' &&
+       Previous HEAD position was 139b20d8e6c5... two
+       HEAD is now at d79ce1670bdc... one
+       EOF
+
+       reset &&
+       check_not_detached &&
+
+       # Various ways of asking for ellipses...
+       # The user can just use any kind of quoting (including none).
+
+       GIT_PRINT_SHA1_ELLIPSIS=yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 1st_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS=Yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 2nd_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS=YES git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 3rd_detach actual &&
+
+       true
+"
+
 test_done
index b5c47ac602d65ebfb0f4baebc098f26d5d1a464c..2b959449730e14dd4e650ce9115a463d71eedca4 100755 (executable)
@@ -245,6 +245,12 @@ test_expect_success 'local clone from linked checkout' '
        ( cd here-clone && git fsck )
 '
 
+test_expect_success 'local clone --shared from linked checkout' '
+       git -C bare worktree add --detach ../baretree &&
+       git clone --local --shared baretree bare-clone &&
+       grep /bare/ bare-clone/.git/objects/info/alternates
+'
+
 test_expect_success '"add" worktree with --no-checkout' '
        git worktree add --no-checkout -b swamp swamp &&
        ! test -e swamp/init.t &&
@@ -313,5 +319,164 @@ test_expect_success 'checkout a branch under bisect' '
 test_expect_success 'rename a branch under bisect not allowed' '
        test_must_fail git branch -M under-bisect bisect-with-new-name
 '
+# Is branch "refs/heads/$1" set to pull from "$2/$3"?
+test_branch_upstream () {
+       printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
+       {
+               git config "branch.$1.remote" &&
+               git config "branch.$1.merge"
+       } >actual.upstream &&
+       test_cmp expect.upstream actual.upstream
+}
+
+test_expect_success '--track sets up tracking' '
+       test_when_finished rm -rf track &&
+       git worktree add --track -b track track master &&
+       test_branch_upstream track . master
+'
+
+# setup remote repository $1 and repository $2 with $1 set up as
+# remote.  The remote has two branches, master and foo.
+setup_remote_repo () {
+       git init $1 &&
+       (
+               cd $1 &&
+               test_commit $1_master &&
+               git checkout -b foo &&
+               test_commit upstream_foo
+       ) &&
+       git init $2 &&
+       (
+               cd $2 &&
+               test_commit $2_master &&
+               git remote add $1 ../$1 &&
+               git config remote.$1.fetch \
+                       "refs/heads/*:refs/remotes/$1/*" &&
+               git fetch --all
+       )
+}
+
+test_expect_success '--no-track avoids setting up tracking' '
+       test_when_finished rm -rf repo_upstream repo_local foo &&
+       setup_remote_repo repo_upstream repo_local &&
+       (
+               cd repo_local &&
+               git worktree add --no-track -b foo ../foo repo_upstream/foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success '"add" <path> <non-existent-branch> fails' '
+       test_must_fail git worktree add foo non-existent
+'
+
+test_expect_success '"add" <path> <branch> dwims' '
+       test_when_finished rm -rf repo_upstream repo_dwim foo &&
+       setup_remote_repo repo_upstream repo_dwim &&
+       git init repo_dwim &&
+       (
+               cd repo_dwim &&
+               git worktree add ../foo foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_upstream foo &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add does not match remote' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git worktree add ../foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add --guess-remote sets up tracking' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git worktree add --guess-remote ../foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_a foo &&
+               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git config worktree.guessRemote true &&
+               git worktree add ../foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_a foo &&
+               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree --no-guess-remote option overrides config' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git config worktree.guessRemote true &&
+               git worktree add --no-guess-remote ../foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+post_checkout_hook () {
+       test_when_finished "rm -f .git/hooks/post-checkout" &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/post-checkout <<-\EOF
+       echo $* >hook.actual
+       EOF
+}
+
+test_expect_success '"add" invokes post-checkout hook (branch)' '
+       post_checkout_hook &&
+       printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
+       git worktree add gumby &&
+       test_cmp hook.expect hook.actual
+'
+
+test_expect_success '"add" invokes post-checkout hook (detached)' '
+       post_checkout_hook &&
+       printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
+       git worktree add --detach grumpy &&
+       test_cmp hook.expect hook.actual
+'
+
+test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
+       post_checkout_hook &&
+       rm -f hook.actual &&
+       git worktree add --no-checkout gloopy &&
+       test_path_is_missing hook.actual
+'
 
 test_done
index 0a4ff6d824a0a3bf44ba8d93c2a8dffdb9c1af65..b81eb5fd6ffaf02d2a404bcbbf71e674494fd6cb 100755 (executable)
@@ -19,7 +19,7 @@ test_expect_success 'setup: create subprojects' '
        git update-index --add sub1 &&
        git add sub2 &&
        git commit -q -m "subprojects added" &&
-       git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
+       GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
        git branch save HEAD &&
        cat >expected <<-\EOF &&
        :000000 160000 00000... A       sub1
index 6a82d1ed876dd5d1073dc63be8ba5720adbf12e3..481a3500900d0fccb28762ba24b2ca422956a44f 100755 (executable)
@@ -1260,6 +1260,28 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
        test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
+test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '
+       rebase_setup_and_clean abbrevcmd &&
+       test_commit "first" file1.txt "first line" first &&
+       test_commit "second" file1.txt "another line" second &&
+       test_commit "fixup! first" file2.txt "first line again" first_fixup &&
+       test_commit "squash! second" file1.txt "another line here" second_squash &&
+       cat >expected <<-EOF &&
+       p $(git rev-list --abbrev-commit -1 first) first
+       f $(git rev-list --abbrev-commit -1 first_fixup) fixup! first
+       x git show HEAD
+       p $(git rev-list --abbrev-commit -1 second) second
+       s $(git rev-list --abbrev-commit -1 second_squash) squash! second
+       x git show HEAD
+       EOF
+       git checkout abbrevcmd &&
+       set_cat_todo_editor &&
+       test_config rebase.abbreviateCommands true &&
+       test_must_fail git rebase -i --exec "git show HEAD" \
+               --autosquash master >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'static check of bad command' '
        rebase_setup_and_clean bad-cmd &&
        set_fake_editor &&
index ab5500db445bad56774bf8eae731c9e2e19718a9..46f15169f55c03742717f36f8d0e99241391b2ea 100755 (executable)
@@ -858,9 +858,8 @@ test_expect_success 'rm files with two different errors' '
        test_i18ncmp expect actual
 '
 
-test_expect_success 'rm empty string should invoke warning' '
-       git rm -rf "" 2>output &&
-       test_i18ngrep "warning: empty strings" output
+test_expect_success 'rm empty string should fail' '
+       test_must_fail git rm -rf ""
 '
 
 test_done
index 0aae21d6984bf0bd5c7c7de425a021da6122beee..2748805642201d7c514792bab8d8b3940fb4086c 100755 (executable)
@@ -331,9 +331,8 @@ test_expect_success 'git add --dry-run --ignore-missing of non-existing file out
        test_i18ncmp expect.err actual.err
 '
 
-test_expect_success 'git add empty string should invoke warning' '
-       git add "" 2>output &&
-       test_i18ngrep "warning: empty strings" output
+test_expect_success 'git add empty string should fail' '
+       test_must_fail git add ""
 '
 
 test_expect_success 'git add --chmod=[+-]x stages correctly' '
index 3b1ac1971af1d972b77b3dfe8ae4a56e00e1fa55..39c7f2ebd740db097d67b12a68fa35f3fa884fc8 100755 (executable)
@@ -804,6 +804,99 @@ test_expect_success 'push -m shows right message' '
        test_cmp expect actual
 '
 
+test_expect_success 'push -m also works without space' '
+       >foo &&
+       git add foo &&
+       git stash push -m"unspaced test message" &&
+       echo "stash@{0}: On master: unspaced test message" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store -m foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store -m "store m" $STASH_ID &&
+       echo "stash@{0}: store m" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store -mfoo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store -m"store mfoo" $STASH_ID &&
+       echo "stash@{0}: store mfoo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store --message=foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store --message="store message=foo" $STASH_ID &&
+       echo "stash@{0}: store message=foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store --message foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store --message "store message foo" $STASH_ID &&
+       echo "stash@{0}: store message foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push -mfoo uses right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m"test mfoo" &&
+       echo "stash@{0}: On master: test mfoo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --message foo is synonym for -mfoo' '
+       >foo &&
+       git add foo &&
+       git stash push --message "test message foo" &&
+       echo "stash@{0}: On master: test message foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --message=foo is synonym for -mfoo' '
+       >foo &&
+       git add foo &&
+       git stash push --message="test message=foo" &&
+       echo "stash@{0}: On master: test message=foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push -m shows right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m "test m foo" &&
+       echo "stash@{0}: On master: test m foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'create stores correct message' '
        >foo &&
        git add foo &&
index 0d1fa45d25e59ea100864a76b105b149c6dfcb85..eadf4f62444350c711a313d2161c9fa17c7aa405 100755 (executable)
@@ -230,4 +230,19 @@ test_expect_success 'rename pretty print common prefix and suffix overlap' '
        test_i18ngrep " d/f/{ => f}/e " output
 '
 
+test_expect_success 'diff-tree -l0 defaults to a big rename limit, not zero' '
+       test_write_lines line1 line2 line3 >myfile &&
+       git add myfile &&
+       git commit -m x &&
+
+       test_write_lines line1 line2 line4 >myotherfile &&
+       git rm myfile &&
+       git add myotherfile &&
+       git commit -m x &&
+
+       git diff-tree -M -l0 HEAD HEAD^ >actual &&
+       # Verify that a rename from myotherfile to myfile was detected
+       grep "myotherfile.*myfile" actual
+'
+
 test_done
index c515e3e53feef29fd6c7434e13d52b3ea913e167..f10798b2dff35df131fabb351240ebf75952b446 100755 (executable)
@@ -118,20 +118,37 @@ test_expect_success setup '
 EOF
 
 V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g')
-while read cmd
+while read magic cmd
 do
-       case "$cmd" in
-       '' | '#'*) continue ;;
+       case "$magic" in
+       '' | '#'*)
+               continue ;;
+       :*)
+               magic=${magic#:}
+               label="$magic-$cmd"
+               case "$magic" in
+               noellipses) ;;
+               *)
+                       die "bug in t4103: unknown magic $magic" ;;
+               esac ;;
+       *)
+               cmd="$magic $cmd" magic=
+               label="$cmd" ;;
        esac
-       test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g')
+       test=$(echo "$label" | sed -e 's|[/ ][/ ]*|_|g')
        pfx=$(printf "%04d" $test_count)
        expect="$TEST_DIRECTORY/t4013/diff.$test"
        actual="$pfx-diff.$test"
 
-       test_expect_success "git $cmd" '
+       test_expect_success "git $cmd # magic is ${magic:-"(not used)"}" '
                {
-                       echo "\$ git $cmd"
-                       git $cmd |
+                       echo "$ git $cmd"
+                       case "$magic" in
+                       "")
+                               GIT_PRINT_SHA1_ELLIPSIS=yes git $cmd ;;
+                       noellipses)
+                               git $cmd ;;
+                       esac |
                        sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \
                            -e "s/^\\(.*mixed; boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/"
                        echo "\$"
@@ -158,9 +175,12 @@ diff-tree -r --abbrev initial
 diff-tree -r --abbrev=4 initial
 diff-tree --root initial
 diff-tree --root --abbrev initial
+:noellipses diff-tree --root --abbrev initial
 diff-tree --root -r initial
 diff-tree --root -r --abbrev initial
+:noellipses diff-tree --root -r --abbrev initial
 diff-tree --root -r --abbrev=4 initial
+:noellipses diff-tree --root -r --abbrev=4 initial
 diff-tree -p initial
 diff-tree --root -p initial
 diff-tree --patch-with-stat initial
@@ -209,6 +229,7 @@ diff-tree -p master
 diff-tree -p -m master
 diff-tree -c master
 diff-tree -c --abbrev master
+:noellipses diff-tree -c --abbrev master
 diff-tree --cc master
 # stat only should show the diffstat with the first parent
 diff-tree -c --stat master
@@ -255,8 +276,10 @@ rev-list --parents HEAD
 rev-list --children HEAD
 
 whatchanged master
+:noellipses whatchanged master
 whatchanged -p master
 whatchanged --root master
+:noellipses whatchanged --root master
 whatchanged --root -p master
 whatchanged --patch-with-stat master
 whatchanged --root --patch-with-stat master
@@ -266,6 +289,7 @@ whatchanged --root -c --patch-with-stat --summary master
 # improved by Timo's patch
 whatchanged --root --cc --patch-with-stat --summary master
 whatchanged -SF master
+:noellipses whatchanged -SF master
 whatchanged -SF -p master
 
 log --patch-with-stat master -- dir/
@@ -284,6 +308,7 @@ show --stat side
 show --stat --summary side
 show --patch-with-stat side
 show --patch-with-raw side
+:noellipses show --patch-with-raw side
 show --patch-with-stat --summary side
 
 format-patch --stdout initial..side
@@ -311,8 +336,10 @@ diff -r --stat initial..side
 diff initial..side
 diff --patch-with-stat initial..side
 diff --patch-with-raw initial..side
+:noellipses diff --patch-with-raw initial..side
 diff --patch-with-stat -r initial..side
 diff --patch-with-raw -r initial..side
+:noellipses diff --patch-with-raw -r initial..side
 diff --name-status dir2 dir
 diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
@@ -325,10 +352,14 @@ diff --dirstat initial rearrange
 diff --dirstat-by-file initial rearrange
 # No-index --abbrev and --no-abbrev
 diff --raw initial
+:noellipses diff --raw initial
 diff --raw --abbrev=4 initial
+:noellipses diff --raw --abbrev=4 initial
 diff --raw --no-abbrev initial
 diff --no-index --raw dir2 dir
+:noellipses diff --no-index --raw dir2 dir
 diff --no-index --raw --abbrev=4 dir2 dir
+:noellipses diff --no-index --raw --abbrev=4 dir2 dir
 diff --no-index --raw --no-abbrev dir2 dir
 EOF
 
diff --git a/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial
new file mode 100644 (file)
index 0000000..4bdad40
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root --abbrev initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 040000 0000000 da7a33f A       dir
+:000000 100644 0000000 01e79c3 A       file0
+:000000 100644 0000000 01e79c3 A       file2
+$
diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial
new file mode 100644 (file)
index 0000000..26fbfeb
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r --abbrev=4 initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000 35d2 A     dir/sub
+:000000 100644 0000 01e7 A     file0
+:000000 100644 0000 01e7 A     file2
+$
diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial
new file mode 100644 (file)
index 0000000..2ac8561
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r --abbrev initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000000 35d242b A       dir/sub
+:000000 100644 0000000 01e79c3 A       file0
+:000000 100644 0000000 01e79c3 A       file2
+$
diff --git a/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master
new file mode 100644 (file)
index 0000000..bb80f01
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c --abbrev master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+::100644 100644 100644 cead32e 7289e35 992913c MM      dir/sub
+::100644 100644 100644 b414108 f4615da 10a8a9f MM      file0
+$
diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir
new file mode 100644 (file)
index 0000000..41b7baf
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --no-index --raw --abbrev=4 dir2 dir
+:000000 100644 0000 0000 A     dir/sub
+$
diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir
new file mode 100644 (file)
index 0000000..0cf3a3e
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --no-index --raw dir2 dir
+:000000 100644 0000000 0000000 A       dir/sub
+$
diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side
new file mode 100644 (file)
index 0000000..8d1f1e3
--- /dev/null
@@ -0,0 +1,36 @@
+$ git diff --patch-with-raw -r initial..side
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side
new file mode 100644 (file)
index 0000000..50d8aee
--- /dev/null
@@ -0,0 +1,36 @@
+$ git diff --patch-with-raw initial..side
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial b/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial
new file mode 100644 (file)
index 0000000..8ae44d6
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff --raw --abbrev=4 initial
+:100644 100644 35d2 9929 M     dir/sub
+:100644 100644 01e7 10a8 M     file0
+:000000 100644 0000 b1e6 A     file1
+:100644 000000 01e7 0000 D     file2
+$
diff --git a/t/t4013/diff.noellipses-diff_--raw_initial b/t/t4013/diff.noellipses-diff_--raw_initial
new file mode 100644 (file)
index 0000000..0175bfb
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff --raw initial
+:100644 100644 35d242b 992913c M       dir/sub
+:100644 100644 01e79c3 10a8a9f M       file0
+:000000 100644 0000000 b1e6722 A       file1
+:100644 000000 01e79c3 0000000 D       file2
+$
diff --git a/t/t4013/diff.noellipses-show_--patch-with-raw_side b/t/t4013/diff.noellipses-show_--patch-with-raw_side
new file mode 100644 (file)
index 0000000..32fed3d
--- /dev/null
@@ -0,0 +1,42 @@
+$ git show --patch-with-raw side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.noellipses-whatchanged_--root_master b/t/t4013/diff.noellipses-whatchanged_--root_master
new file mode 100644 (file)
index 0000000..c2cfd4e
--- /dev/null
@@ -0,0 +1,42 @@
+$ git whatchanged --root master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40 cead32e M       dir/sub
+:000000 100644 0000000 b1e6722 A       file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+:100644 100644 35d242b 8422d40 M       dir/sub
+:100644 100644 01e79c3 b414108 M       file0
+:100644 000000 01e79c3 0000000 D       file2
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+:000000 100644 0000000 35d242b A       dir/sub
+:000000 100644 0000000 01e79c3 A       file0
+:000000 100644 0000000 01e79c3 A       file2
+$
diff --git a/t/t4013/diff.noellipses-whatchanged_-SF_master b/t/t4013/diff.noellipses-whatchanged_-SF_master
new file mode 100644 (file)
index 0000000..b36ce58
--- /dev/null
@@ -0,0 +1,9 @@
+$ git whatchanged -SF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40 cead32e M       dir/sub
+$
diff --git a/t/t4013/diff.noellipses-whatchanged_master b/t/t4013/diff.noellipses-whatchanged_master
new file mode 100644 (file)
index 0000000..55e500f
--- /dev/null
@@ -0,0 +1,32 @@
+$ git whatchanged master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40 cead32e M       dir/sub
+:000000 100644 0000000 b1e6722 A       file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+:100644 100644 35d242b 8422d40 M       dir/sub
+:100644 100644 01e79c3 b414108 M       file0
+:100644 000000 01e79c3 0000000 D       file2
+$
index 6c9a93b734542cc17fa1591525dc3c49019b77fe..17df491a3abe84fca63bb899d1162832d13e1847 100755 (executable)
@@ -106,6 +106,8 @@ test_expect_success 'another test, without options' '
        git diff -w -b --ignore-space-at-eol >out &&
        test_cmp expect out &&
 
+       git diff -w --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
 
        tr "Q_" "\015 " <<-\EOF >expect &&
        diff --git a/x b/x
@@ -128,6 +130,9 @@ test_expect_success 'another test, without options' '
        git diff -b --ignore-space-at-eol >out &&
        test_cmp expect out &&
 
+       git diff -b --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
+
        tr "Q_" "\015 " <<-\EOF >expect &&
        diff --git a/x b/x
        index d99af23..22d9f73 100644
@@ -145,6 +150,29 @@ test_expect_success 'another test, without options' '
         CR at end
        EOF
        git diff --ignore-space-at-eol >out &&
+       test_cmp expect out &&
+
+       git diff --ignore-space-at-eol --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
+
+       tr "Q_" "\015 " <<-\EOF >expect &&
+       diff --git a/x b/x
+       index_d99af23..22d9f73 100644
+       --- a/x
+       +++ b/x
+       @@ -1,6 +1,6 @@
+       -whitespace at beginning
+       -whitespace change
+       -whitespace in the middle
+       -whitespace at end
+       +_      whitespace at beginning
+       +whitespace_    _change
+       +white space in the middle
+       +whitespace at end__
+        unchanged line
+        CR at end
+       EOF
+       git diff --ignore-cr-at-eol >out &&
        test_cmp expect out
 '
 
@@ -608,6 +636,23 @@ test_expect_success 'check with space before tab in indent (diff-tree)' '
        test_must_fail git diff-tree --check HEAD^ HEAD
 '
 
+test_expect_success 'check with ignored trailing whitespace attr (diff-tree)' '
+       test_when_finished "git reset --hard HEAD^" &&
+
+       # create a whitespace error that should be ignored
+       echo "* -whitespace" >.gitattributes &&
+       git add .gitattributes &&
+       echo "foo(); " >x &&
+       git add x &&
+       git commit -m "add trailing space" &&
+
+       # with a worktree diff-tree ignores the whitespace error
+       git diff-tree --root --check HEAD &&
+
+       # without a worktree diff-tree still ignores the whitespace error
+       git -C .git diff-tree --root --check HEAD
+'
+
 test_expect_success 'check trailing whitespace (trailing-space: off)' '
        git config core.whitespace "-trailing-space" &&
        echo "foo ();   " >x &&
index 3950f5034d319be99695c9332b22a8d26e20ff32..6471a68701dd4bc140d43ba91ebb899036ae0d4d 100755 (executable)
@@ -12,62 +12,76 @@ test_expect_success 'setup' '
        git commit -m one
 '
 
-check_diff() {
-expect=$1; shift
-cat >expected <<EOF
-diff --git a/$expect b/$expect
-new file mode 100644
-index 0000000..25c05ef
---- /dev/null
-+++ b/$expect
-@@ -0,0 +1 @@
-+other content
-EOF
-test_expect_success "-p $*" "
-       git diff -p $* HEAD^ >actual &&
-       test_cmp expected actual
-"
+check_diff () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+       diff --git a/$expect b/$expect
+       new file mode 100644
+       index 0000000..25c05ef
+       --- /dev/null
+       +++ b/$expect
+       @@ -0,0 +1 @@
+       +other content
+       EOF
+       test_expect_success "-p $*" "
+               git -C '$dir' diff -p $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
 }
 
-check_numstat() {
-expect=$1; shift
-cat >expected <<EOF
-1      0       $expect
-EOF
-test_expect_success "--numstat $*" "
-       echo '1 0       $expect' >expected &&
-       git diff --numstat $* HEAD^ >actual &&
-       test_cmp expected actual
-"
+check_numstat () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+       1       0       $expect
+       EOF
+       test_expect_success "--numstat $*" "
+               echo '1 0       $expect' >expected &&
+               git -C '$dir' diff --numstat $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
 }
 
-check_stat() {
-expect=$1; shift
-cat >expected <<EOF
- $expect | 1 +
- 1 file changed, 1 insertion(+)
-EOF
-test_expect_success "--stat $*" "
-       git diff --stat $* HEAD^ >actual &&
-       test_i18ncmp expected actual
-"
+check_stat () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+        $expect | 1 +
+        1 file changed, 1 insertion(+)
+       EOF
+       test_expect_success "--stat $*" "
+               git -C '$dir' diff --stat $* HEAD^ >actual &&
+               test_i18ncmp expected actual
+       "
 }
 
-check_raw() {
-expect=$1; shift
-cat >expected <<EOF
-:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A     $expect
-EOF
-test_expect_success "--raw $*" "
-       git diff --no-abbrev --raw $* HEAD^ >actual &&
-       test_cmp expected actual
-"
+check_raw () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+       :000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A      $expect
+       EOF
+       test_expect_success "--raw $*" "
+               git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
 }
 
-for type in diff numstat stat raw; do
-       check_$type file2 --relative=subdir/
-       check_$type file2 --relative=subdir
-       check_$type dir/file2 --relative=sub
+for type in diff numstat stat raw
+do
+       check_$type . file2 --relative=subdir/
+       check_$type . file2 --relative=subdir
+       check_$type subdir file2 --relative
+       check_$type . dir/file2 --relative=sub
 done
 
 test_done
index 3e6b485ecba1a59e3d69edc3ac5a6f0f1d327bd3..2d76a971c43f6850a1f443eab3e91bf3762fe952 100755 (executable)
@@ -85,6 +85,10 @@ test_expect_success 'setup' '
 
 check_diff changed_hello 'changed function'
 
+test_expect_success ' context includes comment' '
+       grep "^ .*Hello comment" changed_hello.diff
+'
+
 test_expect_success ' context includes begin' '
        grep "^ .*Begin of hello" changed_hello.diff
 '
index 63b1a1e4efbb4c5dee315ba56a6ad0658da0a9a5..73e767e1783d8ab97d7a45297a173b1bed9f00a2 100644 (file)
@@ -1,4 +1,7 @@
 
+/*
+ * Hello comment.
+ */
 static void hello(void)        // Begin of hello
 {
        /*
diff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh
new file mode 100755 (executable)
index 0000000..b3f510f
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+test_description='anchored diff algorithm'
+
+. ./test-lib.sh
+
+test_expect_success '--anchored' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       # normally, c is moved to produce the smallest diff
+       test_expect_code 1 git diff --no-index pre post >diff &&
+       grep "^+c" diff &&
+
+       # with anchor, a is moved
+       test_expect_code 1 git diff --no-index --anchored=c pre post >diff &&
+       grep "^+a" diff
+'
+
+test_expect_success '--anchored multiple' '
+       printf "a\nb\nc\nd\ne\nf\n" >pre &&
+       printf "c\na\nb\nf\nd\ne\n" >post &&
+
+       # with 1 anchor, c is not moved, but f is moved
+       test_expect_code 1 git diff --no-index --anchored=c pre post >diff &&
+       grep "^+a" diff && # a is moved instead of c
+       grep "^+f" diff &&
+
+       # with 2 anchors, c and f are not moved
+       test_expect_code 1 git diff --no-index --anchored=c --anchored=f pre post >diff &&
+       grep "^+a" diff &&
+       grep "^+d" diff # d is moved instead of f
+'
+
+test_expect_success '--anchored with nonexistent line has no effect' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       test_expect_code 1 git diff --no-index --anchored=x pre post >diff &&
+       grep "^+c" diff
+'
+
+test_expect_success '--anchored with non-unique line has no effect' '
+       printf "a\nb\nc\nd\ne\nc\n" >pre &&
+       printf "c\na\nb\nc\nd\ne\n" >post &&
+
+       test_expect_code 1 git diff --no-index --anchored=c pre post >diff &&
+       grep "^+c" diff
+'
+
+test_expect_success 'diff still produced with impossible multiple --anchored' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       test_expect_code 1 git diff --no-index --anchored=a --anchored=c pre post >diff &&
+       mv post expected_post &&
+
+       # Ensure that the diff is correct by applying it and then
+       # comparing the result with the original
+       git apply diff &&
+       diff expected_post post
+'
+
+test_expect_success 'later algorithm arguments override earlier ones' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       test_expect_code 1 git diff --no-index --patience --anchored=c pre post >diff &&
+       grep "^+a" diff &&
+
+       test_expect_code 1 git diff --no-index --anchored=c --patience pre post >diff &&
+       grep "^+c" diff &&
+
+       test_expect_code 1 git diff --no-index --histogram --anchored=c pre post >diff &&
+       grep "^+a" diff &&
+
+       test_expect_code 1 git diff --no-index --anchored=c --histogram pre post >diff &&
+       grep "^+c" diff
+'
+
+test_expect_success '--anchored works with other commands like "git show"' '
+       printf "a\nb\nc\n" >file &&
+       git add file &&
+       git commit -m foo &&
+       printf "c\na\nb\n" >file &&
+       git add file &&
+       git commit -m foo &&
+
+       # with anchor, a is moved
+       git show --patience --anchored=c >diff &&
+       grep "^+a" diff
+'
+
+test_done
index 8f155da7a50a657db9fd092e24639f67cd03cd24..25b1f8cc73bc35bedaaffeacbb75a1329d725c3b 100755 (executable)
@@ -737,6 +737,107 @@ test_expect_success 'log.decorate configuration' '
 
 '
 
+test_expect_success 'decorate-refs with glob' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b (octopus-b)
+       octopus-a (octopus-a)
+       reach
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="heads/octopus*" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs without globs' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b
+       octopus-a
+       reach (tag: reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="tags/reach" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'multiple decorate-refs' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b (octopus-b)
+       octopus-a (octopus-a)
+       reach (tag: reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="heads/octopus*" \
+               --decorate-refs="tags/reach" >actual &&
+    test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs-exclude with glob' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (HEAD -> master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh (tag: seventh)
+       octopus-b (tag: octopus-b)
+       octopus-a (tag: octopus-a)
+       reach (tag: reach, reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs-exclude="heads/octopus*" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs-exclude without globs' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (HEAD -> master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh (tag: seventh)
+       octopus-b (tag: octopus-b, octopus-b)
+       octopus-a (tag: octopus-a, octopus-a)
+       reach (reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs-exclude="tags/reach" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'multiple decorate-refs-exclude' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (HEAD -> master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh (tag: seventh)
+       octopus-b (tag: octopus-b)
+       octopus-a (tag: octopus-a)
+       reach (reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs-exclude="heads/octopus*" \
+               --decorate-refs-exclude="tags/reach" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs and decorate-refs-exclude' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b
+       octopus-a
+       reach (reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="heads/*" \
+               --decorate-refs-exclude="heads/oc*" >actual &&
+       test_cmp expect.decorate actual
+'
+
 test_expect_success 'log.decorate config parsing' '
        git log --oneline --decorate=full >expect.full &&
        git log --oneline --decorate=short >expect.short &&
index 935df6a65cab2ba901856633baad7b2e38ebecfa..a1705f70cffe2332fc65f670797539986f330f70 100755 (executable)
@@ -93,4 +93,23 @@ test_expect_success 'command line pathspec parsing for "git log"' '
        git log --merge -- a
 '
 
+test_expect_success 'tree_entry_interesting does not match past submodule boundaries' '
+       test_when_finished "rm -rf repo submodule" &&
+       git init submodule &&
+       test_commit -C submodule initial &&
+       git init repo &&
+       >"repo/[bracket]" &&
+       git -C repo add "[bracket]" &&
+       test_tick &&
+       git -C repo commit -m bracket &&
+       git -C repo rev-list HEAD -- "[bracket]" >expect &&
+
+       git -C repo submodule add ../submodule &&
+       test_tick &&
+       git -C repo commit -m submodule &&
+
+       git -C repo rev-list HEAD -- "[bracket]" >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh
new file mode 100755 (executable)
index 0000000..1b0acc3
--- /dev/null
@@ -0,0 +1,375 @@
+#!/bin/sh
+
+test_description='git pack-objects using object filtering'
+
+. ./test-lib.sh
+
+# Test blob:none filter.
+
+test_expect_success 'setup r1' '
+       echo "{print \$1}" >print_1.awk &&
+       echo "{print \$2}" >print_2.awk &&
+
+       git init r1 &&
+       for n in 1 2 3 4 5
+       do
+               echo "This is file: $n" > r1/file.$n
+               git -C r1 add file.$n
+               git -C r1 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r1 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r1 index-pack ../all.pack &&
+       git -C r1 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:none packfile has no blobs' '
+       git -C r1 pack-objects --rev --stdout --filter=blob:none >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r1 index-pack ../filter.pack &&
+       git -C r1 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       nr=$(wc -l <observed) &&
+       test 0 -eq $nr
+'
+
+test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
+       git -C r1 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r1 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test blob:limit=<n>[kmg] filter.
+# We boundary test around the size parameter.  The filter is strictly less than
+# the value, so size 500 and 1000 should have the same results, but 1001 should
+# filter more.
+
+test_expect_success 'setup r2' '
+       git init r2 &&
+       for n in 1000 10000
+       do
+               printf "%"$n"s" X > r2/large.$n
+               git -C r2 add large.$n
+               git -C r2 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../all.pack &&
+       git -C r2 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=500 omits all blobs' '
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=500 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       nr=$(wc -l <observed) &&
+       test 0 -eq $nr
+'
+
+test_expect_success 'verify blob:limit=1000' '
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1000 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       nr=$(wc -l <observed) &&
+       test 0 -eq $nr
+'
+
+test_expect_success 'verify blob:limit=1001' '
+       git -C r2 ls-files -s large.1000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1001 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=10001' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=10001 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1k' '
+       git -C r2 ls-files -s large.1000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1m' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1m >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' '
+       git -C r2 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:path=<path> filter.
+# Use a local file containing a sparse-checkout specification to filter
+# out blobs not required for the corresponding sparse-checkout.  We do not
+# require sparse-checkout to actually be enabled.
+
+test_expect_success 'setup r3' '
+       git init r3 &&
+       mkdir r3/dir1 &&
+       for n in sparse1 sparse2
+       do
+               echo "This is file: $n" > r3/$n
+               git -C r3 add $n
+               echo "This is file: dir1/$n" > r3/dir1/$n
+               git -C r3 add dir1/$n
+       done &&
+       git -C r3 commit -m "sparse" &&
+       echo dir1/ >pattern1 &&
+       echo sparse1 >pattern2
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r3 index-pack ../all.pack &&
+       git -C r3 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:path=pattern1' '
+       git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern1 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r3 index-pack ../filter.pack &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify normal and sparse:path=pattern1 packfiles have same commits/trees' '
+       git -C r3 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:path=pattern2' '
+       git -C r3 ls-files -s sparse1 dir1/sparse1 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern2 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r3 index-pack ../filter.pack &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify normal and sparse:path=pattern2 packfiles have same commits/trees' '
+       git -C r3 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:oid=<oid-ish> filter.
+# Like sparse:path, but we get the sparse-checkout specification from
+# a blob rather than a file on disk.
+
+test_expect_success 'setup r4' '
+       git init r4 &&
+       mkdir r4/dir1 &&
+       for n in sparse1 sparse2
+       do
+               echo "This is file: $n" > r4/$n
+               git -C r4 add $n
+               echo "This is file: dir1/$n" > r4/dir1/$n
+               git -C r4 add dir1/$n
+       done &&
+       echo dir1/ >r4/pattern &&
+       git -C r4 add pattern &&
+       git -C r4 commit -m "pattern"
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r4 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r4 index-pack ../all.pack &&
+       git -C r4 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:oid=OID' '
+       git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) &&
+       git -C r4 pack-objects --rev --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r4 index-pack ../filter.pack &&
+       git -C r4 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:oid=oid-ish' '
+       git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r4 pack-objects --rev --stdout --filter=sparse:oid=master:pattern >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r4 index-pack ../filter.pack &&
+       git -C r4 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Delete some loose objects and use pack-objects, but WITHOUT any filtering.
+# This models previously omitted objects that we did not receive.
+
+test_expect_success 'setup r1 - delete loose blobs' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       for id in `cat expected | sed "s|..|&/|"`
+       do
+               rm r1/.git/objects/$id
+       done
+'
+
+test_expect_success 'verify pack-objects fails w/ missing objects' '
+       test_must_fail git -C r1 pack-objects --rev --stdout >miss.pack <<-EOF
+       HEAD
+       EOF
+'
+
+test_expect_success 'verify pack-objects fails w/ --missing=error' '
+       test_must_fail git -C r1 pack-objects --rev --stdout --missing=error >miss.pack <<-EOF
+       HEAD
+       EOF
+'
+
+test_expect_success 'verify pack-objects w/ --missing=allow-any' '
+       git -C r1 pack-objects --rev --stdout --missing=allow-any >miss.pack <<-EOF
+       HEAD
+       EOF
+'
+
+test_done
index ded8f98dbed9764744799f7b4bed0c51fc57a158..c19d8dbc9d916463d51dc0903185cb50a6cb5af7 100755 (executable)
@@ -165,4 +165,49 @@ test_expect_success 'git pull --allow-unrelated-histories' '
        )
 '
 
+test_expect_success 'git pull does not add a sign-off line' '
+       test_when_finished "rm -fr src dst actual" &&
+       git init src &&
+       test_commit -C src one &&
+       git clone src dst &&
+       test_commit -C src two &&
+       git -C dst pull --no-ff &&
+       git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+       test_must_be_empty actual
+'
+
+test_expect_success 'git pull --no-signoff does not add sign-off line' '
+       test_when_finished "rm -fr src dst actual" &&
+       git init src &&
+       test_commit -C src one &&
+       git clone src dst &&
+       test_commit -C src two &&
+       git -C dst pull --no-signoff --no-ff &&
+       git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+       test_must_be_empty actual
+'
+
+test_expect_success 'git pull --signoff add a sign-off line' '
+       test_when_finished "rm -fr src dst expected actual" &&
+       echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
+       git init src &&
+       test_commit -C src one &&
+       git clone src dst &&
+       test_commit -C src two &&
+       git -C dst pull --signoff --no-ff &&
+       git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'git pull --no-signoff flag cancels --signoff flag' '
+       test_when_finished "rm -fr src dst actual" &&
+       git init src &&
+       test_commit -C src one &&
+       git clone src dst &&
+       test_commit -C src two &&
+       git -C dst pull --signoff --no-signoff --no-ff &&
+       git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+       test_must_be_empty actual
+'
+
 test_done
index 42251f7f3af0865a31fd38722fbf7433d81fe3f0..a552ad4ead899fbb431c68bef558811d25cd8c63 100755 (executable)
@@ -478,7 +478,47 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea
                git fetch >../actual.out 2>../actual.err
        ) &&
        ! test -s actual.out &&
-       test_i18ncmp expect.err actual.err
+       test_i18ncmp expect.err actual.err &&
+       (
+               cd submodule &&
+               git checkout -q master
+       )
+'
+
+test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .gitmodule entry" '
+       (
+               cd downstream &&
+               git fetch --recurse-submodules
+       ) &&
+       add_upstream_commit &&
+       head1=$(git rev-parse --short HEAD) &&
+       git add submodule &&
+       git rm .gitmodules &&
+       git commit -m "new submodule without .gitmodules" &&
+       printf "" >expect.out &&
+       head2=$(git rev-parse --short HEAD) &&
+       echo "From $pwd/." >expect.err.2 &&
+       echo "   $head1..$head2  master     -> origin/master" >>expect.err.2 &&
+       head -3 expect.err >>expect.err.2 &&
+       (
+               cd downstream &&
+               rm .gitmodules &&
+               git config fetch.recurseSubmodules on-demand &&
+               # fake submodule configuration to avoid skipping submodule handling
+               git config -f .gitmodules submodule.fake.path fake &&
+               git config -f .gitmodules submodule.fake.url fakeurl &&
+               git add .gitmodules &&
+               git config --unset submodule.submodule.url &&
+               git fetch >../actual.out 2>../actual.err &&
+               # cleanup
+               git config --unset fetch.recurseSubmodules &&
+               git reset --hard
+       ) &&
+       test_i18ncmp expect.out actual.out &&
+       test_i18ncmp expect.err.2 actual.err &&
+       git checkout HEAD^ -- .gitmodules &&
+       git add .gitmodules &&
+       git commit -m "new submodule restored .gitmodules"
 '
 
 test_expect_success 'fetching submodules respects parallel settings' '
@@ -530,4 +570,39 @@ test_expect_success 'fetching submodule into a broken repository' '
        test_must_fail git -C dst fetch --recurse-submodules
 '
 
+test_expect_success "fetch new commits when submodule got renamed" '
+       git clone . downstream_rename &&
+       (
+               cd downstream_rename &&
+               git submodule update --init &&
+# NEEDSWORK: we omitted --recursive for the submodule update here since
+# that does not work. See test 7001 for mv "moving nested submodules"
+# for details. Once that is fixed we should add the --recursive option
+# here.
+               git checkout -b rename &&
+               git mv submodule submodule_renamed &&
+               (
+                       cd submodule_renamed &&
+                       git checkout -b rename_sub &&
+                       echo a >a &&
+                       git add a &&
+                       git commit -ma &&
+                       git push origin rename_sub &&
+                       git rev-parse HEAD >../../expect
+               ) &&
+               git add submodule_renamed &&
+               git commit -m "update renamed submodule" &&
+               git push origin rename
+       ) &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules=on-demand &&
+               (
+                       cd submodule &&
+                       git rev-parse origin/rename_sub >../../actual
+               )
+       ) &&
+       test_cmp expect actual
+'
+
 test_done
index 90a4b0d2fe9d8a1d9963bea54d2d1198a24003c2..463783789c8ccda6a483197f6626afbfca7d8ccf 100755 (executable)
@@ -140,6 +140,83 @@ test_expect_success 'push options and submodules' '
        test_cmp expect parent_upstream/.git/hooks/post-receive.push_options
 '
 
+test_expect_success 'default push option' '
+       mk_repo_pair &&
+       git -C upstream config receive.advertisePushOptions true &&
+       (
+               cd workbench &&
+               test_commit one &&
+               git push --mirror up &&
+               test_commit two &&
+               git -c push.pushOption=default push up master
+       ) &&
+       test_refs master master &&
+       echo "default" >expect &&
+       test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+       test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'two default push options' '
+       mk_repo_pair &&
+       git -C upstream config receive.advertisePushOptions true &&
+       (
+               cd workbench &&
+               test_commit one &&
+               git push --mirror up &&
+               test_commit two &&
+               git -c push.pushOption=default1 -c push.pushOption=default2 push up master
+       ) &&
+       test_refs master master &&
+       printf "default1\ndefault2\n" >expect &&
+       test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+       test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'push option from command line overrides from-config push option' '
+       mk_repo_pair &&
+       git -C upstream config receive.advertisePushOptions true &&
+       (
+               cd workbench &&
+               test_commit one &&
+               git push --mirror up &&
+               test_commit two &&
+               git -c push.pushOption=default push --push-option=manual up master
+       ) &&
+       test_refs master master &&
+       echo "manual" >expect &&
+       test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+       test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'empty value of push.pushOption in config clears the list' '
+       mk_repo_pair &&
+       git -C upstream config receive.advertisePushOptions true &&
+       (
+               cd workbench &&
+               test_commit one &&
+               git push --mirror up &&
+               test_commit two &&
+               git -c push.pushOption=default1 -c push.pushOption= -c push.pushOption=default2 push up master
+       ) &&
+       test_refs master master &&
+       echo "default2" >expect &&
+       test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+       test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'invalid push option in config' '
+       mk_repo_pair &&
+       git -C upstream config receive.advertisePushOptions true &&
+       (
+               cd workbench &&
+               test_commit one &&
+               git push --mirror up &&
+               test_commit two &&
+               test_must_fail git -c push.pushOption push up master
+       ) &&
+       test_refs master HEAD@{1}
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh
new file mode 100755 (executable)
index 0000000..9594e89
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='pull signature verification tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create repositories with signed commits' '
+       echo 1 >a && git add a &&
+       test_tick && git commit -m initial &&
+       git tag initial &&
+
+       git clone . signed &&
+       (
+               cd signed &&
+               echo 2 >b && git add b &&
+               test_tick && git commit -S -m "signed"
+       ) &&
+
+       git clone . unsigned &&
+       (
+               cd unsigned &&
+               echo 3 >c && git add c &&
+               test_tick && git commit -m "unsigned"
+       ) &&
+
+       git clone . bad &&
+       (
+               cd bad &&
+               echo 4 >d && git add d &&
+               test_tick && git commit -S -m "bad" &&
+               git cat-file commit HEAD >raw &&
+               sed -e "s/bad/forged bad/" raw >forged &&
+               git hash-object -w -t commit forged >forged.commit &&
+               git checkout $(cat forged.commit)
+       ) &&
+
+       git clone . untrusted &&
+       (
+               cd untrusted &&
+               echo 5 >e && git add e &&
+               test_tick && git commit -SB7227189 -m "untrusted"
+       )
+'
+
+test_expect_success GPG 'pull unsigned commit with --verify-signatures' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_must_fail git pull --ff-only --verify-signatures unsigned 2>pullerror &&
+       test_i18ngrep "does not have a GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit with bad signature with --verify-signatures' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_must_fail git pull --ff-only --verify-signatures bad 2>pullerror &&
+       test_i18ngrep "has a bad GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror &&
+       test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull signed commit with --verify-signatures' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       git pull --verify-signatures signed >pulloutput &&
+       test_i18ngrep "has a good GPG signature" pulloutput
+'
+
+test_expect_success GPG 'pull commit with bad signature without verification' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       git pull --ff-only bad 2>pullerror
+'
+
+test_expect_success GPG 'pull commit with bad signature with --no-verify-signatures' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       test_config pull.verifySignatures true &&
+       git pull --ff-only --no-verify-signatures bad 2>pullerror
+'
+
+test_done
index 50e40abb11e1d317a2a58321f1007dde925c8da2..0f895478f02bcf4cc4408ce065e79e0314b66793 100755 (executable)
@@ -306,23 +306,21 @@ test_expect_success 'clone checking out a tag' '
        test_cmp fetch.expected fetch.actual
 '
 
-setup_ssh_wrapper () {
-       test_expect_success 'setup ssh wrapper' '
-               rm -f "$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
-                       "$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               export GIT_SSH &&
-               export TRASH_DIRECTORY &&
-               >"$TRASH_DIRECTORY"/ssh-output
-       '
-}
+test_expect_success 'set up ssh wrapper' '
+       cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
+               "$TRASH_DIRECTORY/ssh$X" &&
+       GIT_SSH="$TRASH_DIRECTORY/ssh$X" &&
+       export GIT_SSH &&
+       export TRASH_DIRECTORY &&
+       >"$TRASH_DIRECTORY"/ssh-output
+'
 
 copy_ssh_wrapper_as () {
        rm -f "${1%$X}$X" &&
-       cp "$TRASH_DIRECTORY/ssh-wrapper$X" "${1%$X}$X" &&
+       cp "$TRASH_DIRECTORY/ssh$X" "${1%$X}$X" &&
+       test_when_finished "rm $(git rev-parse --sq-quote "${1%$X}$X")" &&
        GIT_SSH="${1%$X}$X" &&
-       export GIT_SSH
+       test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\""
 }
 
 expect_ssh () {
@@ -346,8 +344,6 @@ expect_ssh () {
        (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
 }
 
-setup_ssh_wrapper
-
 test_expect_success 'clone myhost:src uses ssh' '
        git clone myhost:src ssh-clone &&
        expect_ssh myhost src
@@ -364,9 +360,52 @@ test_expect_success 'bracketed hostnames are still ssh' '
        expect_ssh "-p 123" myhost src
 '
 
-test_expect_success 'uplink is not treated as putty' '
+test_expect_success 'OpenSSH variant passes -4' '
+       git clone -4 "[myhost:123]:src" ssh-ipv4-clone &&
+       expect_ssh "-4 -p 123" myhost src
+'
+
+test_expect_success 'variant can be overridden' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/putty" &&
+       git -c ssh.variant=putty clone -4 "[myhost:123]:src" ssh-putty-clone &&
+       expect_ssh "-4 -P 123" myhost src
+'
+
+test_expect_success 'variant=auto picks based on basename' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
+       git -c ssh.variant=auto clone -4 "[myhost:123]:src" ssh-auto-clone &&
+       expect_ssh "-4 -P 123" myhost src
+'
+
+test_expect_success 'simple does not support -4/-6' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
+       test_must_fail git clone -4 "myhost:src" ssh-4-clone-simple
+'
+
+test_expect_success 'simple does not support port' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
+       test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-simple
+'
+
+test_expect_success 'uplink is treated as simple' '
        copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" &&
-       git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+       test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+       git clone "myhost:src" ssh-clone-uplink &&
+       expect_ssh myhost src
+'
+
+test_expect_success 'OpenSSH-like uplink is treated as ssh' '
+       write_script "$TRASH_DIRECTORY/uplink" <<-EOF &&
+       if test "\$1" = "-G"
+       then
+               exit 0
+       fi &&
+       exec "\$TRASH_DIRECTORY/ssh$X" "\$@"
+       EOF
+       test_when_finished "rm -f \"\$TRASH_DIRECTORY/uplink\"" &&
+       GIT_SSH="$TRASH_DIRECTORY/uplink" &&
+       test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" &&
+       git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink &&
        expect_ssh "-p 123" myhost src
 '
 
@@ -418,12 +457,14 @@ test_expect_success 'ssh.variant overrides plink detection' '
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=plink \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 &&
        expect_ssh "-P 123" myhost src
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=tortoiseplink \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 &&
        expect_ssh "-batch -P 123" myhost src
@@ -435,9 +476,6 @@ test_expect_success 'clean failure on broken quoting' '
                git clone "[myhost:123]:src" sq-failure
 '
 
-# Reset the GIT_SSH environment variable for clone tests.
-setup_ssh_wrapper
-
 counter=0
 # $1 url
 # $2 none|host
index d5af7581297d6167b82d029a015e367c057ff7af..13b5e5eb9b9fa2aa96e0496eb9fa3e7049fdfb51 100755 (executable)
@@ -11,7 +11,9 @@ test_expect_success 'setup ssh wrapper' '
        git upload-pack "$TRASH_DIRECTORY"
        EOF
        GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" &&
+       GIT_SSH_VARIANT=ssh &&
        export GIT_SSH &&
+       export GIT_SSH_VARIANT &&
        export TRASH_DIRECTORY
 '
 
index d2d883f3a1d473a2b69fab05adbb21e67e5e600c..b4905b822c07048ddfbc43cfd39265bd2116f197 100755 (executable)
@@ -7,9 +7,9 @@ check_obj () {
        alt=$1; shift
        while read obj expect
        do
-               echo "$obj" >&3 &&
-               echo "$obj $expect" >&4
-       done 3>input 4>expect &&
+               echo "$obj" >&5 &&
+               echo "$obj $expect" >&6
+       done 5>input 6>expect &&
        GIT_ALTERNATE_OBJECT_DIRECTORIES=$alt \
                git "$@" cat-file --batch-check='%(objectname) %(objecttype)' \
                <input >actual &&
diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh
new file mode 100755 (executable)
index 0000000..ba86a44
--- /dev/null
@@ -0,0 +1,294 @@
+#!/bin/sh
+
+test_description='test git wire-protocol transition'
+
+TEST_NO_CREATE_REPO=1
+
+. ./test-lib.sh
+
+# Test protocol v1 with 'git://' transport
+#
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon --export-all --enable=receive-pack
+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
+
+test_expect_success 'create repo to be served by git-daemon' '
+       git init "$daemon_parent" &&
+       test_commit -C "$daemon_parent" one
+'
+
+test_expect_success 'clone with git:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "$GIT_DAEMON_URL/parent" daemon_child 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "clone> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with git:// using protocol v1' '
+       test_commit -C "$daemon_parent" two &&
+
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C daemon_child log -1 --format=%s origin/master >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with git:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with git:// using protocol v1' '
+       test_commit -C daemon_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "push> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+stop_git_daemon
+
+# Test protocol v1 with 'file://' transport
+#
+test_expect_success 'create repo to be served by file:// transport' '
+       git init file_parent &&
+       test_commit -C file_parent one
+'
+
+test_expect_success 'clone with file:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "file://$(pwd)/file_parent" file_child 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with file:// using protocol v1' '
+       test_commit -C file_parent two &&
+
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C file_child log -1 --format=%s origin/master >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with file:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with file:// using protocol v1' '
+       test_commit -C file_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+# Test protocol v1 with 'ssh://' transport
+#
+test_expect_success 'setup ssh wrapper' '
+       GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" &&
+       export GIT_SSH &&
+       GIT_SSH_VARIANT=ssh &&
+       export GIT_SSH_VARIANT &&
+       export TRASH_DIRECTORY &&
+       >"$TRASH_DIRECTORY"/ssh-output
+'
+
+expect_ssh () {
+       test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' &&
+       echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" &&
+       (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
+}
+
+test_expect_success 'create repo to be served by ssh:// transport' '
+       git init ssh_parent &&
+       test_commit -C ssh_parent one
+'
+
+test_expect_success 'clone with ssh:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with ssh:// using protocol v1' '
+       test_commit -C ssh_parent two &&
+
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               fetch 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s origin/master >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with ssh:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               pull 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with ssh:// using protocol v1' '
+       test_commit -C ssh_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+       expect_ssh git-receive-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+# Test protocol v1 with 'http://' transport
+#
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'create repo to be served by http:// transport' '
+       git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one
+'
+
+test_expect_success 'clone with http:// using protocol v1' '
+       GIT_TRACE_PACKET=1 GIT_TRACE_CURL=1 git -c protocol.version=1 \
+               clone "$HTTPD_URL/smart/http_parent" http_child 2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "Git-Protocol: version=1" log &&
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'fetch with http:// using protocol v1' '
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two &&
+
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C http_child log -1 --format=%s origin/master >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'pull with http:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'push with http:// using protocol v1' '
+       test_commit -C http_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               push origin HEAD:client_branch && #2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+stop_httpd
+
+test_done
index 8c2c6eaef83fe90d1faa088fe65ee4a851b1e471..f84ff941c3624be821fd87a67922ad1d2ca00b34 100755 (executable)
@@ -894,4 +894,21 @@ test_expect_success 'bisect start takes options and revs in any order' '
        test_cmp expected actual
 '
 
+test_expect_success 'git bisect reset cleans bisection state properly' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect good $HASH1 &&
+       git bisect bad $HASH4 &&
+       git bisect reset &&
+       test -z "$(git for-each-ref "refs/bisect/*")" &&
+       test_path_is_missing "$GIT_DIR/BISECT_EXPECTED_REV" &&
+       test_path_is_missing "$GIT_DIR/BISECT_ANCESTORS_OK" &&
+       test_path_is_missing "$GIT_DIR/BISECT_LOG" &&
+       test_path_is_missing "$GIT_DIR/BISECT_RUN" &&
+       test_path_is_missing "$GIT_DIR/BISECT_TERMS" &&
+       test_path_is_missing "$GIT_DIR/head-name" &&
+       test_path_is_missing "$GIT_DIR/BISECT_HEAD" &&
+       test_path_is_missing "$GIT_DIR/BISECT_START"
+'
+
 test_done
diff --git a/t/t6100-rev-list-in-order.sh b/t/t6100-rev-list-in-order.sh
new file mode 100755 (executable)
index 0000000..b2bb0a7
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='rev-list testing in-commit-order'
+
+. ./test-lib.sh
+
+test_expect_success 'setup a commit history with trees, blobs' '
+       for x in one two three four
+       do
+               echo $x >$x &&
+               git add $x &&
+               git commit -m "add file $x" ||
+               return 1
+       done &&
+       for x in four three
+       do
+               git rm $x &&
+               git commit -m "remove $x" ||
+               return 1
+       done
+'
+
+test_expect_success 'rev-list --in-commit-order' '
+       git rev-list --in-commit-order --objects HEAD >actual.raw &&
+       cut -c 1-40 >actual <actual.raw &&
+
+       git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF &&
+               HEAD^{commit}
+               HEAD^{tree}
+               HEAD^{tree}:one
+               HEAD^{tree}:two
+               HEAD~1^{commit}
+               HEAD~1^{tree}
+               HEAD~1^{tree}:three
+               HEAD~2^{commit}
+               HEAD~2^{tree}
+               HEAD~2^{tree}:four
+               HEAD~3^{commit}
+               # HEAD~3^{tree} skipped, same as HEAD~1^{tree}
+               HEAD~4^{commit}
+               # HEAD~4^{tree} skipped, same as HEAD^{tree}
+               HEAD~5^{commit}
+               HEAD~5^{tree}
+       EOF
+       grep -v "#" >expect <expect.raw &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'rev-list lists blobs and trees after commits' '
+       git rev-list --objects HEAD >actual.raw &&
+       cut -c 1-40 >actual <actual.raw &&
+
+       git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF &&
+               HEAD^{commit}
+               HEAD~1^{commit}
+               HEAD~2^{commit}
+               HEAD~3^{commit}
+               HEAD~4^{commit}
+               HEAD~5^{commit}
+               HEAD^{tree}
+               HEAD^{tree}:one
+               HEAD^{tree}:two
+               HEAD~1^{tree}
+               HEAD~1^{tree}:three
+               HEAD~2^{tree}
+               HEAD~2^{tree}:four
+               # HEAD~3^{tree} skipped, same as HEAD~1^{tree}
+               # HEAD~4^{tree} skipped, same as HEAD^{tree}
+               HEAD~5^{tree}
+       EOF
+       grep -v "#" >expect <expect.raw &&
+
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh
new file mode 100755 (executable)
index 0000000..0a37dd5
--- /dev/null
@@ -0,0 +1,225 @@
+#!/bin/sh
+
+test_description='git rev-list using object filtering'
+
+. ./test-lib.sh
+
+# Test the blob:none filter.
+
+test_expect_success 'setup r1' '
+       echo "{print \$1}" >print_1.awk &&
+       echo "{print \$2}" >print_2.awk &&
+
+       git init r1 &&
+       for n in 1 2 3 4 5
+       do
+               echo "This is file: $n" > r1/file.$n
+               git -C r1 add file.$n
+               git -C r1 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob:none omits all 5 blobs' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r1 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:none \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify emitted+omitted == all' '
+       git -C r1 rev-list HEAD --objects \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r1 rev-list HEAD --objects --filter-print-omitted --filter=blob:none \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+
+# Test blob:limit=<n>[kmg] filter.
+# We boundary test around the size parameter.  The filter is strictly less than
+# the value, so size 500 and 1000 should have the same results, but 1001 should
+# filter more.
+
+test_expect_success 'setup r2' '
+       git init r2 &&
+       for n in 1000 10000
+       do
+               printf "%"$n"s" X > r2/large.$n
+               git -C r2 add large.$n
+               git -C r2 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob:limit=500 omits all blobs' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=500 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify emitted+omitted == all' '
+       git -C r2 rev-list HEAD --objects \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --objects --filter-print-omitted --filter=blob:limit=500 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1000' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1000 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1001' '
+       git -C r2 ls-files -s large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1001 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1k' '
+       git -C r2 ls-files -s large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1k \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1m' '
+       cat </dev/null >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:path=<path> filter.
+# Use a local file containing a sparse-checkout specification to filter
+# out blobs not required for the corresponding sparse-checkout.  We do not
+# require sparse-checkout to actually be enabled.
+
+test_expect_success 'setup r3' '
+       git init r3 &&
+       mkdir r3/dir1 &&
+       for n in sparse1 sparse2
+       do
+               echo "This is file: $n" > r3/$n
+               git -C r3 add $n
+               echo "This is file: dir1/$n" > r3/dir1/$n
+               git -C r3 add dir1/$n
+       done &&
+       git -C r3 commit -m "sparse" &&
+       echo dir1/ >pattern1 &&
+       echo sparse1 >pattern2
+'
+
+test_expect_success 'verify sparse:path=pattern1 omits top-level files' '
+       git -C r3 ls-files -s sparse1 sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern1 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:path=pattern2 omits both sparse2 files' '
+       git -C r3 ls-files -s sparse2 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern2 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:oid=<oid-ish> filter.
+# Like sparse:path, but we get the sparse-checkout specification from
+# a blob rather than a file on disk.
+
+test_expect_success 'setup r3 part 2' '
+       echo dir1/ >r3/pattern &&
+       git -C r3 add pattern &&
+       git -C r3 commit -m "pattern"
+'
+
+test_expect_success 'verify sparse:oid=OID omits top-level files' '
+       git -C r3 ls-files -s pattern sparse1 sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       oid=$(git -C r3 ls-files -s pattern | awk -f print_2.awk) &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=$oid \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:oid=oid-ish omits top-level files' '
+       git -C r3 ls-files -s pattern sparse1 sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=master:pattern \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Delete some loose objects and use rev-list, but WITHOUT any filtering.
+# This models previously omitted objects that we did not receive.
+
+test_expect_success 'rev-list W/ --missing=print' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       for id in `cat expected | sed "s|..|&/|"`
+       do
+               rm r1/.git/objects/$id
+       done &&
+       git -C r1 rev-list --quiet HEAD --missing=print --objects \
+               | awk -f print_1.awk \
+               | sed "s/?//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'rev-list W/O --missing fails' '
+       test_must_fail git -C r1 rev-list --quiet --objects HEAD
+'
+
+test_expect_success 'rev-list W/ missing=allow-any' '
+       git -C r1 rev-list --quiet --missing=allow-any --objects HEAD
+'
+
+test_done
index 1c0e8659d97c0792ce5761cf0704aa7f061575e0..3e3fb462a098c01c72270acbb276abd2df225a66 100755 (executable)
@@ -304,12 +304,46 @@ test_expect_success 'describe chokes on severely broken submodules' '
        mv .git/modules/sub1/ .git/modules/sub_moved &&
        test_must_fail git describe --dirty
 '
-test_expect_success 'describe ignoring a borken submodule' '
+test_expect_success 'describe ignoring a broken submodule' '
        git describe --broken >out &&
        test_when_finished "mv .git/modules/sub_moved .git/modules/sub1" &&
        grep broken out
 '
 
+test_expect_success 'describe a blob at a directly tagged commit' '
+       echo "make it a unique blob" >file &&
+       git add file && git commit -m "content in file" &&
+       git tag -a -m "latest annotated tag" unique-file &&
+       git describe HEAD:file >actual &&
+       echo "unique-file:file" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'describe a blob with its first introduction' '
+       git commit --allow-empty -m "empty commit" &&
+       git rm file &&
+       git commit -m "delete blob" &&
+       git revert HEAD &&
+       git commit --allow-empty -m "empty commit" &&
+       git describe HEAD:file >actual &&
+       echo "unique-file:file" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'describe directly tagged blob' '
+       git tag test-blob unique-file:file &&
+       git describe test-blob >actual &&
+       echo "unique-file:file" >expect &&
+       # suboptimal: we rather want to see "test-blob"
+       test_cmp expect actual
+'
+
+test_expect_success 'describe tag object' '
+       git tag test-blob-1 -a -m msg unique-file:file &&
+       test_must_fail git describe test-blob-1 2>actual &&
+       test_i18ngrep "fatal: test-blob-1 is neither a commit nor blob" actual
+'
+
 test_expect_failure ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
        i=1 &&
        while test $i -lt 8000
index 3aa534933e08f85ea1c215e5ef6cc6c7f4a7cd31..c128dfc5790790de9edf1b4d2cfa8b028c1036bc 100755 (executable)
@@ -766,4 +766,36 @@ test_expect_success 'Verify usage of %(symref:rstrip) atom' '
        test_cmp expected actual
 '
 
+test_expect_success ':remotename and :remoteref' '
+       git init remote-tests &&
+       (
+               cd remote-tests &&
+               test_commit initial &&
+               git remote add from fifth.coffee:blub &&
+               git config branch.master.remote from &&
+               git config branch.master.merge refs/heads/stable &&
+               git remote add to southridge.audio:repo &&
+               git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+               git config branch.master.pushRemote to &&
+               for pair in "%(upstream)=refs/remotes/from/stable" \
+                       "%(upstream:remotename)=from" \
+                       "%(upstream:remoteref)=refs/heads/stable" \
+                       "%(push)=refs/remotes/to/pushed/master" \
+                       "%(push:remotename)=to" \
+                       "%(push:remoteref)=refs/heads/pushed/master"
+               do
+                       echo "${pair#*=}" >expect &&
+                       git for-each-ref --format="${pair%=*}" \
+                               refs/heads/master >actual &&
+                       test_cmp expect actual
+               done &&
+               git branch push-simple &&
+               git config branch.push-simple.pushRemote from &&
+               actual="$(git for-each-ref \
+                       --format="%(push:remotename),%(push:remoteref)" \
+                       refs/heads/push-simple)" &&
+               test from, = "$actual"
+       )
+'
+
 test_done
index 865168ec6a7be7fc7778419f2dd1066e9c80c1bc..f5f46a95b4be9bca4ca30e70cbe75051a191c96a 100755 (executable)
@@ -214,6 +214,44 @@ test_expect_success TTY 'git tag as alias respects pager.tag with -l' '
        ! test -e paginated.out
 '
 
+test_expect_success TTY 'git branch defaults to paging' '
+       rm -f paginated.out &&
+       test_terminal git branch &&
+       test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects pager.branch' '
+       rm -f paginated.out &&
+       test_terminal git -c pager.branch=false branch &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects --no-pager' '
+       rm -f paginated.out &&
+       test_terminal git --no-pager branch &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch --edit-description ignores pager.branch' '
+       rm -f paginated.out editor.used &&
+       write_script editor <<-\EOF &&
+               echo "New description" >"$1"
+               touch editor.used
+       EOF
+       EDITOR=./editor test_terminal git -c pager.branch branch --edit-description &&
+       ! test -e paginated.out &&
+       test -e editor.used
+'
+
+test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' '
+       rm -f paginated.out &&
+       git branch other &&
+       test_when_finished "git branch -D other" &&
+       test_terminal git -c pager.branch branch --set-upstream-to=other &&
+       test_when_finished "git branch --unset-upstream" &&
+       ! test -e paginated.out
+'
+
 # A colored commit log will begin with an appropriate ANSI escape
 # for the first color; the text "commit" comes later.
 colorful() {
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
new file mode 100755 (executable)
index 0000000..eb2d13b
--- /dev/null
@@ -0,0 +1,317 @@
+#!/bin/sh
+
+test_description='git status with file system watcher'
+
+. ./test-lib.sh
+
+#
+# To run the entire git test suite using fsmonitor:
+#
+# copy t/t7519/fsmonitor-all to a location in your path and then set
+# GIT_FSMONITOR_TEST=fsmonitor-all and run your tests.
+#
+
+# Note, after "git reset --hard HEAD" no extensions exist other than 'TREE'
+# "git update-index --fsmonitor" can be used to get the extension written
+# before testing the results.
+
+clean_repo () {
+       git reset --hard HEAD &&
+       git clean -fd
+}
+
+dirty_repo () {
+       : >untracked &&
+       : >dir1/untracked &&
+       : >dir2/untracked &&
+       echo 1 >modified &&
+       echo 2 >dir1/modified &&
+       echo 3 >dir2/modified &&
+       echo 4 >new &&
+       echo 5 >dir1/new &&
+       echo 6 >dir2/new
+}
+
+write_integration_script () {
+       write_script .git/hooks/fsmonitor-test<<-\EOF
+       if test "$#" -ne 2
+       then
+               echo "$0: exactly 2 arguments expected"
+               exit 2
+       fi
+       if test "$1" != 1
+       then
+               echo "Unsupported core.fsmonitor hook version." >&2
+               exit 1
+       fi
+       printf "untracked\0"
+       printf "dir1/untracked\0"
+       printf "dir2/untracked\0"
+       printf "modified\0"
+       printf "dir1/modified\0"
+       printf "dir2/modified\0"
+       printf "new\0"
+       printf "dir1/new\0"
+       printf "dir2/new\0"
+       EOF
+}
+
+test_lazy_prereq UNTRACKED_CACHE '
+       { git update-index --test-untracked-cache; ret=$?; } &&
+       test $ret -ne 1
+'
+
+test_expect_success 'setup' '
+       mkdir -p .git/hooks &&
+       : >tracked &&
+       : >modified &&
+       mkdir dir1 &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
+       mkdir dir2 &&
+       : >dir2/tracked &&
+       : >dir2/modified &&
+       git -c core.fsmonitor= add . &&
+       git -c core.fsmonitor= commit -m initial &&
+       git config core.fsmonitor .git/hooks/fsmonitor-test &&
+       cat >.gitignore <<-\EOF
+       .gitignore
+       expect*
+       actual*
+       marker*
+       EOF
+'
+
+# test that the fsmonitor extension is off by default
+test_expect_success 'fsmonitor extension is off by default' '
+       test-dump-fsmonitor >actual &&
+       grep "^no fsmonitor" actual
+'
+
+# test that "update-index --fsmonitor" adds the fsmonitor extension
+test_expect_success 'update-index --fsmonitor" adds the fsmonitor extension' '
+       git update-index --fsmonitor &&
+       test-dump-fsmonitor >actual &&
+       grep "^fsmonitor last update" actual
+'
+
+# test that "update-index --no-fsmonitor" removes the fsmonitor extension
+test_expect_success 'update-index --no-fsmonitor" removes the fsmonitor extension' '
+       git update-index --no-fsmonitor &&
+       test-dump-fsmonitor >actual &&
+       grep "^no fsmonitor" actual
+'
+
+cat >expect <<EOF &&
+h dir1/modified
+H dir1/tracked
+h dir2/modified
+H dir2/tracked
+h modified
+H tracked
+EOF
+
+# test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
+test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
+       git update-index --fsmonitor &&
+       git update-index --fsmonitor-valid dir1/modified &&
+       git update-index --fsmonitor-valid dir2/modified &&
+       git update-index --fsmonitor-valid modified &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+H dir1/tracked
+H dir2/modified
+H dir2/tracked
+H modified
+H tracked
+EOF
+
+# test that "update-index --no-fsmonitor-valid" clears the fsmonitor valid bit
+test_expect_success 'update-index --no-fsmonitor-valid" clears the fsmonitor valid bit' '
+       git update-index --no-fsmonitor-valid dir1/modified &&
+       git update-index --no-fsmonitor-valid dir2/modified &&
+       git update-index --no-fsmonitor-valid modified &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+H dir1/tracked
+H dir2/modified
+H dir2/tracked
+H modified
+H tracked
+EOF
+
+# test that all files returned by the script get flagged as invalid
+test_expect_success 'all files returned by integration script get flagged as invalid' '
+       write_integration_script &&
+       dirty_repo &&
+       git update-index --fsmonitor &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+h dir1/new
+H dir1/tracked
+H dir2/modified
+h dir2/new
+H dir2/tracked
+H modified
+h new
+H tracked
+EOF
+
+# test that newly added files are marked valid
+test_expect_success 'newly added files are marked valid' '
+       git add new &&
+       git add dir1/new &&
+       git add dir2/new &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+h dir1/new
+h dir1/tracked
+H dir2/modified
+h dir2/new
+h dir2/tracked
+H modified
+h new
+h tracked
+EOF
+
+# test that all unmodified files get marked valid
+test_expect_success 'all unmodified files get marked valid' '
+       # modified files result in update-index returning 1
+       test_must_fail git update-index --refresh --force-write-index &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+h dir1/tracked
+h dir2/modified
+h dir2/tracked
+h modified
+h tracked
+EOF
+
+# test that *only* files returned by the integration script get flagged as invalid
+test_expect_success '*only* files returned by the integration script get flagged as invalid' '
+       write_script .git/hooks/fsmonitor-test<<-\EOF &&
+       printf "dir1/modified\0"
+       EOF
+       clean_repo &&
+       git update-index --refresh --force-write-index &&
+       echo 1 >modified &&
+       echo 2 >dir1/modified &&
+       echo 3 >dir2/modified &&
+       test_must_fail git update-index --refresh --force-write-index &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+# Ensure commands that call refresh_index() to move the index back in time
+# properly invalidate the fsmonitor cache
+test_expect_success 'refresh_index() invalidates fsmonitor cache' '
+       write_script .git/hooks/fsmonitor-test<<-\EOF &&
+       EOF
+       clean_repo &&
+       dirty_repo &&
+       git add . &&
+       git commit -m "to reset" &&
+       git reset HEAD~1 &&
+       git status >actual &&
+       git -c core.fsmonitor= status >expect &&
+       test_i18ncmp expect actual
+'
+
+# test fsmonitor with and without preloadIndex
+preload_values="false true"
+for preload_val in $preload_values
+do
+       test_expect_success "setup preloadIndex to $preload_val" '
+               git config core.preloadIndex $preload_val &&
+               if test $preload_val = true
+               then
+                       GIT_FORCE_PRELOAD_TEST=$preload_val; export GIT_FORCE_PRELOAD_TEST
+               else
+                       unset GIT_FORCE_PRELOAD_TEST
+               fi
+       '
+
+       # test fsmonitor with and without the untracked cache (if available)
+       uc_values="false"
+       test_have_prereq UNTRACKED_CACHE && uc_values="false true"
+       for uc_val in $uc_values
+       do
+               test_expect_success "setup untracked cache to $uc_val" '
+                       git config core.untrackedcache $uc_val
+               '
+
+               # Status is well tested elsewhere so we'll just ensure that the results are
+               # the same when using core.fsmonitor.
+               test_expect_success 'compare status with and without fsmonitor' '
+                       write_integration_script &&
+                       clean_repo &&
+                       dirty_repo &&
+                       git add new &&
+                       git add dir1/new &&
+                       git add dir2/new &&
+                       git status >actual &&
+                       git -c core.fsmonitor= status >expect &&
+                       test_i18ncmp expect actual
+               '
+
+               # Make sure it's actually skipping the check for modified and untracked
+               # (if enabled) files unless it is told about them.
+               test_expect_success "status doesn't detect unreported modifications" '
+                       write_script .git/hooks/fsmonitor-test<<-\EOF &&
+                       :>marker
+                       EOF
+                       clean_repo &&
+                       git status &&
+                       test_path_is_file marker &&
+                       dirty_repo &&
+                       rm -f marker &&
+                       git status >actual &&
+                       test_path_is_file marker &&
+                       test_i18ngrep ! "Changes not staged for commit:" actual &&
+                       if test $uc_val = true
+                       then
+                               test_i18ngrep ! "Untracked files:" actual
+                       fi &&
+                       if test $uc_val = false
+                       then
+                               test_i18ngrep "Untracked files:" actual
+                       fi &&
+                       rm -f marker
+               '
+       done
+done
+
+# test that splitting the index dosn't interfere
+test_expect_success 'splitting the index results in the same state' '
+       write_integration_script &&
+       dirty_repo &&
+       git update-index --fsmonitor  &&
+       git ls-files -f >expect &&
+       test-dump-fsmonitor >&2 && echo &&
+       git update-index --fsmonitor --split-index &&
+       test-dump-fsmonitor >&2 && echo &&
+       git ls-files -f >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t7519/fsmonitor-all b/t/t7519/fsmonitor-all
new file mode 100755 (executable)
index 0000000..691bc94
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+
+if test "$#" -ne 2
+then
+       echo "$0: exactly 2 arguments expected" >&2
+       exit 2
+fi
+
+if test "$1" != 1
+then
+       echo "Unsupported core.fsmonitor hook version." >&2
+       exit 1
+fi
+
+echo "/"
diff --git a/t/t7519/fsmonitor-none b/t/t7519/fsmonitor-none
new file mode 100755 (executable)
index 0000000..ed9cf5a
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+
+if test "$#" -ne 2
+then
+       echo "$0: exactly 2 arguments expected" >&2
+       exit 2
+fi
+
+if test "$1" != 1
+then
+       echo "Unsupported core.fsmonitor hook version." >&2
+       exit 1
+fi
diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman
new file mode 100755 (executable)
index 0000000..5514edc
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $time) = @ARGV;
+#print STDERR "$0 $version $time\n";
+
+# Check the hook interface version
+
+if ($version == 1) {
+       # convert nanoseconds to seconds
+       $time = int $time / 1000000000;
+} else {
+       die "Unsupported query-fsmonitor hook version '$version'.\n" .
+           "Falling back to scanning...\n";
+}
+
+my $git_work_tree;
+if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+       $git_work_tree = Win32::GetCwd();
+       $git_work_tree =~ tr/\\/\//;
+} else {
+       require Cwd;
+       $git_work_tree = Cwd::cwd();
+}
+
+my $retry = 1;
+
+launch_watchman();
+
+sub launch_watchman {
+
+       my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j')
+           or die "open2() failed: $!\n" .
+           "Falling back to scanning...\n";
+
+       # In the query expression below we're asking for names of files that
+       # changed since $time but were not transient (ie created after
+       # $time but no longer exist).
+       #
+       # To accomplish this, we're using the "since" generator to use the
+       # recency index to select candidate nodes and "fields" to limit the
+       # output to file names only. Then we're using the "expression" term to
+       # further constrain the results.
+       #
+       # The category of transient files that we want to ignore will have a
+       # creation clock (cclock) newer than $time_t value and will also not
+       # currently exist.
+
+       my $query = <<" END";
+               ["query", "$git_work_tree", {
+                       "since": $time,
+                       "fields": ["name"],
+                       "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
+               }]
+       END
+       
+       open (my $fh, ">", ".git/watchman-query.json");
+       print $fh $query;
+       close $fh;
+
+       print CHLD_IN $query;
+       close CHLD_IN;
+       my $response = do {local $/; <CHLD_OUT>};
+
+       open ($fh, ">", ".git/watchman-response.json");
+       print $fh $response;
+       close $fh;
+
+       die "Watchman: command returned no output.\n" .
+           "Falling back to scanning...\n" if $response eq "";
+       die "Watchman: command returned invalid output: $response\n" .
+           "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+       my $json_pkg;
+       eval {
+               require JSON::XS;
+               $json_pkg = "JSON::XS";
+               1;
+       } or do {
+               require JSON::PP;
+               $json_pkg = "JSON::PP";
+       };
+
+       my $o = $json_pkg->new->utf8->decode($response);
+
+       if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
+               print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
+               $retry--;
+               qx/watchman watch "$git_work_tree"/;
+               die "Failed to make watchman watch '$git_work_tree'.\n" .
+                   "Falling back to scanning...\n" if $? != 0;
+
+               # Watchman will always return all files on the first query so
+               # return the fast "everything is dirty" flag to git and do the
+               # Watchman query just to get it over with now so we won't pay
+               # the cost in git to look up each individual file.
+
+               open ($fh, ">", ".git/watchman-output.out");
+               print "/\0";
+               close $fh;
+
+               print "/\0";
+               eval { launch_watchman() };
+               exit 0;
+       }
+
+       die "Watchman: $o->{error}.\n" .
+           "Falling back to scanning...\n" if $o->{error};
+
+       open ($fh, ">", ".git/watchman-output.out");
+       binmode $fh, ":utf8";
+       print $fh @{$o->{files}};
+       close $fh;
+
+       binmode STDOUT, ":utf8";
+       local $, = "\0";
+       print @{$o->{files}};
+}
diff --git a/t/t7520-ignored-hook-warning.sh b/t/t7520-ignored-hook-warning.sh
new file mode 100755 (executable)
index 0000000..634fb7f
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='ignored hook warning'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       hookdir="$(git rev-parse --git-dir)/hooks" &&
+       hook="$hookdir/pre-commit" &&
+       mkdir -p "$hookdir" &&
+       write_script "$hook" <<-\EOF
+       exit 0
+       EOF
+'
+
+test_expect_success 'no warning if hook is not ignored' '
+       git commit --allow-empty -m "more" 2>message &&
+       test_i18ngrep ! -e "hook was ignored" message
+'
+
+test_expect_success POSIXPERM 'warning if hook is ignored' '
+       chmod -x "$hook" &&
+       git commit --allow-empty -m "even more" 2>message &&
+       test_i18ngrep -e "hook was ignored" message
+'
+
+test_expect_success POSIXPERM 'no warning if advice.ignoredHook set to false' '
+       test_config advice.ignoredHook false &&
+       chmod -x "$hook" &&
+       git commit --allow-empty -m "even more" 2>message &&
+       test_i18ngrep ! -e "hook was ignored" message
+'
+
+test_expect_success 'no warning if unset advice.ignoredHook and hook removed' '
+       rm -f "$hook" &&
+       test_unconfig advice.ignoredHook &&
+       git commit --allow-empty -m "even more" 2>message &&
+       test_i18ngrep ! -e "hook was ignored" message
+'
+
+test_done
diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh
new file mode 100755 (executable)
index 0000000..9179094
--- /dev/null
@@ -0,0 +1,233 @@
+#!/bin/sh
+
+test_description='git status ignored modes'
+
+. ./test-lib.sh
+
+test_expect_success 'setup initial commit and ignore file' '
+       cat >.gitignore <<-\EOF &&
+       *.ign
+       ignored_dir/
+       !*.unignore
+       EOF
+       git add . &&
+       git commit -m "Initial commit"
+'
+
+test_expect_success 'Verify behavior of status on directories with ignored files' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! dir/ignored/ignored_1.ign
+       ! dir/ignored/ignored_2.ign
+       ! ignored/ignored_1.ign
+       ! ignored/ignored_2.ign
+       EOF
+
+       mkdir -p ignored dir/ignored &&
+       touch ignored/ignored_1.ign ignored/ignored_2.ign \
+               dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+       git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on directory with tracked & ignored files' '
+       test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! dir/tracked_ignored/ignored_1.ign
+       ! dir/tracked_ignored/ignored_2.ign
+       ! tracked_ignored/ignored_1.ign
+       ! tracked_ignored/ignored_2.ign
+       EOF
+
+       mkdir -p tracked_ignored dir/tracked_ignored &&
+       touch tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
+               tracked_ignored/ignored_1.ign tracked_ignored/ignored_2.ign \
+               dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 \
+               dir/tracked_ignored/ignored_1.ign dir/tracked_ignored/ignored_2.ign &&
+
+       git add tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
+               dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 &&
+       git commit -m "commit tracked files" &&
+
+       git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on directory with untracked and ignored files' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? dir/untracked_ignored/untracked_1
+       ? dir/untracked_ignored/untracked_2
+       ? expect
+       ? output
+       ? untracked_ignored/untracked_1
+       ? untracked_ignored/untracked_2
+       ! dir/untracked_ignored/ignored_1.ign
+       ! dir/untracked_ignored/ignored_2.ign
+       ! untracked_ignored/ignored_1.ign
+       ! untracked_ignored/ignored_2.ign
+       EOF
+
+       mkdir -p untracked_ignored dir/untracked_ignored &&
+       touch untracked_ignored/untracked_1 untracked_ignored/untracked_2 \
+               untracked_ignored/ignored_1.ign untracked_ignored/ignored_2.ign \
+               dir/untracked_ignored/untracked_1 dir/untracked_ignored/untracked_2 \
+               dir/untracked_ignored/ignored_1.ign dir/untracked_ignored/ignored_2.ign &&
+
+       git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status matching ignored files on ignored directory' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! ignored_dir/
+       EOF
+
+       mkdir ignored_dir &&
+       touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+               ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign &&
+
+       git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on ignored directory containing tracked file' '
+       test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! ignored_dir/ignored_1
+       ! ignored_dir/ignored_1.ign
+       ! ignored_dir/ignored_2
+       ! ignored_dir/ignored_2.ign
+       EOF
+
+       mkdir ignored_dir &&
+       touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+               ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
+               ignored_dir/tracked &&
+       git add -f ignored_dir/tracked &&
+       git commit -m "Force add file in ignored directory" &&
+       git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify matching ignored files with --untracked-files=normal' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ? untracked_dir/
+       ! ignored_dir/
+       ! ignored_files/ignored_1.ign
+       ! ignored_files/ignored_2.ign
+       EOF
+
+       mkdir ignored_dir ignored_files untracked_dir &&
+       touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+               ignored_files/ignored_1.ign ignored_files/ignored_2.ign \
+               untracked_dir/untracked &&
+       git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify matching ignored files with --untracked-files=normal' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ? untracked_dir/
+       ! ignored_dir/
+       ! ignored_files/ignored_1.ign
+       ! ignored_files/ignored_2.ign
+       EOF
+
+       mkdir ignored_dir ignored_files untracked_dir &&
+       touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+               ignored_files/ignored_1.ign ignored_files/ignored_2.ign \
+               untracked_dir/untracked &&
+       git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on ignored directory containing tracked file' '
+       test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! ignored_dir/ignored_1
+       ! ignored_dir/ignored_1.ign
+       ! ignored_dir/ignored_2
+       ! ignored_dir/ignored_2.ign
+       EOF
+
+       mkdir ignored_dir &&
+       touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+               ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
+               ignored_dir/tracked &&
+       git add -f ignored_dir/tracked &&
+       git commit -m "Force add file in ignored directory" &&
+       git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify behavior of status with --ignored=no' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       EOF
+
+       mkdir -p ignored dir/ignored &&
+       touch ignored/ignored_1.ign ignored/ignored_2.ign \
+               dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+       git status --porcelain=v2 --ignored=no --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=all' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! dir/ignored/ignored_1.ign
+       ! dir/ignored/ignored_2.ign
+       ! ignored/ignored_1.ign
+       ! ignored/ignored_2.ign
+       EOF
+
+       mkdir -p ignored dir/ignored &&
+       touch ignored/ignored_1.ign ignored/ignored_2.ign \
+               dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+       git status --porcelain=v2 --ignored=traditional --untracked-files=all >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=normal' '
+       test_when_finished "git clean -fdx" &&
+       cat >expect <<-\EOF &&
+       ? expect
+       ? output
+       ! dir/
+       ! ignored/
+       EOF
+
+       mkdir -p ignored dir/ignored &&
+       touch ignored/ignored_1.ign ignored/ignored_2.ign \
+               dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+       git status --porcelain=v2 --ignored=traditional --untracked-files=normal >output &&
+       test_i18ncmp expect output
+'
+
+test_done
index 8ae69a61c33a2691a142759f45a8addd36322343..e797c741124ef86eae0e3a8d6a5372b0a84c91e2 100755 (executable)
@@ -35,27 +35,72 @@ test_expect_success GPG 'create signed commits' '
 '
 
 test_expect_success GPG 'merge unsigned commit with verification' '
+       test_when_finished "git reset --hard && git checkout initial" &&
        test_must_fail git merge --ff-only --verify-signatures side-unsigned 2>mergeerror &&
        test_i18ngrep "does not have a GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge unsigned commit with merge.verifySignatures=true' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       test_must_fail git merge --ff-only side-unsigned 2>mergeerror &&
+       test_i18ngrep "does not have a GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge commit with bad signature with verification' '
+       test_when_finished "git reset --hard && git checkout initial" &&
        test_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2>mergeerror &&
        test_i18ngrep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=true' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       test_must_fail git merge --ff-only $(cat forged.commit) 2>mergeerror &&
+       test_i18ngrep "has a bad GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge commit with untrusted signature with verification' '
+       test_when_finished "git reset --hard && git checkout initial" &&
        test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
        test_i18ngrep "has an untrusted GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       test_must_fail git merge --ff-only side-untrusted 2>mergeerror &&
+       test_i18ngrep "has an untrusted GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
+       test_when_finished "git reset --hard && git checkout initial" &&
        git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
        test_i18ngrep "has a good GPG signature" mergeoutput
 '
 
+test_expect_success GPG 'merge signed commit with merge.verifySignatures=true' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       git merge --verbose --ff-only side-signed >mergeoutput &&
+       test_i18ngrep "has a good GPG signature" mergeoutput
+'
+
 test_expect_success GPG 'merge commit with bad signature without verification' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       git merge $(cat forged.commit)
+'
+
+test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=false' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures false &&
        git merge $(cat forged.commit)
 '
 
+test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=true and --no-verify-signatures' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       git merge --no-verify-signatures $(cat forged.commit)
+'
+
 test_done
index 2a6679c2f596fb13a34786a4aa4b954e61c61113..1797f632a388d16f23a47c7693c7a3f0f0deabf6 100755 (executable)
@@ -60,6 +60,18 @@ test_expect_success setup '
                echo " line with leading space3"
                echo "line without leading space2"
        } >space &&
+       cat >hello.ps1 <<-\EOF &&
+       # No-op.
+       function dummy() {}
+
+       # Say hello.
+       function hello() {
+         echo "Hello world."
+       } # hello
+
+       # Still a no-op.
+       function dummy() {}
+       EOF
        git add . &&
        test_tick &&
        git commit -m initial
@@ -766,18 +778,27 @@ test_expect_success 'grep -W shows no trailing empty lines' '
        test_cmp expected actual
 '
 
-cat >expected <<EOF
-hello.c=       printf("Hello world.\n");
-hello.c:       return 0;
-hello.c-       /* char ?? */
-EOF
-
 test_expect_success 'grep -W with userdiff' '
        test_when_finished "rm -f .gitattributes" &&
-       git config diff.custom.xfuncname "(printf.*|})$" &&
-       echo "hello.c diff=custom" >.gitattributes &&
-       git grep -W return >actual &&
-       test_cmp expected actual
+       git config diff.custom.xfuncname "^function .*$" &&
+       echo "hello.ps1 diff=custom" >.gitattributes &&
+       git grep -W echo >function-context-userdiff-actual
+'
+
+test_expect_success ' includes preceding comment' '
+       grep "# Say hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes function line' '
+       grep "=function hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes matching line' '
+       grep ":  echo" function-context-userdiff-actual
+'
+
+test_expect_success ' includes last line of the function' '
+       grep "} # hello" function-context-userdiff-actual
 '
 
 for threads in $(test_seq 0 10)
@@ -1110,6 +1131,12 @@ test_expect_success PCRE 'grep -P pattern' '
        test_cmp expected actual
 '
 
+test_expect_success LIBPCRE2 "grep -P with (*NO_JIT) doesn't error out" '
+       git grep -P "(*NO_JIT)\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp expected actual
+
+'
+
 test_expect_success !PCRE 'grep -P pattern errors without PCRE' '
        test_must_fail git grep -P "foo.*bar"
 '
diff --git a/t/t9004-example.sh b/t/t9004-example.sh
new file mode 100755 (executable)
index 0000000..b28a028
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='check that example code compiles and runs'
+. ./test-lib.sh
+
+test_expect_success 'decorate' '
+       test-example-decorate
+'
+
+test_done
diff --git a/t/t9169-git-svn-dcommit-crlf.sh b/t/t9169-git-svn-dcommit-crlf.sh
new file mode 100755 (executable)
index 0000000..54b1f61
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+test_description='git svn dcommit CRLF'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup commit repository' '
+       svn_cmd mkdir -m "$test_description" "$svnrepo/dir" &&
+       git svn clone "$svnrepo" work &&
+       (
+               cd work &&
+               echo foo >>foo &&
+               git update-index --add foo &&
+               printf "a\\r\\n\\r\\nb\\r\\nc\\r\\n" >cmt &&
+               p=$(git rev-parse HEAD) &&
+               t=$(git write-tree) &&
+               cmt=$(git commit-tree -p $p $t <cmt) &&
+               git update-ref refs/heads/master $cmt &&
+               git cat-file commit HEAD | tail -n4 >out &&
+               test_cmp cmt out &&
+               git svn dcommit &&
+               printf "a\\n\\nb\\nc\\n" >exp &&
+               git cat-file commit HEAD | sed -ne 6,9p >out &&
+               test_cmp exp out
+       )
+'
+
+test_done
index d47560b6343db7006461259e4808837f3d35eecb..e4d06accc458191d0d52fe325b3118ac019e96c2 100755 (executable)
@@ -876,7 +876,7 @@ test_expect_success 'L: verify internal tree sorting' '
        EXPECT_END
 
        git fast-import <input &&
-       git diff-tree --abbrev --raw L^ L >output &&
+       GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev --raw L^ L >output &&
        test_cmp expect output
 '
 
index 116bd6a70cf727f8dff496b9340b85146208e778..9a0a21f49ae7bef3d0a1b653cce811d12499672e 100644 (file)
@@ -80,7 +80,7 @@ done,*)
        # from any previous runs.
        >"$GIT_TEST_TEE_OUTPUT_FILE"
 
-       (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1;
+       (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1;
         echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
        test "$(cat "$BASE.exit")" = 0
        exit
@@ -264,7 +264,6 @@ do
                shift ;;
        -x)
                trace=t
-               verbose=t
                shift ;;
        --verbose-log)
                verbose_log=t
@@ -283,6 +282,11 @@ then
        test -z "$verbose_log" && verbose=t
 fi
 
+if test -n "$trace" && test -z "$verbose_log"
+then
+       verbose=t
+fi
+
 if test -n "$color"
 then
        # Save the color control sequences now rather than run tput
@@ -586,7 +590,9 @@ maybe_setup_valgrind () {
 }
 
 want_trace () {
-       test "$trace" = t && test "$verbose" = t
+       test "$trace" = t && {
+               test "$verbose" = t || test "$verbose_log" = t
+       }
 }
 
 # This is a separate function because some tests use
@@ -601,26 +607,40 @@ test_eval_inner_ () {
 }
 
 test_eval_ () {
-       # We run this block with stderr redirected to avoid extra cruft
-       # during a "-x" trace. Once in "set -x" mode, we cannot prevent
+       # If "-x" tracing is in effect, then we want to avoid polluting stderr
+       # with non-test commands. But once in "set -x" mode, we cannot prevent
        # the shell from printing the "set +x" to turn it off (nor the saving
        # of $? before that). But we can make sure that the output goes to
        # /dev/null.
        #
-       # The test itself is run with stderr put back to &4 (so either to
-       # /dev/null, or to the original stderr if --verbose was used).
+       # There are a few subtleties here:
+       #
+       #   - we have to redirect descriptor 4 in addition to 2, to cover
+       #     BASH_XTRACEFD
+       #
+       #   - the actual eval has to come before the redirection block (since
+       #     it needs to see descriptor 4 to set up its stderr)
+       #
+       #   - likewise, any error message we print must be outside the block to
+       #     access descriptor 4
+       #
+       #   - checking $? has to come immediately after the eval, but it must
+       #     be _inside_ the block to avoid polluting the "set -x" output
+       #
+
+       test_eval_inner_ "$@" </dev/null >&3 2>&4
        {
-               test_eval_inner_ "$@" </dev/null >&3 2>&4
                test_eval_ret_=$?
                if want_trace
                then
                        set +x
-                       if test "$test_eval_ret_" != 0
-                       then
-                               say_color error >&4 "error: last command exited with \$?=$test_eval_ret_"
-                       fi
                fi
-       } 2>/dev/null
+       } 2>/dev/null 4>&2
+
+       if test "$test_eval_ret_" != 0 && want_trace
+       then
+               say_color error >&4 "error: last command exited with \$?=$test_eval_ret_"
+       fi
        return $test_eval_ret_
 }
 
@@ -1028,6 +1048,8 @@ test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE1$USE_LIBPCRE2" && test_set_prereq PCRE
+test -n "$USE_LIBPCRE1" && test_set_prereq LIBPCRE1
+test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2
 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # Can we rely on git's output in the C locale?
index b8f4b5e145b8b6b1d80cac299dbaedc8253e861b..450908b2e0bc4a94bc23a862abf04a56366aedd7 100644 (file)
  * `create_tempfile()` returns an allocated tempfile on success or NULL
  * on failure. On errors, `errno` describes the reason for failure.
  *
- * `delete_tempfile()`, `rename_tempfile()`, and `close_tempfile_gently()`
- * return 0 on success. On failure they set `errno` appropriately and return
- * -1. `delete` and `rename` (but not `close`) do their best to delete the
- * temporary file before returning.
+ * `rename_tempfile()` and `close_tempfile_gently()` return 0 on success.
+ * On failure they set `errno` appropriately and return -1.
+ * `delete_tempfile()` and `rename` (but not `close`) do their best to
+ * delete the temporary file before returning.
  */
 
 struct tempfile {
diff --git a/templates/hooks--fsmonitor-watchman.sample b/templates/hooks--fsmonitor-watchman.sample
new file mode 100755 (executable)
index 0000000..e673bb3
--- /dev/null
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $time) = @ARGV;
+
+# Check the hook interface version
+
+if ($version == 1) {
+       # convert nanoseconds to seconds
+       $time = int $time / 1000000000;
+} else {
+       die "Unsupported query-fsmonitor hook version '$version'.\n" .
+           "Falling back to scanning...\n";
+}
+
+my $git_work_tree;
+if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+       $git_work_tree = Win32::GetCwd();
+       $git_work_tree =~ tr/\\/\//;
+} else {
+       require Cwd;
+       $git_work_tree = Cwd::cwd();
+}
+
+my $retry = 1;
+
+launch_watchman();
+
+sub launch_watchman {
+
+       my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
+           or die "open2() failed: $!\n" .
+           "Falling back to scanning...\n";
+
+       # In the query expression below we're asking for names of files that
+       # changed since $time but were not transient (ie created after
+       # $time but no longer exist).
+       #
+       # To accomplish this, we're using the "since" generator to use the
+       # recency index to select candidate nodes and "fields" to limit the
+       # output to file names only. Then we're using the "expression" term to
+       # further constrain the results.
+       #
+       # The category of transient files that we want to ignore will have a
+       # creation clock (cclock) newer than $time_t value and will also not
+       # currently exist.
+
+       my $query = <<" END";
+               ["query", "$git_work_tree", {
+                       "since": $time,
+                       "fields": ["name"],
+                       "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
+               }]
+       END
+
+       print CHLD_IN $query;
+       close CHLD_IN;
+       my $response = do {local $/; <CHLD_OUT>};
+
+       die "Watchman: command returned no output.\n" .
+           "Falling back to scanning...\n" if $response eq "";
+       die "Watchman: command returned invalid output: $response\n" .
+           "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+       my $json_pkg;
+       eval {
+               require JSON::XS;
+               $json_pkg = "JSON::XS";
+               1;
+       } or do {
+               require JSON::PP;
+               $json_pkg = "JSON::PP";
+       };
+
+       my $o = $json_pkg->new->utf8->decode($response);
+
+       if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
+               print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
+               $retry--;
+               qx/watchman watch "$git_work_tree"/;
+               die "Failed to make watchman watch '$git_work_tree'.\n" .
+                   "Falling back to scanning...\n" if $? != 0;
+
+               # Watchman will always return all files on the first query so
+               # return the fast "everything is dirty" flag to git and do the
+               # Watchman query just to get it over with now so we won't pay
+               # the cost in git to look up each individual file.
+               print "/\0";
+               eval { launch_watchman() };
+               exit 0;
+       }
+
+       die "Watchman: $o->{error}.\n" .
+           "Falling back to scanning...\n" if $o->{error};
+
+       binmode STDOUT, ":utf8";
+       local $, = "\0";
+       print @{$o->{files}};
+}
diff --git a/trace.c b/trace.c
index cb1293ed33e16f605f8ac99336f040275c7bda7a..b7530b51a9e4d20825d42fcfd2a5dd389ad3b44e 100644 (file)
--- a/trace.c
+++ b/trace.c
 #include "cache.h"
 #include "quote.h"
 
-/*
- * "Normalize" a key argument by converting NULL to our trace_default,
- * and otherwise passing through the value. All caller-facing functions
- * should normalize their inputs in this way, though most get it
- * for free by calling get_trace_fd() (directly or indirectly).
- */
-static void normalize_trace_key(struct trace_key **key)
-{
-       static struct trace_key trace_default = { "GIT_TRACE" };
-       if (!*key)
-               *key = &trace_default;
-}
+struct trace_key trace_default_key = { "GIT_TRACE", 0, 0, 0 };
+struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
 
 /* Get a trace file descriptor from "key" env variable. */
 static int get_trace_fd(struct trace_key *key)
 {
        const char *trace;
 
-       normalize_trace_key(&key);
-
        /* don't open twice */
        if (key->initialized)
                return key->fd;
@@ -81,8 +69,6 @@ static int get_trace_fd(struct trace_key *key)
 
 void trace_disable(struct trace_key *key)
 {
-       normalize_trace_key(&key);
-
        if (key->need_close)
                close(key->fd);
        key->fd = 0;
@@ -128,7 +114,6 @@ static int prepare_trace_line(const char *file, int line,
 static void trace_write(struct trace_key *key, const void *buf, unsigned len)
 {
        if (write_in_full(get_trace_fd(key), buf, len) < 0) {
-               normalize_trace_key(&key);
                warning("unable to write trace for %s: %s",
                        key->key, strerror(errno));
                trace_disable(key);
@@ -167,13 +152,13 @@ static void trace_argv_vprintf_fl(const char *file, int line,
 {
        struct strbuf buf = STRBUF_INIT;
 
-       if (!prepare_trace_line(file, line, NULL, &buf))
+       if (!prepare_trace_line(file, line, &trace_default_key, &buf))
                return;
 
        strbuf_vaddf(&buf, format, ap);
 
        sq_quote_argv(&buf, argv, 0);
-       print_trace_line(NULL, &buf);
+       print_trace_line(&trace_default_key, &buf);
 }
 
 void trace_strbuf_fl(const char *file, int line, struct trace_key *key,
@@ -188,8 +173,6 @@ void trace_strbuf_fl(const char *file, int line, struct trace_key *key,
        print_trace_line(key, &buf);
 }
 
-static struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
-
 static void trace_performance_vprintf_fl(const char *file, int line,
                                         uint64_t nanos, const char *format,
                                         va_list ap)
@@ -215,7 +198,7 @@ void trace_printf(const char *format, ...)
 {
        va_list ap;
        va_start(ap, format);
-       trace_vprintf_fl(NULL, 0, NULL, format, ap);
+       trace_vprintf_fl(NULL, 0, &trace_default_key, format, ap);
        va_end(ap);
 }
 
diff --git a/trace.h b/trace.h
index 179b249c597125ac8ad3ed2d0c0e6b00a94773eb..88055abef7342ee638f6597a6628bcdf4baeace9 100644 (file)
--- a/trace.h
+++ b/trace.h
@@ -11,7 +11,10 @@ struct trace_key {
        unsigned int  need_close : 1;
 };
 
+extern struct trace_key trace_default_key;
+
 #define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 }
+extern struct trace_key trace_perf_key;
 
 extern void trace_repo_setup(const char *prefix);
 extern int trace_want(struct trace_key *key);
@@ -77,24 +80,42 @@ extern void trace_performance_since(uint64_t start, const char *format, ...);
  * comma, but this is non-standard.
  */
 
-#define trace_printf(...) \
-       trace_printf_key_fl(TRACE_CONTEXT, __LINE__, NULL, __VA_ARGS__)
-
-#define trace_printf_key(key, ...) \
-       trace_printf_key_fl(TRACE_CONTEXT, __LINE__, key, __VA_ARGS__)
-
-#define trace_argv_printf(argv, ...) \
-       trace_argv_printf_fl(TRACE_CONTEXT, __LINE__, argv, __VA_ARGS__)
-
-#define trace_strbuf(key, data) \
-       trace_strbuf_fl(TRACE_CONTEXT, __LINE__, key, data)
-
-#define trace_performance(nanos, ...) \
-       trace_performance_fl(TRACE_CONTEXT, __LINE__, nanos, __VA_ARGS__)
-
-#define trace_performance_since(start, ...) \
-       trace_performance_fl(TRACE_CONTEXT, __LINE__, getnanotime() - (start), \
-                            __VA_ARGS__)
+#define trace_printf_key(key, ...)                                         \
+       do {                                                                \
+               if (trace_pass_fl(key))                                     \
+                       trace_printf_key_fl(TRACE_CONTEXT, __LINE__, key,   \
+                                           __VA_ARGS__);                   \
+       } while (0)
+
+#define trace_printf(...) trace_printf_key(&trace_default_key, __VA_ARGS__)
+
+#define trace_argv_printf(argv, ...)                                       \
+       do {                                                                \
+               if (trace_pass_fl(&trace_default_key))                      \
+                       trace_argv_printf_fl(TRACE_CONTEXT, __LINE__,       \
+                                           argv, __VA_ARGS__);             \
+       } while (0)
+
+#define trace_strbuf(key, data)                                                    \
+       do {                                                                \
+               if (trace_pass_fl(key))                                     \
+                       trace_strbuf_fl(TRACE_CONTEXT, __LINE__, key, data);\
+       } while (0)
+
+#define trace_performance(nanos, ...)                                      \
+       do {                                                                \
+               if (trace_pass_fl(&trace_perf_key))                         \
+                       trace_performance_fl(TRACE_CONTEXT, __LINE__, nanos,\
+                                            __VA_ARGS__);                  \
+       } while (0)
+
+#define trace_performance_since(start, ...)                                \
+       do {                                                                \
+               if (trace_pass_fl(&trace_perf_key))                         \
+                       trace_performance_fl(TRACE_CONTEXT, __LINE__,       \
+                                            getnanotime() - (start),       \
+                                            __VA_ARGS__);                  \
+       } while (0)
 
 /* backend functions, use non-*fl macros instead */
 __attribute__((format (printf, 4, 5)))
@@ -108,6 +129,10 @@ extern void trace_strbuf_fl(const char *file, int line, struct trace_key *key,
 __attribute__((format (printf, 4, 5)))
 extern void trace_performance_fl(const char *file, int line,
                                 uint64_t nanos, const char *fmt, ...);
+static inline int trace_pass_fl(struct trace_key *key)
+{
+       return key->fd || !key->initialized;
+}
 
 #endif /* HAVE_VARIADIC_MACROS */
 
index c948d5215c22fbbb3e174e219017935741e9b2d5..508015023176290ec0f8d78043d852cdd37d5ce6 100644 (file)
@@ -11,6 +11,7 @@
 #include "sigchain.h"
 #include "argv-array.h"
 #include "refs.h"
+#include "transport-internal.h"
 
 static int debug;
 
@@ -535,7 +536,7 @@ static int fetch_with_import(struct transport *transport,
                else
                        private = xstrdup(name);
                if (private) {
-                       if (read_ref(private, posn->old_oid.hash) < 0)
+                       if (read_ref(private, &posn->old_oid) < 0)
                                die("Could not read ref %s", private);
                        free(private);
                }
@@ -650,7 +651,7 @@ static int fetch(struct transport *transport,
 
        if (process_connect(transport, 0)) {
                do_take_over(transport);
-               return transport->fetch(transport, nr_heads, to_fetch);
+               return transport->vtable->fetch(transport, nr_heads, to_fetch);
        }
 
        count = 0;
@@ -795,7 +796,8 @@ static int push_update_refs_status(struct helper_data *data,
                private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
                if (!private)
                        continue;
-               update_ref("update by helper", private, ref->new_oid.hash, NULL, 0, 0);
+               update_ref("update by helper", private, &ref->new_oid, NULL,
+                          0, 0);
                free(private);
        }
        strbuf_release(&buf);
@@ -881,7 +883,8 @@ static int push_refs_with_push(struct transport *transport,
                        struct strbuf cas = STRBUF_INIT;
                        strbuf_addf(&cas, "%s:%s",
                                    ref->name, oid_to_hex(&ref->old_oid_expect));
-                       string_list_append(&cas_options, strbuf_detach(&cas, NULL));
+                       string_list_append_nodup(&cas_options,
+                                                strbuf_detach(&cas, NULL));
                }
        }
        if (buf.len == 0) {
@@ -896,6 +899,7 @@ static int push_refs_with_push(struct transport *transport,
        strbuf_addch(&buf, '\n');
        sendline(data, &buf);
        strbuf_release(&buf);
+       string_list_clear(&cas_options, 0);
 
        return push_update_refs_status(data, remote_refs, flags);
 }
@@ -929,7 +933,8 @@ static int push_refs_with_export(struct transport *transport,
                private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
                if (private && !get_oid(private, &oid)) {
                        strbuf_addf(&buf, "^%s", private);
-                       string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
+                       string_list_append_nodup(&revlist_args,
+                                                strbuf_detach(&buf, NULL));
                        oidcpy(&ref->old_oid, &oid);
                }
                free(private);
@@ -941,10 +946,9 @@ static int push_refs_with_export(struct transport *transport,
                                        int flag;
 
                                        /* Follow symbolic refs (mainly for HEAD). */
-                                       name = resolve_ref_unsafe(
-                                                ref->peer_ref->name,
-                                                RESOLVE_REF_READING,
-                                                oid.hash, &flag);
+                                       name = resolve_ref_unsafe(ref->peer_ref->name,
+                                                                 RESOLVE_REF_READING,
+                                                                 &oid, &flag);
                                        if (!name || !(flag & REF_ISSYMREF))
                                                name = ref->peer_ref->name;
 
@@ -987,7 +991,7 @@ static int push_refs(struct transport *transport,
 
        if (process_connect(transport, 1)) {
                do_take_over(transport);
-               return transport->push_refs(transport, remote_refs, flags);
+               return transport->vtable->push_refs(transport, remote_refs, flags);
        }
 
        if (!remote_refs) {
@@ -1035,7 +1039,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 
        if (process_connect(transport, for_push)) {
                do_take_over(transport);
-               return transport->get_refs_list(transport, for_push);
+               return transport->vtable->get_refs_list(transport, for_push);
        }
 
        if (data->push && for_push)
@@ -1066,8 +1070,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
                if (eon) {
                        if (has_attribute(eon + 1, "unchanged")) {
                                (*tail)->status |= REF_STATUS_UPTODATE;
-                               if (read_ref((*tail)->name,
-                                            (*tail)->old_oid.hash) < 0)
+                               if (read_ref((*tail)->name, &(*tail)->old_oid) < 0)
                                        die(_("Could not read ref %s"),
                                            (*tail)->name);
                        }
@@ -1084,6 +1087,15 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
        return ret;
 }
 
+static struct transport_vtable vtable = {
+       set_helper_option,
+       get_refs_list,
+       fetch,
+       push_refs,
+       connect_helper,
+       release_helper
+};
+
 int transport_helper_init(struct transport *transport, const char *name)
 {
        struct helper_data *data = xcalloc(1, sizeof(*data));
@@ -1095,12 +1107,7 @@ int transport_helper_init(struct transport *transport, const char *name)
                debug = 1;
 
        transport->data = data;
-       transport->set_option = set_helper_option;
-       transport->get_refs_list = get_refs_list;
-       transport->fetch = fetch;
-       transport->push_refs = push_refs;
-       transport->disconnect = release_helper;
-       transport->connect = connect_helper;
+       transport->vtable = &vtable;
        transport->smart_options = &(data->transport_options);
        return 0;
 }
diff --git a/transport-internal.h b/transport-internal.h
new file mode 100644 (file)
index 0000000..3c1a29d
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef TRANSPORT_INTERNAL_H
+#define TRANSPORT_INTERNAL_H
+
+struct ref;
+struct transport;
+
+struct transport_vtable {
+       /**
+        * Returns 0 if successful, positive if the option is not
+        * recognized or is inapplicable, and negative if the option
+        * is applicable but the value is invalid.
+        **/
+       int (*set_option)(struct transport *connection, const char *name,
+                         const char *value);
+       /**
+        * Returns a list of the remote side's refs. In order to allow
+        * the transport to try to share connections, for_push is a
+        * hint as to whether the ultimate operation is a push or a fetch.
+        *
+        * If the transport is able to determine the remote hash for
+        * the ref without a huge amount of effort, it should store it
+        * in the ref's old_sha1 field; otherwise it should be all 0.
+        **/
+       struct ref *(*get_refs_list)(struct transport *transport, int for_push);
+
+       /**
+        * Fetch the objects for the given refs. Note that this gets
+        * an array, and should ignore the list structure.
+        *
+        * If the transport did not get hashes for refs in
+        * get_refs_list(), it should set the old_sha1 fields in the
+        * provided refs now.
+        **/
+       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+
+       /**
+        * Push the objects and refs. Send the necessary objects, and
+        * then, for any refs where peer_ref is set and
+        * peer_ref->new_oid is different from old_oid, tell the
+        * remote side to update each ref in the list from old_oid to
+        * peer_ref->new_oid.
+        *
+        * Where possible, set the status for each ref appropriately.
+        *
+        * The transport must modify new_sha1 in the ref to the new
+        * value if the remote accepted the change. Note that this
+        * could be a different value from peer_ref->new_oid if the
+        * process involved generating new commits.
+        **/
+       int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
+       int (*connect)(struct transport *connection, const char *name,
+                      const char *executable, int fd[2]);
+
+       /** get_refs_list(), fetch(), and push_refs() can keep
+        * resources (such as a connection) reserved for further
+        * use. disconnect() releases these resources.
+        **/
+       int (*disconnect)(struct transport *connection);
+};
+
+#endif
index f1e2f61991424f3314158676754f6b8d305a31f2..fc802260f61c7496f4250b15ef6f5ab8dd316898 100644 (file)
@@ -17,6 +17,7 @@
 #include "string-list.h"
 #include "sha1-array.h"
 #include "sigchain.h"
+#include "transport-internal.h"
 
 static void set_upstreams(struct transport *transport, struct ref *refs,
        int pretend)
@@ -305,8 +306,8 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v
                if (ref->deletion) {
                        delete_ref(NULL, rs.dst, NULL, 0);
                } else
-                       update_ref("update by push", rs.dst,
-                                       ref->new_oid.hash, NULL, 0, 0);
+                       update_ref("update by push", rs.dst, &ref->new_oid,
+                                  NULL, 0, 0);
                free(rs.dst);
        }
 }
@@ -607,6 +608,15 @@ static int disconnect_git(struct transport *transport)
        return 0;
 }
 
+static struct transport_vtable taken_over_vtable = {
+       NULL,
+       get_refs_via_connect,
+       fetch_refs_via_pack,
+       git_transport_push,
+       NULL,
+       disconnect_git
+};
+
 void transport_take_over(struct transport *transport,
                         struct child_process *child)
 {
@@ -624,12 +634,7 @@ void transport_take_over(struct transport *transport,
        data->got_remote_heads = 0;
        transport->data = data;
 
-       transport->set_option = NULL;
-       transport->get_refs_list = get_refs_via_connect;
-       transport->fetch = fetch_refs_via_pack;
-       transport->push = NULL;
-       transport->push_refs = git_transport_push;
-       transport->disconnect = disconnect_git;
+       transport->vtable = &taken_over_vtable;
        transport->smart_options = &(data->options);
 
        transport->cannot_reuse = 1;
@@ -752,6 +757,24 @@ void transport_check_allowed(const char *type)
                die("transport '%s' not allowed", type);
 }
 
+static struct transport_vtable bundle_vtable = {
+       NULL,
+       get_refs_from_bundle,
+       fetch_refs_from_bundle,
+       NULL,
+       NULL,
+       close_bundle
+};
+
+static struct transport_vtable builtin_smart_vtable = {
+       NULL,
+       get_refs_via_connect,
+       fetch_refs_via_pack,
+       git_transport_push,
+       connect_git,
+       disconnect_git
+};
+
 struct transport *transport_get(struct remote *remote, const char *url)
 {
        const char *helper;
@@ -788,9 +811,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                transport_check_allowed("file");
                ret->data = data;
-               ret->get_refs_list = get_refs_from_bundle;
-               ret->fetch = fetch_refs_from_bundle;
-               ret->disconnect = close_bundle;
+               ret->vtable = &bundle_vtable;
                ret->smart_options = NULL;
        } else if (!is_url(url)
                || starts_with(url, "file://")
@@ -805,12 +826,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
                 */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
-               ret->set_option = NULL;
-               ret->get_refs_list = get_refs_via_connect;
-               ret->fetch = fetch_refs_via_pack;
-               ret->push_refs = git_transport_push;
-               ret->connect = connect_git;
-               ret->disconnect = disconnect_git;
+               ret->vtable = &builtin_smart_vtable;
                ret->smart_options = &(data->options);
 
                data->conn = NULL;
@@ -844,9 +860,9 @@ int transport_set_option(struct transport *transport,
                git_reports = set_git_option(transport->smart_options,
                                             name, value);
 
-       if (transport->set_option)
-               protocol_reports = transport->set_option(transport, name,
-                                                       value);
+       if (transport->vtable->set_option)
+               protocol_reports = transport->vtable->set_option(transport,
+                                                                name, value);
 
        /* If either report is 0, report 0 (success). */
        if (!git_reports || !protocol_reports)
@@ -969,13 +985,7 @@ int transport_push(struct transport *transport,
        *reject_reasons = 0;
        transport_verify_remote_names(refspec_nr, refspec);
 
-       if (transport->push) {
-               /* Maybe FIXME. But no important transport uses this case. */
-               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
-                       die("This transport does not support using --set-upstream");
-
-               return transport->push(transport, refspec_nr, refspec, flags);
-       } else if (transport->push_refs) {
+       if (transport->vtable->push_refs) {
                struct ref *remote_refs;
                struct ref *local_refs = get_local_heads();
                int match_flags = MATCH_REFS_NONE;
@@ -988,7 +998,7 @@ int transport_push(struct transport *transport,
                if (check_push_refs(local_refs, refspec_nr, refspec) < 0)
                        return -1;
 
-               remote_refs = transport->get_refs_list(transport, 1);
+               remote_refs = transport->vtable->get_refs_list(transport, 1);
 
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
@@ -1063,7 +1073,7 @@ int transport_push(struct transport *transport,
                }
 
                if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY))
-                       push_ret = transport->push_refs(transport, remote_refs, flags);
+                       push_ret = transport->vtable->push_refs(transport, remote_refs, flags);
                else
                        push_ret = 0;
                err = push_had_errors(remote_refs);
@@ -1097,7 +1107,7 @@ int transport_push(struct transport *transport,
 const struct ref *transport_get_remote_refs(struct transport *transport)
 {
        if (!transport->got_remote_refs) {
-               transport->remote_refs = transport->get_refs_list(transport, 0);
+               transport->remote_refs = transport->vtable->get_refs_list(transport, 0);
                transport->got_remote_refs = 1;
        }
 
@@ -1134,7 +1144,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
                        heads[nr_heads++] = rm;
        }
 
-       rc = transport->fetch(transport, nr_heads, heads);
+       rc = transport->vtable->fetch(transport, nr_heads, heads);
 
        free(heads);
        return rc;
@@ -1151,8 +1161,8 @@ void transport_unlock_pack(struct transport *transport)
 int transport_connect(struct transport *transport, const char *name,
                      const char *exec, int fd[2])
 {
-       if (transport->connect)
-               return transport->connect(transport, name, exec, fd);
+       if (transport->vtable->connect)
+               return transport->vtable->connect(transport, name, exec, fd);
        else
                die("Operation not supported by protocol");
 }
@@ -1160,8 +1170,8 @@ int transport_connect(struct transport *transport, const char *name,
 int transport_disconnect(struct transport *transport)
 {
        int ret = 0;
-       if (transport->disconnect)
-               ret = transport->disconnect(transport);
+       if (transport->vtable->disconnect)
+               ret = transport->vtable->disconnect(transport);
        free(transport);
        return ret;
 }
index bc5571574b67803249567bcee501dfcc342b0b0f..731c78b6795740f8e9bfb80741b0170671b37dd3 100644 (file)
@@ -30,6 +30,8 @@ enum transport_family {
 };
 
 struct transport {
+       const struct transport_vtable *vtable;
+
        struct remote *remote;
        const char *url;
        void *data;
@@ -59,59 +61,6 @@ struct transport {
         */
        const struct string_list *push_options;
 
-       /**
-        * Returns 0 if successful, positive if the option is not
-        * recognized or is inapplicable, and negative if the option
-        * is applicable but the value is invalid.
-        **/
-       int (*set_option)(struct transport *connection, const char *name,
-                         const char *value);
-
-       /**
-        * Returns a list of the remote side's refs. In order to allow
-        * the transport to try to share connections, for_push is a
-        * hint as to whether the ultimate operation is a push or a fetch.
-        *
-        * If the transport is able to determine the remote hash for
-        * the ref without a huge amount of effort, it should store it
-        * in the ref's old_sha1 field; otherwise it should be all 0.
-        **/
-       struct ref *(*get_refs_list)(struct transport *transport, int for_push);
-
-       /**
-        * Fetch the objects for the given refs. Note that this gets
-        * an array, and should ignore the list structure.
-        *
-        * If the transport did not get hashes for refs in
-        * get_refs_list(), it should set the old_sha1 fields in the
-        * provided refs now.
-        **/
-       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
-
-       /**
-        * Push the objects and refs. Send the necessary objects, and
-        * then, for any refs where peer_ref is set and
-        * peer_ref->new_oid is different from old_oid, tell the
-        * remote side to update each ref in the list from old_oid to
-        * peer_ref->new_oid.
-        *
-        * Where possible, set the status for each ref appropriately.
-        *
-        * The transport must modify new_sha1 in the ref to the new
-        * value if the remote accepted the change. Note that this
-        * could be a different value from peer_ref->new_oid if the
-        * process involved generating new commits.
-        **/
-       int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
-       int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
-       int (*connect)(struct transport *connection, const char *name,
-                      const char *executable, int fd[2]);
-
-       /** get_refs_list(), fetch(), and push_refs() can keep
-        * resources (such as a connection) reserved for further
-        * use. disconnect() releases these resources.
-        **/
-       int (*disconnect)(struct transport *connection);
        char *pack_lockfile;
        signed verbose : 3;
        /**
index 4bb93155bc6e0349156b21ad1287d573593a79d5..fe2e466ac1dcb164b214ce4856ed9d62884ddbd3 100644 (file)
@@ -212,9 +212,9 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p,
                mode = 0;
        }
 
-       if (DIFF_OPT_TST(opt, RECURSIVE) && isdir) {
+       if (opt->flags.recursive && isdir) {
                recurse = 1;
-               emitthis = DIFF_OPT_TST(opt, TREE_IN_RECURSIVE);
+               emitthis = opt->flags.tree_in_recursive;
        }
 
        if (emitthis) {
@@ -425,7 +425,7 @@ static struct combine_diff_path *ll_diff_tree_paths(
        ttree = fill_tree_descriptor(&t, oid);
 
        /* Enable recursion indefinitely */
-       opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
+       opt->pathspec.recursive = opt->flags.recursive;
 
        for (;;) {
                int imin, cmp;
@@ -484,7 +484,7 @@ static struct combine_diff_path *ll_diff_tree_paths(
                /* t = p[imin] */
                if (cmp == 0) {
                        /* are either pi > p[imin] or diff(t,pi) != Ã¸ ? */
-                       if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) {
+                       if (!opt->flags.find_copies_harder) {
                                for (i = 0; i < nparent; ++i) {
                                        /* p[i] > p[imin] */
                                        if (tp[i].entry.mode & S_IFXMIN_NEQ)
@@ -522,7 +522,7 @@ static struct combine_diff_path *ll_diff_tree_paths(
                /* t > p[imin] */
                else {
                        /* âˆ€i pi=p[imin] -> D += "-p[imin]" */
-                       if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) {
+                       if (!opt->flags.find_copies_harder) {
                                for (i = 0; i < nparent; ++i)
                                        if (tp[i].entry.mode & S_IFXMIN_NEQ)
                                                goto skip_emit_tp;
@@ -608,8 +608,8 @@ static void try_to_follow_renames(const struct object_id *old_oid,
        q->nr = 0;
 
        diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
-       DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+       diff_opts.flags.recursive = 1;
+       diff_opts.flags.find_copies_harder = 1;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = opt->pathspec.items[0].match;
        diff_opts.break_opt = opt->break_opt;
@@ -706,7 +706,7 @@ int diff_tree_oid(const struct object_id *old_oid,
        strbuf_addstr(&base, base_str);
 
        retval = ll_diff_tree_oid(old_oid, new_oid, &base, opt);
-       if (!*base_str && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename())
+       if (!*base_str && opt->flags.follow_renames && diff_might_be_rename())
                try_to_follow_renames(old_oid, new_oid, &base, opt);
 
        strbuf_release(&base);
index 684f0e33736ca237a933f4def3e1560356bde2c2..63a87ed666bbb10cb3c2bd0e27117ac696e7d1b3 100644 (file)
@@ -1011,7 +1011,8 @@ static enum interesting do_match(const struct name_entry *entry,
                                 * character.  More accurate matching can then
                                 * be performed in the submodule itself.
                                 */
-                               if (ps->recursive && S_ISGITLINK(entry->mode) &&
+                               if (ps->recurse_submodules &&
+                                   S_ISGITLINK(entry->mode) &&
                                    !ps_strncmp(item, match + baselen,
                                                entry->path,
                                                item->nowildcard_len - baselen))
@@ -1060,7 +1061,7 @@ static enum interesting do_match(const struct name_entry *entry,
                 * character.  More accurate matching can then
                 * be performed in the submodule itself.
                 */
-               if (ps->recursive && S_ISGITLINK(entry->mode) &&
+               if (ps->recurse_submodules && S_ISGITLINK(entry->mode) &&
                    !ps_strncmp(item, match, base->buf + base_offset,
                                item->nowildcard_len)) {
                        strbuf_setlen(base, base_offset + baselen);
index 71b70ccb12756cde1f1c5aeda952be6e2b91f5b8..bf8b6029013c31000d83d6292f83ce0d0021d5d4 100644 (file)
@@ -14,6 +14,7 @@
 #include "dir.h"
 #include "submodule.h"
 #include "submodule-config.h"
+#include "fsmonitor.h"
 
 /*
  * Error messages expected by scripts out of plumbing commands such as
@@ -408,6 +409,7 @@ static int apply_sparse_checkout(struct index_state *istate,
                ce->ce_flags &= ~CE_SKIP_WORKTREE;
        if (was_skip_worktree != ce_skip_worktree(ce)) {
                ce->ce_flags |= CE_UPDATE_IN_BASE;
+               mark_fsmonitor_invalid(istate, ce);
                istate->cache_changed |= CE_ENTRY_CHANGED;
        }
 
@@ -1541,15 +1543,15 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
        int cnt = 0;
 
        if (S_ISGITLINK(ce->ce_mode)) {
-               unsigned char sha1[20];
-               int sub_head = resolve_gitlink_ref(ce->name, "HEAD", sha1);
+               struct object_id oid;
+               int sub_head = resolve_gitlink_ref(ce->name, "HEAD", &oid);
                /*
                 * If we are not going to update the submodule, then
                 * we don't care.
                 */
-               if (!sub_head && !hashcmp(sha1, ce->oid.hash))
+               if (!sub_head && !oidcmp(&oid, &ce->oid))
                        return 0;
-               return verify_clean_submodule(sub_head ? NULL : sha1_to_hex(sha1),
+               return verify_clean_submodule(sub_head ? NULL : oid_to_hex(&oid),
                                              ce, error_type, o);
        }
 
index e25f725c0feaa57c9a09c976628217c99dfb5652..d5de18127c63a9d017578e0969743392d39526d4 100644 (file)
@@ -18,6 +18,7 @@
 #include "parse-options.h"
 #include "argv-array.h"
 #include "prio-queue.h"
+#include "protocol.h"
 
 static const char * const upload_pack_usage[] = {
        N_("git upload-pack [<options>] <dir>"),
@@ -787,7 +788,7 @@ static void receive_needs(void)
                if (skip_prefix(line, "deepen-not ", &arg)) {
                        char *ref = NULL;
                        struct object_id oid;
-                       if (expand_ref(arg, strlen(arg), oid.hash, &ref) != 1)
+                       if (expand_ref(arg, strlen(arg), &oid, &ref) != 1)
                                die("git upload-pack: ambiguous deepen-not: %s", line);
                        string_list_append(&deepen_not, ref);
                        free(ref);
@@ -955,7 +956,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
                packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons);
        }
        capabilities = NULL;
-       if (!peel_ref(refname, peeled.hash))
+       if (!peel_ref(refname, &peeled))
                packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
        return 0;
 }
@@ -1066,6 +1067,23 @@ int cmd_main(int argc, const char **argv)
                die("'%s' does not appear to be a git repository", dir);
 
        git_config(upload_pack_config, NULL);
-       upload_pack();
+
+       switch (determine_protocol_version_server()) {
+       case protocol_v1:
+               /*
+                * v1 is just the original protocol with a version string,
+                * so just fall through after writing the version string.
+                */
+               if (advertise_refs || !stateless_rpc)
+                       packet_write_fmt(1, "version 1\n");
+
+               /* fallthrough */
+       case protocol_v0:
+               upload_pack();
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
+
        return 0;
 }
index 6106a8098c8a46575f9afae5da9909b5c196f754..41b718c29e1b9fc2981d7d14a3d25e69c31a3030 100644 (file)
--- a/version.c
+++ b/version.c
@@ -3,6 +3,7 @@
 #include "strbuf.h"
 
 const char git_version_string[] = GIT_VERSION;
+const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;
 
 const char *git_user_agent(void)
 {
index 6911a4f1558de0c0d0b880a5b859925869a3e86e..7c62e80577154d79bec050424945eb500d262a0f 100644 (file)
--- a/version.h
+++ b/version.h
@@ -2,6 +2,7 @@
 #define VERSION_H
 
 extern const char git_version_string[];
+extern const char git_built_from_commit_string[];
 
 const char *git_user_agent(void);
 const char *git_user_agent_sanitized(void);
index 274f1a4935798cba3e8cc7250559f14305678419..5d4d3733f75648dfa8d6955a5ec1ed9f3dfeb77b 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -7,7 +7,7 @@
 #include "blob.h"
 #include "refs.h"
 
-static unsigned char current_commit_sha1[20];
+static struct object_id current_commit_oid;
 
 void walker_say(struct walker *walker, const char *fmt, ...)
 {
@@ -24,9 +24,9 @@ static void report_missing(const struct object *obj)
        fprintf(stderr, "Cannot obtain needed %s %s\n",
                obj->type ? typename(obj->type): "object",
                oid_to_hex(&obj->oid));
-       if (!is_null_sha1(current_commit_sha1))
+       if (!is_null_oid(&current_commit_oid))
                fprintf(stderr, "while processing commit %s.\n",
-                       sha1_to_hex(current_commit_sha1));
+                       oid_to_hex(&current_commit_oid));
 }
 
 static int process(struct walker *walker, struct object *obj);
@@ -82,7 +82,7 @@ static int process_commit(struct walker *walker, struct commit *commit)
        if (commit->object.flags & COMPLETE)
                return 0;
 
-       hashcpy(current_commit_sha1, commit->object.oid.hash);
+       oidcpy(&current_commit_oid, &commit->object.oid);
 
        walker_say(walker, "walk %s\n", oid_to_hex(&commit->object.oid));
 
@@ -187,14 +187,14 @@ static int loop(struct walker *walker)
        return 0;
 }
 
-static int interpret_target(struct walker *walker, char *target, unsigned char *sha1)
+static int interpret_target(struct walker *walker, char *target, struct object_id *oid)
 {
-       if (!get_sha1_hex(target, sha1))
+       if (!get_oid_hex(target, oid))
                return 0;
        if (!check_refname_format(target, 0)) {
                struct ref *ref = alloc_ref(target);
                if (!walker->fetch_ref(walker, ref)) {
-                       hashcpy(sha1, ref->old_oid.hash);
+                       oidcpy(oid, &ref->old_oid);
                        free(ref);
                        return 0;
                }
@@ -259,7 +259,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
        struct strbuf refname = STRBUF_INIT;
        struct strbuf err = STRBUF_INIT;
        struct ref_transaction *transaction = NULL;
-       unsigned char *sha1 = xmalloc(targets * 20);
+       struct object_id *oids = xmalloc(targets * sizeof(struct object_id));
        char *msg = NULL;
        int i, ret = -1;
 
@@ -279,11 +279,11 @@ int walker_fetch(struct walker *walker, int targets, char **target,
        }
 
        for (i = 0; i < targets; i++) {
-               if (interpret_target(walker, target[i], &sha1[20 * i])) {
+               if (interpret_target(walker, target[i], oids + i)) {
                        error("Could not interpret response from server '%s' as something to pull", target[i]);
                        goto done;
                }
-               if (process(walker, lookup_unknown_object(&sha1[20 * i])))
+               if (process(walker, lookup_unknown_object(oids[i].hash)))
                        goto done;
        }
 
@@ -304,7 +304,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
                strbuf_reset(&refname);
                strbuf_addf(&refname, "refs/%s", write_ref[i]);
                if (ref_transaction_update(transaction, refname.buf,
-                                          &sha1[20 * i], NULL, 0,
+                                          oids + i, NULL, 0,
                                           msg ? msg : "fetch (unknown)",
                                           &err)) {
                        error("%s", err.buf);
@@ -321,7 +321,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
 done:
        ref_transaction_free(transaction);
        free(msg);
-       free(sha1);
+       free(oids);
        strbuf_release(&err);
        strbuf_release(&refname);
        return ret;
index f8c40f2f5f8ce5b18c976b5281f5618adbeee4a7..f5da7d286d537fa99a1bd2dd5180068b9d85da2f 100644 (file)
@@ -31,7 +31,7 @@ static void add_head_info(struct worktree *wt)
        target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
                                         "HEAD",
                                         0,
-                                        wt->head_sha1, &flags);
+                                        &wt->head_oid, &flags);
        if (!target)
                return;
 
index 9276c81ae7cb5c42a86fd9ebbdb28720be409e47..c28a880e1839ef26604d80de78eb9c663a6be381 100644 (file)
@@ -8,7 +8,7 @@ struct worktree {
        char *id;
        char *head_ref;         /* NULL if HEAD is broken or detached */
        char *lock_reason;      /* internal use */
-       unsigned char head_sha1[20];
+       struct object_id head_oid;
        int is_detached;
        int is_bare;
        int is_current;
index 94e5ebaf8b11840383edfdae9624d34ae283061a..ef26f0744632fdcca919e68ffc58bf414b29e597 100644 (file)
@@ -559,12 +559,12 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        init_revisions(&rev, NULL);
        setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
-       DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
+       rev.diffopt.flags.dirty_submodules = 1;
        rev.diffopt.ita_invisible_in_index = 1;
        if (!s->show_untracked_files)
-               DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+               rev.diffopt.flags.ignore_untracked_in_submodules = 1;
        if (s->ignore_submodule_arg) {
-               DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+               rev.diffopt.flags.override_submodule_config = 1;
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
        }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
@@ -583,7 +583,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
 
-       DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+       rev.diffopt.flags.override_submodule_config = 1;
        rev.diffopt.ita_invisible_in_index = 1;
        if (s->ignore_submodule_arg) {
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
@@ -658,10 +658,15 @@ static void wt_status_collect_untracked(struct wt_status *s)
        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
                dir.flags |=
                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
-       if (s->show_ignored_files)
+       if (s->show_ignored_mode) {
                dir.flags |= DIR_SHOW_IGNORED_TOO;
-       else
+
+               if (s->show_ignored_mode == SHOW_MATCHING_IGNORED)
+                       dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
+       } else {
                dir.untracked = the_index.untracked;
+       }
+
        setup_standard_excludes(&dir);
 
        fill_directory(&dir, &the_index, &s->pathspec);
@@ -949,7 +954,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
        const char *c = color(WT_STATUS_HEADER, s);
 
        init_revisions(&rev, NULL);
-       DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
+       rev.diffopt.flags.allow_textconv = 1;
        rev.diffopt.ita_invisible_in_index = 1;
 
        memset(&opt, 0, sizeof(opt));
@@ -1449,7 +1454,7 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
                return;
        }
 
-       if (dwim_ref(cb.buf.buf, cb.buf.len, oid.hash, &ref) == 1 &&
+       if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref) == 1 &&
            /* sha1 is a commit? match without further lookup */
            (!oidcmp(&cb.noid, &oid) ||
             /* perhaps sha1 is a tag, try to dereference to a commit */
@@ -1619,7 +1624,7 @@ static void wt_longstatus_print(struct wt_status *s)
        }
        if (s->show_untracked_files) {
                wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
-               if (s->show_ignored_files)
+               if (s->show_ignored_mode)
                        wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
                if (advice_status_u_option && 2000 < s->untracked_in_ms) {
                        status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
@@ -2263,10 +2268,10 @@ int has_unstaged_changes(int ignore_submodules)
 
        init_revisions(&rev_info, NULL);
        if (ignore_submodules) {
-               DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
-               DIFF_OPT_SET(&rev_info.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+               rev_info.diffopt.flags.ignore_submodules = 1;
+               rev_info.diffopt.flags.override_submodule_config = 1;
        }
-       DIFF_OPT_SET(&rev_info.diffopt, QUICK);
+       rev_info.diffopt.flags.quick = 1;
        diff_setup_done(&rev_info.diffopt);
        result = run_diff_files(&rev_info, 0);
        return diff_result_code(&rev_info.diffopt, result);
@@ -2285,8 +2290,8 @@ int has_uncommitted_changes(int ignore_submodules)
 
        init_revisions(&rev_info, NULL);
        if (ignore_submodules)
-               DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
-       DIFF_OPT_SET(&rev_info.diffopt, QUICK);
+               rev_info.diffopt.flags.ignore_submodules = 1;
+       rev_info.diffopt.flags.quick = 1;
        add_head_to_pending(&rev_info);
        diff_setup_done(&rev_info.diffopt);
        result = run_diff_index(&rev_info, 1);
@@ -2299,14 +2304,14 @@ int has_uncommitted_changes(int ignore_submodules)
  */
 int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
 {
-       struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
+       struct lock_file lock_file = LOCK_INIT;
        int err = 0, fd;
 
-       fd = hold_locked_index(lock_file, 0);
+       fd = hold_locked_index(&lock_file, 0);
        refresh_cache(REFRESH_QUIET);
        if (0 <= fd)
-               update_index_if_able(&the_index, lock_file);
-       rollback_lock_file(lock_file);
+               update_index_if_able(&the_index, &lock_file);
+       rollback_lock_file(&lock_file);
 
        if (has_unstaged_changes(ignore_submodules)) {
                /* TRANSLATORS: the action is e.g. "pull with rebase" */
index 64f4d33ea183c1a6e700f1f6667b83f0e904773e..fe27b465e220bb720dfd0b4d52ddeae74a942707 100644 (file)
@@ -27,6 +27,12 @@ enum untracked_status_type {
        SHOW_ALL_UNTRACKED_FILES
 };
 
+enum show_ignored_type {
+       SHOW_NO_IGNORED,
+       SHOW_TRADITIONAL_IGNORED,
+       SHOW_MATCHING_IGNORED,
+};
+
 /* from where does this commit originate */
 enum commit_whence {
        FROM_COMMIT,     /* normal */
@@ -70,7 +76,7 @@ struct wt_status {
        int display_comment_prefix;
        int relative_paths;
        int submodule_summary;
-       int show_ignored_files;
+       enum show_ignored_type show_ignored_mode;
        enum untracked_status_type show_untracked_files;
        const char *ignore_submodule_arg;
        char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
index 785ecb0899454f55dc160a132b5cf932ec382f09..c1937a291126ca5e451763ccdf22bf8cccd0bd42 100644 (file)
 extern "C" {
 #endif /* #ifdef __cplusplus */
 
+/* xpparm_t.flags */
+#define XDF_NEED_MINIMAL (1 << 0)
+
+#define XDF_IGNORE_WHITESPACE (1 << 1)
+#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2)
+#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3)
+#define XDF_IGNORE_CR_AT_EOL (1 << 4)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \
+                             XDF_IGNORE_WHITESPACE_CHANGE | \
+                             XDF_IGNORE_WHITESPACE_AT_EOL | \
+                             XDF_IGNORE_CR_AT_EOL)
 
-#define XDF_NEED_MINIMAL (1 << 1)
-#define XDF_IGNORE_WHITESPACE (1 << 2)
-#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
-#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
-#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
+#define XDF_IGNORE_BLANK_LINES (1 << 7)
 
-#define XDF_PATIENCE_DIFF (1 << 5)
-#define XDF_HISTOGRAM_DIFF (1 << 6)
+#define XDF_PATIENCE_DIFF (1 << 14)
+#define XDF_HISTOGRAM_DIFF (1 << 15)
 #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
 #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
 
-#define XDF_IGNORE_BLANK_LINES (1 << 7)
-
-#define XDF_INDENT_HEURISTIC (1 << 8)
+#define XDF_INDENT_HEURISTIC (1 << 23)
 
+/* xdemitconf_t.flags */
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_FUNCCONTEXT (1 << 2)
 
@@ -80,6 +86,10 @@ typedef struct s_mmbuffer {
 
 typedef struct s_xpparam {
        unsigned long flags;
+
+       /* See Documentation/diff-options.txt. */
+       char **anchors;
+       size_t anchors_nr;
 } xpparam_t;
 
 typedef struct s_xdemitcb {
index 6881445e4afc3482ba008ebbdab81b51167863fc..7778dc2b190f84fe2ab1e1539756a7e1f4254c3b 100644 (file)
@@ -121,6 +121,12 @@ static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri,
        return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv);
 }
 
+static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri)
+{
+       char dummy[1];
+       return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0;
+}
+
 struct func_line {
        long len;
        char buf[80];
@@ -178,7 +184,6 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
                        /* Appended chunk? */
                        if (i1 >= xe->xdf1.nrec) {
-                               char dummy[1];
                                long i2 = xch->i2;
 
                                /*
@@ -186,8 +191,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                                 * a whole function was added.
                                 */
                                while (i2 < xe->xdf2.nrec) {
-                                       if (match_func_rec(&xe->xdf2, xecfg, i2,
-                                               dummy, sizeof(dummy)) >= 0)
+                                       if (is_func_rec(&xe->xdf2, xecfg, i2))
                                                goto post_context_calculation;
                                        i2++;
                                }
@@ -200,6 +204,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                        }
 
                        fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
+                       while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) &&
+                              !is_func_rec(&xe->xdf1, xecfg, fs1 - 1))
+                               fs1--;
                        if (fs1 < 0)
                                fs1 = 0;
                        if (fs1 < s1) {
index a44e776328ce617c3144c17870ab1616a2239947..f3573d9f00e77ab551b7f109f29356721165ff47 100644 (file)
@@ -62,6 +62,12 @@ struct hashmap {
                 * initially, "next" reflects only the order in file1.
                 */
                struct entry *next, *previous;
+
+               /*
+                * If 1, this entry can serve as an anchor. See
+                * Documentation/diff-options.txt for more information.
+                */
+               unsigned anchor : 1;
        } *entries, *first, *last;
        /* were common records found? */
        unsigned long has_matches;
@@ -70,8 +76,19 @@ struct hashmap {
        xpparam_t const *xpp;
 };
 
+static int is_anchor(xpparam_t const *xpp, const char *line)
+{
+       int i;
+       for (i = 0; i < xpp->anchors_nr; i++) {
+               if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i])))
+                       return 1;
+       }
+       return 0;
+}
+
 /* The argument "pass" is 1 for the first file, 2 for the second. */
-static void insert_record(int line, struct hashmap *map, int pass)
+static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
+                         int pass)
 {
        xrecord_t **records = pass == 1 ?
                map->env->xdf1.recs : map->env->xdf2.recs;
@@ -110,6 +127,7 @@ static void insert_record(int line, struct hashmap *map, int pass)
                return;
        map->entries[index].line1 = line;
        map->entries[index].hash = record->ha;
+       map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
        if (!map->first)
                map->first = map->entries + index;
        if (map->last) {
@@ -147,11 +165,11 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
 
        /* First, fill with entries from the first file */
        while (count1--)
-               insert_record(line1++, result, 1);
+               insert_record(xpp, line1++, result, 1);
 
        /* Then search for matches in the second file */
        while (count2--)
-               insert_record(line2++, result, 2);
+               insert_record(xpp, line2++, result, 2);
 
        return 0;
 }
@@ -192,14 +210,28 @@ static struct entry *find_longest_common_sequence(struct hashmap *map)
        int longest = 0, i;
        struct entry *entry;
 
+       /*
+        * If not -1, this entry in sequence must never be overridden.
+        * Therefore, overriding entries before this has no effect, so
+        * do not do that either.
+        */
+       int anchor_i = -1;
+
        for (entry = map->first; entry; entry = entry->next) {
                if (!entry->line2 || entry->line2 == NON_UNIQUE)
                        continue;
                i = binary_search(sequence, longest, entry);
                entry->previous = i < 0 ? NULL : sequence[i];
-               sequence[++i] = entry;
-               if (i == longest)
+               ++i;
+               if (i <= anchor_i)
+                       continue;
+               sequence[i] = entry;
+               if (entry->anchor) {
+                       anchor_i = i;
+                       longest = anchor_i + 1;
+               } else if (i == longest) {
                        longest++;
+               }
        }
 
        /* No common unique lines were found */
index 088001db4f45af338696ea3112559b7bc729a5d3..88e5995535467475216de82b1eeb744e744f3b4d 100644 (file)
@@ -156,6 +156,24 @@ int xdl_blankline(const char *line, long size, long flags)
        return (i == size);
 }
 
+/*
+ * Have we eaten everything on the line, except for an optional
+ * CR at the very end?
+ */
+static int ends_with_optional_cr(const char *l, long s, long i)
+{
+       int complete = s && l[s-1] == '\n';
+
+       if (complete)
+               s--;
+       if (s == i)
+               return 1;
+       /* do not ignore CR at the end of an incomplete line */
+       if (complete && s == i + 1 && l[i] == '\r')
+               return 1;
+       return 0;
+}
+
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 {
        int i1, i2;
@@ -170,7 +188,8 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 
        /*
         * -w matches everything that matches with -b, and -b in turn
-        * matches everything that matches with --ignore-space-at-eol.
+        * matches everything that matches with --ignore-space-at-eol,
+        * which in turn matches everything that matches with --ignore-cr-at-eol.
         *
         * Each flavor of ignoring needs different logic to skip whitespaces
         * while we have both sides to compare.
@@ -204,6 +223,14 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
                        i1++;
                        i2++;
                }
+       } else if (flags & XDF_IGNORE_CR_AT_EOL) {
+               /* Find the first difference and see how the line ends */
+               while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
+                       i1++;
+                       i2++;
+               }
+               return (ends_with_optional_cr(l1, s1, i1) &&
+                       ends_with_optional_cr(l2, s2, i2));
        }
 
        /*
@@ -230,9 +257,16 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
                char const *top, long flags) {
        unsigned long ha = 5381;
        char const *ptr = *data;
+       int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
 
        for (; ptr < top && *ptr != '\n'; ptr++) {
-               if (XDL_ISSPACE(*ptr)) {
+               if (cr_at_eol_only) {
+                       /* do not ignore CR at the end of an incomplete line */
+                       if (*ptr == '\r' &&
+                           (ptr + 1 < top && ptr[1] == '\n'))
+                               continue;
+               }
+               else if (XDL_ISSPACE(*ptr)) {
                        const char *ptr2 = ptr;
                        int at_eol;
                        while (ptr + 1 < top && XDL_ISSPACE(ptr[1])