summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--Documentation/RelNotes/2.10.0.txt189
-rw-r--r--Documentation/RelNotes/2.9.3.txt58
-rw-r--r--Documentation/config.txt30
-rw-r--r--Documentation/diff-options.txt3
-rw-r--r--Documentation/git-fsck.txt9
-rw-r--r--Documentation/git-push.txt8
-rw-r--r--Documentation/git-svn.txt3
-rw-r--r--Documentation/git-worktree.txt36
-rw-r--r--Documentation/gitattributes.txt15
-rw-r--r--Documentation/githooks.txt18
-rw-r--r--Documentation/pretty-formats.txt10
-rw-r--r--Documentation/rev-list-options.txt44
-rw-r--r--Documentation/technical/pack-protocol.txt12
-rw-r--r--Documentation/technical/protocol-capabilities.txt9
-rw-r--r--Makefile26
-rw-r--r--builtin/blame.c13
-rw-r--r--builtin/branch.c19
-rw-r--r--builtin/cat-file.c4
-rw-r--r--builtin/clone.c12
-rw-r--r--builtin/commit.c2
-rw-r--r--builtin/fetch.c8
-rw-r--r--builtin/fsck.c84
-rw-r--r--builtin/index-pack.c33
-rw-r--r--builtin/notes.c5
-rw-r--r--builtin/pack-objects.c17
-rw-r--r--builtin/push.c21
-rw-r--r--builtin/receive-pack.c177
-rw-r--r--builtin/remote.c8
-rw-r--r--builtin/rev-list.c17
-rw-r--r--builtin/revert.c13
-rw-r--r--builtin/rm.c5
-rw-r--r--builtin/submodule--helper.c2
-rw-r--r--builtin/worktree.c66
-rw-r--r--cache-tree.c14
-rw-r--r--cache.h23
-rw-r--r--compat/mingw.h3
-rw-r--r--config.c117
-rw-r--r--config.mak.uname3
-rw-r--r--configure.ac5
-rw-r--r--connected.c71
-rw-r--r--connected.h37
-rw-r--r--contrib/completion/git-completion.bash6
-rw-r--r--contrib/git-jump/README6
-rwxr-xr-xcontrib/git-jump/git-jump8
-rw-r--r--contrib/persistent-https/Makefile6
-rwxr-xr-xcontrib/subtree/git-subtree.sh678
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh32
-rw-r--r--convert.c42
-rw-r--r--convert.h3
-rw-r--r--daemon.c8
-rw-r--r--date.c8
-rw-r--r--diff.c7
-rw-r--r--dir-iterator.c202
-rw-r--r--dir-iterator.h87
-rw-r--r--dir.c2
-rw-r--r--dir.h23
-rw-r--r--fetch-pack.c19
-rw-r--r--fsck.c113
-rw-r--r--fsck.h7
-rwxr-xr-xgit-difftool.perl86
-rw-r--r--git-rebase--interactive.sh2
-rwxr-xr-xgit-submodule.sh24
-rwxr-xr-xgit-svn.perl4
-rw-r--r--grep.c22
-rw-r--r--grep.h1
-rw-r--r--http-walker.c55
-rw-r--r--http.c16
-rw-r--r--iterator.h81
-rw-r--r--list.h166
-rw-r--r--merge-recursive.c17
-rw-r--r--notes-merge.c6
-rw-r--r--pack-check.c23
-rw-r--r--pack.h1
-rw-r--r--parse-options-cb.c29
-rw-r--r--parse-options.h2
-rw-r--r--path.c2
-rw-r--r--read-cache.c5
-rw-r--r--refs.c96
-rw-r--r--refs.h141
-rw-r--r--refs/files-backend.c1569
-rw-r--r--refs/iterator.c384
-rw-r--r--refs/refs-internal.h321
-rw-r--r--revision.c8
-rw-r--r--send-pack.c27
-rw-r--r--send-pack.h3
-rw-r--r--sha1_file.c2
-rw-r--r--strbuf.c7
-rw-r--r--strbuf.h6
-rw-r--r--submodule-config.c34
-rw-r--r--t/helper/test-submodule-config.c2
-rw-r--r--t/lib-git-svn.sh91
-rw-r--r--t/lib-httpd.sh8
-rw-r--r--t/lib-httpd/apache.conf4
-rwxr-xr-xt/t0000-basic.sh2
-rwxr-xr-xt/t0006-date.sh3
-rwxr-xr-xt/t0021-conversion.sh11
-rwxr-xr-xt/t0025-crlf-auto.sh4
-rwxr-xr-xt/t0027-auto-crlf.sh29
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh8
-rwxr-xr-xt/t1050-large.sh7
-rwxr-xr-xt/t1100-commit-tree-options.sh2
-rwxr-xr-xt/t1400-update-ref.sh41
-rwxr-xr-xt/t1404-update-ref-df-conflicts.sh107
-rwxr-xr-xt/t1404-update-ref-errors.sh407
-rwxr-xr-xt/t1430-bad-ref-name.sh2
-rwxr-xr-xt/t1450-fsck.sh22
-rwxr-xr-xt/t1700-split-index.sh24
-rwxr-xr-xt/t2028-worktree-move.sh62
-rwxr-xr-xt/t2203-add-intent.sh31
-rwxr-xr-xt/t3102-ls-tree-wildcards.sh8
-rwxr-xr-xt/t3200-branch.sh9
-rwxr-xr-xt/t3404-rebase-interactive.sh8
-rwxr-xr-xt/t3415-rebase-autosquash.sh33
-rwxr-xr-xt/t4010-diff-pathspec.sh2
-rwxr-xr-xt/t4033-diff-patience.sh8
-rwxr-xr-xt/t4054-diff-bogus-tree.sh10
-rwxr-xr-xt/t4130-apply-criss-cross-rename.sh10
-rwxr-xr-xt/t4202-log.sh14
-rwxr-xr-xt/t4205-log-pretty-formats.sh398
-rwxr-xr-xt/t5504-fetch-receive-strict.sh4
-rwxr-xr-xt/t5510-fetch.sh4
-rwxr-xr-xt/t5541-http-push-smart.sh9
-rwxr-xr-xt/t5545-push-options.sh103
-rwxr-xr-xt/t6038-merge-text-auto.sh74
-rwxr-xr-xt/t7011-skip-worktree-reading.sh12
-rwxr-xr-xt/t7012-skip-worktree-writing.sh10
-rwxr-xr-xt/t7060-wtstatus.sh4
-rwxr-xr-xt/t7063-status-untracked-cache.sh6
-rwxr-xr-xt/t7411-submodule-config.sh11
-rwxr-xr-xt/t7508-status.sh2
-rwxr-xr-xt/t7512-status-help.sh1
-rwxr-xr-xt/t7800-difftool.sh14
-rwxr-xr-xt/t8003-blame-corner-cases.sh79
-rwxr-xr-xt/t9100-git-svn-basic.sh23
-rwxr-xr-xt/t9115-git-svn-dcommit-funky-renames.sh7
-rwxr-xr-xt/t9118-git-svn-funky-branch-names.sh2
-rwxr-xr-xt/t9120-git-svn-clone-with-percent-escapes.sh2
-rwxr-xr-xt/t9142-git-svn-shallow-clone.sh2
-rwxr-xr-xt/t9158-git-svn-mergeinfo.sh2
-rwxr-xr-xt/t9160-git-svn-preserve-empty-dirs.sh1
-rw-r--r--t/test-lib.sh7
-rw-r--r--templates/hooks--pre-receive.sample24
-rw-r--r--transport.c8
-rw-r--r--transport.h7
-rw-r--r--worktree.c81
-rw-r--r--worktree.h21
-rw-r--r--wt-status.c9
-rw-r--r--xdiff/xpatience.c2
-rw-r--r--xdiff/xutils.c6
150 files changed, 5690 insertions, 1862 deletions
diff --git a/.travis.yml b/.travis.yml
index c2b76f9b7e..477c3d2efb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,7 @@ addons:
packages:
- language-pack-is
- git-svn
+ - apache2
env:
global:
@@ -31,6 +32,7 @@ env:
- DEFAULT_TEST_TARGET=prove
- GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
- GIT_TEST_OPTS="--verbose --tee"
+ - GIT_TEST_HTTPD=true
- 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
diff --git a/Documentation/RelNotes/2.10.0.txt b/Documentation/RelNotes/2.10.0.txt
index fe921ddd58..a9fb0736d6 100644
--- a/Documentation/RelNotes/2.10.0.txt
+++ b/Documentation/RelNotes/2.10.0.txt
@@ -34,7 +34,7 @@ UI, Workflows & Features
* "upload-pack" allows a custom "git pack-objects" replacement when
responding to "fetch/clone" via the uploadpack.packObjectsHook.
- (merge 20b20a2 jk/upload-pack-hook later to maint).
+ (merge b738396 jk/upload-pack-hook later to maint).
* Teach format-patch and mailsplit (hence "am") how a line that
happens to begin with "From " in the e-mail message is quoted with
@@ -84,6 +84,27 @@ UI, Workflows & Features
* A new configuration variable core.sshCommand has been added to
specify what value for GIT_SSH_COMMAND to use per repository.
+ * "git worktree prune" protected worktrees that are marked as
+ "locked" by creating a file in a known location. "git worktree"
+ command learned a dedicated command pair to create and remove such
+ a file, so that the users do not have to do this with editor.
+
+ * A handful of "git svn" updates.
+
+ * "git push" learned to accept and pass extra options to the
+ receiving end so that hooks can read and react to them.
+
+ * "git status" learned to suggest "merge --abort" during a conflicted
+ merge, just like it already suggests "rebase --abort" during a
+ conflicted rebase.
+ (merge b0a61ab mm/status-suggest-merge-abort later to maint).
+
+ * "git jump" script (in contrib/) has been updated a bit.
+ (merge a91e692 jk/git-jump later to maint).
+
+ * "git push" and "git clone" learned to give better progress meters
+ to the end user who is waiting on the terminal.
+
Performance, Internal Implementation, Development Support etc.
@@ -156,7 +177,6 @@ Performance, Internal Implementation, Development Support etc.
* Improve the look of the way "git fetch" reports what happened to
each ref that was fetched.
- (merge bc437d1 nd/fetch-ref-summary later to maint).
* The .c/.h sources are marked as such in our .gitattributes file so
that "git diff -W" and friends would work better.
@@ -169,6 +189,63 @@ Performance, Internal Implementation, Development Support etc.
* "git p4" used a location outside $GIT_DIR/refs/ to place its
temporary branches, which has been moved to refs/git-p4-tmp/.
+ * Existing autoconf generated test for the need to link with pthread
+ library did not check all the functions from pthread libraries;
+ recent FreeBSD has some functions in libc but not others, and we
+ mistakenly thought linking with libc is enough when it is not.
+ (merge a9b02de ew/autoconf-pthread later to maint).
+
+ * When "git fsck" reports a broken link (e.g. a tree object contains
+ a blob that does not exist), both containing object and the object
+ that is referred to were reported with their 40-hex object names.
+ The command learned the "--name-objects" option to show the path to
+ the containing object from existing refs (e.g. "HEAD~24^2:file.txt").
+
+ * Allow http daemon tests in Travis CI tests.
+ (merge d9d1426 ls/travis-enable-httpd-tests later to maint).
+
+ * Makefile assumed that -lrt is always available on platforms that
+ want to use clock_gettime() and CLOCK_MONOTONIC, which is not a
+ case for recent Mac OS X. The necessary symbols are often found in
+ libc on many modern systems and having -lrt on the command line, as
+ long as the library exists, had no effect, but when the platform
+ removes librt.a that is a different matter--having -lrt will break
+ the linkage.
+
+ This change could be seen as a regression for those who do need to
+ specify -lrt, as they now specifically ask for NEEDS_LIBRT when
+ building. Hopefully they are in the minority these days.
+
+ * Further preparatory work on the refs API before the pluggable
+ backend series can land.
+
+ * Error handling in the codepaths that updates refs has been
+ improved.
+
+ * The API to iterate over all the refs (i.e. for_each_ref(), etc.)
+ has been revamped.
+
+ * A few tests that specifically target "git rebase -i" have been
+ added.
+
+ * Dumb http transport on the client side has been optimized.
+ (merge ecba195 ew/http-walker later to maint).
+
+ * Users of the parse_options_concat() API function need to allocate
+ extra slots in advance and fill them with OPT_END() when they want
+ to decide the set of supported options dynamically, which makes the
+ code error-prone and hard to read. This has been corrected by tweaking
+ the API to allocate and return a new copy of "struct option" array.
+ (merge 023ff39 jk/parse-options-concat later to maint).
+
+ * "git fetch" exchanges batched have/ack messages between the sender
+ and the receiver, initially doubling every time and then falling
+ back to enlarge the window size linearly. The "smart http"
+ transport, being an half-duplex protocol, outgrows the preset limit
+ too quickly and becomes inefficient when interacting with a large
+ repository. The internal mechanism learned to grow the window size
+ more aggressively when working with the "smart http" transport.
+
Also contains various documentation updates and code clean-ups.
@@ -249,7 +326,6 @@ notes for details).
bogus offset value to the caller. Use a more benign looking
+0000 instead and let "git log" going in such a case, instead
of aborting.
- (merge bab7483 jk/tzoffset-fix later to maint).
* One among four invocations of readlink(1) in our test suite has
been rewritten so that the test can run on systems without the
@@ -272,58 +348,46 @@ notes for details).
finds its subject line did not ignore leading blank lines, as is
commonly done by other codepaths. Make it ignore leading blank
lines to match.
- (merge 054a5ae js/find-commit-subject-ignore-leading-blanks later to maint).
* For a long time, we carried an in-code comment that said our
colored output would work only when we use fprintf/fputs on
Windows, which no longer is the case for the past few years.
- (merge 3d0a833 js/color-on-windows-comment later to maint).
* "gc.autoPackLimit" when set to 1 should not trigger a repacking
when there is only one pack, but the code counted poorly and did
so.
- (merge 5f4e3bf ew/gc-auto-pack-limit-fix later to maint).
* Add a test to specify the desired behaviour that currently is not
available in "git rebase -Xsubtree=...".
- (merge 5f35900 dg/subtree-rebase-test later to maint).
* More mark-up updates to typeset strings that are expected to
literally typed by the end user in fixed-width font.
- (merge 661c3e9 mm/doc-tt later to maint).
* "git commit --amend --allow-empty-message -S" for a commit without
any message body could have misidentified where the header of the
commit object ends.
- (merge 3324dd8 js/sign-empty-commit-fix later to maint).
* "git rebase -i --autostash" did not restore the auto-stashed change
when the operation was aborted.
- (merge 33ba9c6 ps/rebase-i-auto-unstash-upon-abort later to maint).
* Git does not know what the contents in the index should be for a
path added with "git add -N" yet, so "git grep --cached" should not
show hits (or show lack of hits, with -L) in such a path, but that
logic does not apply to "git grep", i.e. searching in the working
tree files. But we did so by mistake, which has been corrected.
- (merge b8e47d1 nd/ita-cleanup later to maint).
* "git blame -M" missed a single line that was moved within the file.
- (merge 17a07e2 dk/blame-move-no-reason-for-1-line-context later to maint).
* Fix recently introduced codepaths that are involved in parallel
submodule operations, which gave up on reading too early, and
could have wasted CPU while attempting to write under a corner
case condition.
- (merge d751dd1 sb/submodule-parallel-fetch later to maint).
* "git grep -i" has been taught to fold case in non-ascii locales
correctly.
- (merge 695f95b nd/icase later to maint).
* A test that unconditionally used "mktemp" learned that the command
is not necessarily available everywhere.
- (merge c578a09 ak/lazy-prereq-mktemp later to maint).
* There are certain house-keeping tasks that need to be performed at
the very beginning of any Git program, and programs that are not
@@ -336,7 +400,6 @@ notes for details).
* The test framework learned a new helper test_match_signal to
check an exit code from getting killed by an expected signal.
- (merge 03c39b3 jk/test-match-signal later to maint).
* General code clean-up around a helper function to write a
single-liner to a file.
@@ -347,13 +410,101 @@ notes for details).
which was not gender-neutral and also inconsistent with the rest of
the system where outside stuff is usuall called "theirs" in
contrast to "ours".
- (merge 715a51b js/am-call-theirs-theirs-in-fallback-3way later to maint).
+
+ * "git blame file" allowed the lineage of lines in the uncommitted,
+ unadded contents of "file" to be inspected, but it refused when
+ "file" did not appear in the current commit. When "file" was
+ created by renaming an existing file (but the change has not been
+ committed), this restriction was unnecessarily tight.
+ (merge c66b470 mh/blame-worktree later to maint).
+
+ * "git add -N dir/file && git write-tree" produced an incorrect tree
+ when there are other paths in the same directory that sorts after
+ "file".
+ (merge 6d6a782 nd/cache-tree-ita later to maint).
+
+ * "git fetch http://user:pass@host/repo..." scrubbed the userinfo
+ part, but "git push" didn't.
+ (merge 68f3c07 jk/push-scrub-url later to maint).
+
+ * "git merge" with renormalization did not work well with
+ merge-recursive, due to "safer crlf" conversion kicking in when it
+ shouldn't.
+ (merge 1335d76 jc/renormalize-merge-kill-safer-crlf later to maint).
+
+ * The use of strbuf in "git rm" to build filename to remove was a bit
+ suboptimal, which has been fixed.
+ (merge deb8e15 rs/rm-strbuf-optim later to maint).
+
+ * An age old bug that caused "git diff --ignore-space-at-eol"
+ misbehave has been fixed.
+ (merge 044fb19 js/ignore-space-at-eol later to maint).
+
+ * "git notes merge" had a code to see if a path exists (and fails if
+ it does) and then open the path for writing (when it doesn't).
+ Replace it with open with O_EXCL.
+ (merge deb9c15 rs/notes-merge-no-toctou later to maint).
+
+ * "git pack-objects" and "git index-pack" mostly operate with off_t
+ when talking about the offset of objects in a packfile, but there
+ were a handful of places that used "unsigned long" to hold that
+ value, leading to an unintended truncation.
+ (merge ec9d224 nd/pack-ofs-4gb-limit later to maint).
+
+ * Recent update to "git daemon" tries to enable the socket-level
+ KEEPALIVE, but when it is spawned via inetd, the standard input
+ file descriptor may not necessarily be connected to a socket.
+ Suppress an ENOTSOCK error from setsockopt().
+ (merge fab6027 ew/daemon-socket-keepalive later to maint).
+
+ * Recent FreeBSD stopped making perl available at /usr/bin/perl;
+ switch the default the built-in path to /usr/local/bin/perl on not
+ too ancient FreeBSD releases.
+ (merge 259f22a ew/find-perl-on-freebsd-in-local later to maint).
+
+ * "git commit --help" said "--no-verify" is only about skipping the
+ pre-commit hook, and failed to say that it also skipped the
+ commit-msg hook.
+ (merge def480f os/no-verify-skips-commit-msg-too later to maint).
+
+ * "git merge" in Git v2.9 was taught to forbid merging an unrelated
+ lines of history by default, but that is exactly the kind of thing
+ the "--rejoin" mode of "git subtree" (in contrib/) wants to do.
+ "git subtree" has been taught to use the "--allow-unrelated-histories"
+ option to override the default.
+ (merge 0f12c7d da/subtree-2.9-regression later to maint).
+
+ * The build procedure for "git persistent-https" helper (in contrib/)
+ has been updated so that it can be built with more recent versions
+ of Go.
+ (merge accb613 pm/build-persistent-https-with-recent-go later to maint).
+
+ * There is an optimization used in "git diff $treeA $treeB" to borrow
+ an already checked-out copy in the working tree when it is known to
+ be the same as the blob being compared, expecting that open/mmap of
+ such a file is faster than reading it from the object store, which
+ involves inflating and applying delta. This however kicked in even
+ when the checked-out copy needs to go through the convert-to-git
+ conversion (including the clean filter), which defeats the whole
+ point of the optimization. The optimization has been disabled when
+ the conversion is necessary.
+ (merge 06dec43 jk/diff-do-not-reuse-wtf-needs-cleaning later to maint).
+
+ * "git -c grep.patternType=extended log --basic-regexp" misbehaved
+ because the internal API to access the grep machinery was not
+ designed well.
+ (merge 8465541 jc/grep-commandline-vs-configuration later to maint).
* Other minor clean-ups and documentation updates
(merge e51b0df pb/commit-editmsg-path later to maint).
(merge b333d0d jk/send-pack-stdio later to maint).
(merge fcf0fe9 lf/sideband-returns-void later to maint).
(merge c2691e2 ah/unpack-trees-advice-messages later to maint).
- (merge 82f6178 nd/doc-new-command later to maint).
- (merge fa90ab4 js/t3404-grammo-fix later to maint).
(merge c61b2af lf/recv-sideband-cleanup later to maint).
+ (merge 31471ba rs/use-strbuf-addbuf later to maint).
+ (merge 503e224 nd/test-helpers later to maint).
+ (merge 16726cf jc/doc-diff-filter-exclude later to maint).
+ (merge fd2e7da rs/worktree-use-strbuf-absolute-path later to maint).
+ (merge 406621f sb/submodule-deinit-all later to maint).
+ (merge 55cbe18 rs/submodule-config-code-cleanup later to maint).
+ (merge 280abfd sb/pack-protocol-doc-nak later to maint).
diff --git a/Documentation/RelNotes/2.9.3.txt b/Documentation/RelNotes/2.9.3.txt
new file mode 100644
index 0000000000..28003a54ef
--- /dev/null
+++ b/Documentation/RelNotes/2.9.3.txt
@@ -0,0 +1,58 @@
+Git v2.9.3 Release Notes
+========================
+
+Fixes since v2.9.2
+------------------
+
+ * A helper function that takes the contents of a commit object and
+ finds its subject line did not ignore leading blank lines, as is
+ commonly done by other codepaths. Make it ignore leading blank
+ lines to match.
+
+ * Git does not know what the contents in the index should be for a
+ path added with "git add -N" yet, so "git grep --cached" should not
+ show hits (or show lack of hits, with -L) in such a path, but that
+ logic does not apply to "git grep", i.e. searching in the working
+ tree files. But we did so by mistake, which has been corrected.
+
+ * "git rebase -i --autostash" did not restore the auto-stashed change
+ when the operation was aborted.
+
+ * "git commit --amend --allow-empty-message -S" for a commit without
+ any message body could have misidentified where the header of the
+ commit object ends.
+
+ * More mark-up updates to typeset strings that are expected to
+ literally typed by the end user in fixed-width font.
+
+ * For a long time, we carried an in-code comment that said our
+ colored output would work only when we use fprintf/fputs on
+ Windows, which no longer is the case for the past few years.
+
+ * "gc.autoPackLimit" when set to 1 should not trigger a repacking
+ when there is only one pack, but the code counted poorly and did
+ so.
+
+ * One part of "git am" had an oddball helper function that called
+ stuff from outside "his" as opposed to calling what we have "ours",
+ which was not gender-neutral and also inconsistent with the rest of
+ the system where outside stuff is usuall called "theirs" in
+ contrast to "ours".
+
+ * The test framework learned a new helper test_match_signal to
+ check an exit code from getting killed by an expected signal.
+
+ * "git blame -M" missed a single line that was moved within the file.
+
+ * Fix recently introduced codepaths that are involved in parallel
+ submodule operations, which gave up on reading too early, and
+ could have wasted CPU while attempting to write under a corner
+ case condition.
+
+ * "git grep -i" has been taught to fold case in non-ascii locales
+ correctly.
+
+ * A test that unconditionally used "mktemp" learned that the command
+ is not necessarily available everywhere.
+
+Also contains minor documentation updates and code clean-ups.
diff --git a/Documentation/config.txt b/Documentation/config.txt
index e455faeb23..bc1c433c4e 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -412,13 +412,11 @@ file with mixed line endings would be reported by the `core.safecrlf`
mechanism.
core.autocrlf::
- Setting this variable to "true" is almost the same as setting
- the `text` attribute to "auto" on all files except that text
- files are not guaranteed to be normalized: files that contain
- `CRLF` in the repository will not be touched. Use this
- setting if you want to have `CRLF` line endings in your
- working directory even though the repository does not have
- normalized line endings. This variable can be set to 'input',
+ Setting this variable to "true" is the same as setting
+ the `text` attribute to "auto" on all files and core.eol to "crlf".
+ Set to true if you want to have `CRLF` line endings in your
+ working directory and the repository has LF line endings.
+ This variable can be set to 'input',
in which case no output conversion is performed.
core.symlinks::
@@ -2429,8 +2427,13 @@ rebase.instructionFormat
receive.advertiseAtomic::
By default, git-receive-pack will advertise the atomic push
- capability to its clients. If you don't want to this capability
- to be advertised, set this variable to false.
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
+
+receive.advertisePushOptions::
+ By default, git-receive-pack will advertise the push options
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.autogc::
By default, git-receive-pack will run "git-gc --auto" after
@@ -2485,6 +2488,15 @@ receive.fsck.skipList::
can be safely ignored such as invalid committer email addresses.
Note: corrupt objects cannot be skipped with this setting.
+receive.keepAlive::
+ After receiving the pack from the client, `receive-pack` may
+ produce no output (if `--quiet` was specified) while processing
+ the pack, causing some networks to drop the TCP connection.
+ With this option set, if `receive-pack` does not transmit
+ any data in this phase for `receive.keepAlive` seconds, it will
+ send a short keepalive packet. The default is 5 seconds; set
+ to 0 to disable keepalives entirely.
+
receive.unpackLimit::
If the number of objects received in a push is below this
limit then the objects will be unpacked into loose object
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index d9ae681d8f..705a873942 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -419,6 +419,9 @@ ifndef::git-format-patch[]
paths are selected if there is any file that matches
other criteria in the comparison; if there is no file
that matches other criteria, nothing is selected.
++
+Also, these upper-case letters can be downcased to exclude. E.g.
+`--diff-filter=ad` excludes added and deleted paths.
-S<string>::
Look for differences that change the number of occurrences of
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index 7fc68eb319..b9f060e3b2 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -11,7 +11,8 @@ SYNOPSIS
[verse]
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
- [--[no-]dangling] [--[no-]progress] [--connectivity-only] [<object>*]
+ [--[no-]dangling] [--[no-]progress] [--connectivity-only]
+ [--[no-]name-objects] [<object>*]
DESCRIPTION
-----------
@@ -82,6 +83,12 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs
a blob, the contents are written into the file, rather than
its object name.
+--name-objects::
+ When displaying names of reachable objects, in addition to the
+ SHA-1 also display a name that describes *how* they are reachable,
+ compatible with linkgit:git-rev-parse[1], e.g.
+ `HEAD@{1234567890}~25^2:src/`.
+
--[no-]progress::
Progress status is reported on the standard error stream by
default when it is attached to a terminal, unless
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 93c3527f0c..ec514f6cd5 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
- [-u | --set-upstream]
+ [-u | --set-upstream] [--push-option=<string>]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
@@ -156,6 +156,12 @@ 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::
+ 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.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 7e17cade7f..5f9e65b0c4 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -625,6 +625,9 @@ config key: svn.authorsfile
with the committer name as the first argument. The program is
expected to return a single line of the form "Name <email>",
which will be treated as if included in the authors file.
++
+[verse]
+config key: svn.authorsProg
-q::
--quiet::
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 7c4cfb0885..0aeb020d02 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -11,7 +11,9 @@ SYNOPSIS
[verse]
'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <worktree>
'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <worktree>
DESCRIPTION
-----------
@@ -38,9 +40,8 @@ section "DETAILS" for more information.
If a linked working tree is stored on a portable device or network share
which is not always mounted, you can prevent its administrative files from
-being pruned by creating a file named 'locked' alongside the other
-administrative files, optionally containing a plain text reason that
-pruning should be suppressed. See section "DETAILS" for more information.
+being pruned by issuing the `git worktree lock` command, optionally
+specifying `--reason` to explain why the working tree is locked.
COMMANDS
--------
@@ -62,10 +63,22 @@ each of the linked worktrees. The output details include if the worktree is
bare, the revision currently checked out, and the branch currently checked out
(or 'detached HEAD' if none).
+lock::
+
+If a working tree is on a portable device or network share which
+is not always mounted, lock it to prevent its administrative
+files from being pruned automatically. This also prevents it from
+being moved or deleted. Optionally, specify a reason for the lock
+with `--reason`.
+
prune::
Prune working tree information in $GIT_DIR/worktrees.
+unlock::
+
+Unlock a working tree, allowing it to be pruned, moved or deleted.
+
OPTIONS
-------
@@ -111,6 +124,18 @@ OPTIONS
--expire <time>::
With `prune`, only expire unused working trees older than <time>.
+--reason <string>::
+ With `lock`, an explanation why the working tree is locked.
+
+<worktree>::
+ Working trees can be identified by path, either relative or
+ absolute.
++
+If the last path components in the working tree's path is unique among
+working trees, it can be used to identify worktrees. For example if
+you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg",
+then "ghi" or "def/ghi" is enough to point to the former working tree.
+
DETAILS
-------
Each linked working tree has a private sub-directory in the repository's
@@ -151,7 +176,8 @@ instead.
To prevent a $GIT_DIR/worktrees entry from being pruned (which
can be useful in some situations, such as when the
-entry's working tree is stored on a portable device), add a file named
+entry's working tree is stored on a portable device), use the
+`git worktree lock` command, which adds a file named
'locked' to the entry's directory. The file contains the reason in
plain text. For example, if a linked working tree's `.git` file points
to `/path/main/.git/worktrees/test-next` then a file named
@@ -227,8 +253,6 @@ performed manually, such as:
- `remove` to remove a linked working tree and its administrative files (and
warn if the working tree is dirty)
- `mv` to move or rename a working tree and update its administrative files
-- `lock` to prevent automatic pruning of administrative files (for instance,
- for a working tree on a portable device)
GIT
---
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 8882a3e914..b40068bdfd 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -115,6 +115,7 @@ text file is normalized, its line endings are converted to LF in the
repository. To control what line ending style is used in the working
directory, use the `eol` attribute for a single file and the
`core.eol` configuration variable for all text files.
+Note that `core.autocrlf` overrides `core.eol`
Set::
@@ -130,8 +131,9 @@ Unset::
Set to string value "auto"::
When `text` is set to "auto", the path is marked for automatic
- end-of-line normalization. If Git decides that the content is
- text, its line endings are normalized to LF on checkin.
+ end-of-line conversion. If Git decides that the content is
+ text, its line endings are converted to LF on checkin.
+ When the file has been commited with CRLF, no conversion is done.
Unspecified::
@@ -146,7 +148,7 @@ unspecified.
^^^^^
This attribute sets a specific line-ending style to be used in the
-working directory. It enables end-of-line normalization without any
+working directory. It enables end-of-line conversion without any
content checks, effectively setting the `text` attribute.
Set to string value "crlf"::
@@ -186,9 +188,10 @@ the working directory, and prevent .jpg files from being normalized
regardless of their content.
------------------------
+* text=auto
*.txt text
-*.vcproj eol=crlf
-*.sh eol=lf
+*.vcproj text eol=crlf
+*.sh text eol=lf
*.jpg -text
------------------------
@@ -198,7 +201,7 @@ normalization in Git.
If you simply want to have CRLF line endings in your working directory
regardless of the repository you are working with, you can set the
-config variable "core.autocrlf" without changing any attributes.
+config variable "core.autocrlf" without using any attributes.
------------------------
[core]
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index d82e912e55..9565dc3fda 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -247,6 +247,15 @@ Both standard output and standard error output are forwarded to
'git send-pack' on the other end, so you can simply `echo` messages
for the user.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
+
[[update]]
update
~~~~~~
@@ -322,6 +331,15 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
directory in Git distribution, which implements sending commit
emails.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
+
[[post-update]]
post-update
~~~~~~~~~~~
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 29b19b992f..b95d67ec01 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -147,8 +147,14 @@ endif::git-rev-list[]
"U" for a good signature with unknown validity and "N" for no signature
- '%GS': show the name of the signer for a signed commit
- '%GK': show the key used to sign a signed commit
-- '%gD': reflog selector, e.g., `refs/stash@{1}`
-- '%gd': shortened reflog selector, e.g., `stash@{1}`
+- '%gD': reflog selector, e.g., `refs/stash@{1}` or
+ `refs/stash@{2 minutes ago`}; the format follows the rules described
+ for the `-g` option. The portion before the `@` is the refname as
+ given on the command line (so `git log -g refs/heads/master` would
+ yield `refs/heads/master@{0}`).
+- '%gd': shortened reflog selector; same as `%gD`, but the refname
+ portion is shortened for human readability (so `refs/heads/master`
+ becomes just `master`).
- '%gn': reflog identity name
- '%gN': reflog identity name (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1])
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index c5bd21812d..a779c9dfec 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -252,10 +252,25 @@ list.
+
With `--pretty` format other than `oneline` (for obvious reasons),
this causes the output to have two extra lines of information
-taken from the reflog. By default, 'commit@\{Nth}' notation is
-used in the output. When the starting commit is specified as
-'commit@\{now}', output also uses 'commit@\{timestamp}' notation
-instead. Under `--pretty=oneline`, the commit message is
+taken from the reflog. The reflog designator in the output may be shown
+as `ref@{Nth}` (where `Nth` is the reverse-chronological index in the
+reflog) or as `ref@{timestamp}` (with the timestamp for that entry),
+depending on a few rules:
++
+--
+1. If the starting point is specified as `ref@{Nth}`, show the index
+format.
++
+2. If the starting point was specified as `ref@{now}`, show the
+timestamp format.
++
+3. If neither was used, but `--date` was given on the command line, show
+the timestamp in the format requested by `--date`.
++
+4. Otherwise, show the index format.
+--
++
+Under `--pretty=oneline`, the commit message is
prefixed with this information on the same line.
This option cannot be combined with `--reverse`.
See also linkgit:git-reflog[1].
@@ -274,6 +289,10 @@ ifdef::git-rev-list[]
Try to speed up the traversal using the pack bitmap index (if
one is available). Note that when traversing with `--objects`,
trees and blobs will not have their associated path printed.
+
+--progress=<header>::
+ Show progress reports on stderr as objects are considered. The
+ `<header>` text will be printed with each progress update.
endif::git-rev-list[]
--
@@ -710,8 +729,8 @@ include::pretty-options.txt[]
`iso-local`), the user's local time zone is used instead.
+
`--date=relative` shows dates relative to the current time,
-e.g. ``2 hours ago''. The `-local` option cannot be used with
-`--raw` or `--relative`.
+e.g. ``2 hours ago''. The `-local` option has no effect for
+`--date=relative`.
+
`--date=local` is an alias for `--date=default-local`.
+
@@ -731,7 +750,18 @@ format, often found in email messages.
+
`--date=short` shows only the date, but not the time, in `YYYY-MM-DD` format.
+
-`--date=raw` shows the date in the internal raw Git format `%s %z` format.
+`--date=raw` shows the date as seconds since the epoch (1970-01-01
+00:00:00 UTC), followed by a space, and then the timezone as an offset
+from UTC (a `+` or `-` with four digits; the first two are hours, and
+the second two are minutes). I.e., as if the timestamp were formatted
+with `strftime("%s %z")`).
+Note that the `-local` option does not affect the seconds-since-epoch
+value (which is always measured in UTC), but does switch the accompanying
+timezone value.
++
+`--date=unix` shows the date as a Unix epoch timestamp (seconds since
+1970). As with `--raw`, this is always in UTC and therefore `-local`
+has no effect.
+
`--date=format:...` feeds the format `...` to your system `strftime`.
Use `--date=format:%c` to show the date in your system locale's
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index 8b36343802..736f3894a8 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -307,7 +307,7 @@ In multi_ack mode:
ready to make a packfile, it will blindly ACK all 'have' obj-ids
back to the client.
- * the server will then send a 'NACK' and then wait for another response
+ * the server will then send a 'NAK' and then wait for another response
from the client - either a 'done' or another list of 'have' lines.
In multi_ack_detailed mode:
@@ -454,7 +454,8 @@ The reference discovery phase is done nearly the same way as it is in the
fetching protocol. Each reference obj-id and name on the server is sent
in packet-line format to the client, followed by a flush-pkt. The only
real difference is that the capability listing is different - the only
-possible values are 'report-status', 'delete-refs' and 'ofs-delta'.
+possible values are 'report-status', 'delete-refs', 'ofs-delta' and
+'push-options'.
Reference Update Request and Packfile Transfer
----------------------------------------------
@@ -465,9 +466,10 @@ that it wants to update, it sends a line listing the obj-id currently on
the server, the obj-id the client would like to update it to and the name
of the reference.
-This list is followed by a flush-pkt and then the packfile that should
-contain all the objects that the server will need to complete the new
-references.
+This list is followed by a flush-pkt. Then the push options are transmitted
+one per packet followed by another flush-pkt. After that the packfile that
+should contain all the objects that the server will need to complete the new
+references will be sent.
----
update-request = *shallow ( command-list | push-cert ) [packfile]
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index eaab6b4ac7..4c28d3a8ae 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -253,6 +253,15 @@ atomic pushes. If the pushing client requests this capability, the server
will update the refs in one atomic transaction. Either all refs are
updated or none.
+push-options
+------------
+
+If the server sends the 'push-options' capability it is able to accept
+push options after the update commands have been sent, but before the
+packfile is streamed. If the pushing client requests this capability,
+the server will pass the options to the pre- and post- receive hooks
+that process this push request.
+
allow-tip-sha1-in-want
----------------------
diff --git a/Makefile b/Makefile
index 3b82ce0070..ad3624d95b 100644
--- a/Makefile
+++ b/Makefile
@@ -351,9 +351,12 @@ all::
# Define GMTIME_UNRELIABLE_ERRORS if your gmtime() function does not
# return NULL when it receives a bogus time_t.
#
-# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime in librt.
+# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime.
#
-# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC in librt.
+# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC.
+#
+# Define NEEDS_LIBRT if your platform requires linking with librt (glibc version
+# before 2.17) for clock_gettime and CLOCK_MONOTONIC.
#
# Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily
# compiles the following initialization:
@@ -718,6 +721,7 @@ LIB_OBJS += diff-lib.o
LIB_OBJS += diff-no-index.o
LIB_OBJS += diff.o
LIB_OBJS += dir.o
+LIB_OBJS += dir-iterator.o
LIB_OBJS += editor.o
LIB_OBJS += entry.o
LIB_OBJS += environment.o
@@ -783,6 +787,7 @@ LIB_OBJS += read-cache.o
LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
LIB_OBJS += refs/files-backend.o
+LIB_OBJS += refs/iterator.o
LIB_OBJS += ref-filter.o
LIB_OBJS += remote.o
LIB_OBJS += replace_object.o
@@ -1466,13 +1471,16 @@ endif
ifdef HAVE_CLOCK_GETTIME
BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME
- EXTLIBS += -lrt
endif
ifdef HAVE_CLOCK_MONOTONIC
BASIC_CFLAGS += -DHAVE_CLOCK_MONOTONIC
endif
+ifdef NEEDS_LIBRT
+ EXTLIBS += -lrt
+endif
+
ifdef HAVE_BSD_SYSCTL
BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
endif
@@ -2238,17 +2246,9 @@ perf: all
.PHONY: test perf
-t/helper/test-ctype$X: ctype.o
-
-t/helper/test-date$X: date.o ctype.o
-
-t/helper/test-delta$X: diff-delta.o patch-delta.o
-
-t/helper/test-line-buffer$X: vcs-svn/lib.a
-
-t/helper/test-parse-options$X: parse-options.o parse-options-cb.o
+t/helper/test-line-buffer$X: $(VCSSVN_LIB)
-t/helper/test-svn-fe$X: vcs-svn/lib.a
+t/helper/test-svn-fe$X: $(VCSSVN_LIB)
.PRECIOUS: $(TEST_OBJS)
diff --git a/builtin/blame.c b/builtin/blame.c
index 8fec0e1fb3..2e7b3030c6 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -2229,6 +2229,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)
static void verify_working_tree_path(struct commit *work_tree, const char *path)
{
struct commit_list *parents;
+ int pos;
for (parents = work_tree->parents; parents; parents = parents->next) {
const unsigned char *commit_sha1 = parents->item->object.oid.hash;
@@ -2239,7 +2240,14 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path)
sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
return;
}
- die("no such path '%s' in HEAD", path);
+
+ pos = cache_name_pos(path, strlen(path));
+ if (pos >= 0)
+ ; /* path is in the index */
+ else if (!strcmp(active_cache[-1 - pos]->name, path))
+ ; /* path is in the index, unmerged */
+ else
+ die("no such path '%s' in HEAD", path);
}
static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
@@ -2625,6 +2633,9 @@ parse_done:
case DATE_RAW:
blame_date_width = sizeof("1161298804 -0700");
break;
+ case DATE_UNIX:
+ blame_date_width = sizeof("1161298804");
+ break;
case DATE_SHORT:
blame_date_width = sizeof("2006-10-19");
break;
diff --git a/builtin/branch.c b/builtin/branch.c
index bf0672578f..7df05437f1 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -212,7 +212,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
die(_("Couldn't look up commit object for HEAD"));
}
for (i = 0; i < argc; i++, strbuf_release(&bname)) {
- const char *target;
+ char *target = NULL;
int flags = 0;
strbuf_branchname(&bname, argv[i]);
@@ -231,11 +231,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
}
}
- target = resolve_ref_unsafe(name,
- RESOLVE_REF_READING
- | RESOLVE_REF_NO_RECURSE
- | RESOLVE_REF_ALLOW_BAD_NAME,
- sha1, &flags);
+ target = resolve_refdup(name,
+ RESOLVE_REF_READING
+ | RESOLVE_REF_NO_RECURSE
+ | RESOLVE_REF_ALLOW_BAD_NAME,
+ sha1, &flags);
if (!target) {
error(remote_branch
? _("remote-tracking branch '%s' not found.")
@@ -248,7 +248,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
force)) {
ret = 1;
- continue;
+ goto next;
}
if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
@@ -258,7 +258,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
: _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
- continue;
+ goto next;
}
if (!quiet) {
printf(remote_branch
@@ -270,6 +270,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
: find_unique_abbrev(sha1, DEFAULT_ABBREV));
}
delete_branch_config(bname.buf);
+
+ next:
+ free(target);
}
free(name);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 618103fdee..2dfe6265f7 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -131,7 +131,7 @@ struct expand_data {
unsigned char sha1[20];
enum object_type type;
unsigned long size;
- unsigned long disk_size;
+ off_t disk_size;
const char *rest;
unsigned char delta_base_sha1[20];
@@ -191,7 +191,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
if (data->mark_query)
data->info.disk_sizep = &data->disk_size;
else
- strbuf_addf(sb, "%lu", data->disk_size);
+ strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size);
} else if (is_atom("rest", atom, len)) {
if (data->mark_query)
data->split_on_whitespace = 1;
diff --git a/builtin/clone.c b/builtin/clone.c
index 31ea247e3f..f044a8c27f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -624,13 +624,13 @@ static void update_remote_refs(const struct ref *refs,
const struct ref *rm = mapped_refs;
if (check_connectivity) {
- if (transport->progress)
- fprintf(stderr, _("Checking connectivity... "));
- if (check_everything_connected_with_transport(iterate_ref_map,
- 0, &rm, transport))
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
+
+ opt.transport = transport;
+ opt.progress = transport->progress;
+
+ if (check_connected(iterate_ref_map, &rm, &opt))
die(_("remote did not send all necessary objects"));
- if (transport->progress)
- fprintf(stderr, _("done.\n"));
}
if (refs) {
diff --git a/builtin/commit.c b/builtin/commit.c
index 1f6dbcd0d0..77e3dc8494 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1617,7 +1617,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
OPT_BOOL('o', "only", &only, N_("commit only specified files")),
- OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
+ OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
STATUS_FORMAT_SHORT),
diff --git a/builtin/fetch.c b/builtin/fetch.c
index d9ea6f392a..164623bb6f 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -729,7 +729,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
url = xstrdup("foreign");
rm = ref_map;
- if (check_everything_connected(iterate_ref_map, 0, &rm)) {
+ if (check_connected(iterate_ref_map, &rm, NULL)) {
rc = error(_("%s did not send all necessary objects\n"), url);
goto abort;
}
@@ -866,6 +866,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
static int quickfetch(struct ref *ref_map)
{
struct ref *rm = ref_map;
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
/*
* If we are deepening a shallow clone we already have these
@@ -876,7 +877,8 @@ static int quickfetch(struct ref *ref_map)
*/
if (depth)
return -1;
- return check_everything_connected(iterate_ref_map, 1, &rm);
+ opt.quiet = 1;
+ return check_connected(iterate_ref_map, &rm, &opt);
}
static int fetch_refs(struct transport *transport, struct ref *ref_map)
@@ -921,7 +923,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
for (ref = stale_refs; ref; ref = ref->next)
string_list_append(&refnames, ref->name);
- result = delete_refs(&refnames);
+ result = delete_refs(&refnames, 0);
string_list_clear(&refnames, 0);
}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 3f27456883..2de272ea36 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -13,6 +13,7 @@
#include "dir.h"
#include "progress.h"
#include "streaming.h"
+#include "decorate.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
@@ -35,11 +36,26 @@ static int write_lost_and_found;
static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
+static int name_objects;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
#define ERROR_REFS 010
+static const char *describe_object(struct object *obj)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ char *name = name_objects ?
+ lookup_decoration(fsck_walk_options.object_names, obj) : NULL;
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, oid_to_hex(&obj->oid));
+ if (name)
+ strbuf_addf(&buf, " (%s)", name);
+
+ return buf.buf;
+}
+
static int fsck_config(const char *var, const char *value, void *cb)
{
if (strcmp(var, "fsck.skiplist") == 0) {
@@ -67,7 +83,7 @@ static void objreport(struct object *obj, const char *msg_type,
const char *err)
{
fprintf(stderr, "%s in %s %s: %s\n",
- msg_type, typename(obj->type), oid_to_hex(&obj->oid), err);
+ msg_type, typename(obj->type), describe_object(obj), err);
}
static int objerror(struct object *obj, const char *err)
@@ -77,7 +93,8 @@ static int objerror(struct object *obj, const char *err)
return -1;
}
-static int fsck_error_func(struct object *obj, int type, const char *message)
+static int fsck_error_func(struct fsck_options *o,
+ struct object *obj, int type, const char *message)
{
objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message);
return (type == FSCK_WARN) ? 0 : 1;
@@ -97,7 +114,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
if (!obj) {
/* ... these references to parent->fld are safe here */
printf("broken link from %7s %s\n",
- typename(parent->type), oid_to_hex(&parent->oid));
+ typename(parent->type), describe_object(parent));
printf("broken link from %7s %s\n",
(type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
errors_found |= ERROR_REACHABLE;
@@ -114,9 +131,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
if (!(obj->flags & HAS_OBJ)) {
if (parent && !has_object_file(&obj->oid)) {
printf("broken link from %7s %s\n",
- typename(parent->type), oid_to_hex(&parent->oid));
+ typename(parent->type), describe_object(parent));
printf(" to %7s %s\n",
- typename(obj->type), oid_to_hex(&obj->oid));
+ typename(obj->type), describe_object(obj));
errors_found |= ERROR_REACHABLE;
}
return 1;
@@ -190,7 +207,8 @@ static void check_reachable_object(struct object *obj)
return; /* it is in pack - forget about it */
if (connectivity_only && has_object_file(&obj->oid))
return;
- printf("missing %s %s\n", typename(obj->type), oid_to_hex(&obj->oid));
+ printf("missing %s %s\n", typename(obj->type),
+ describe_object(obj));
errors_found |= ERROR_REACHABLE;
return;
}
@@ -215,7 +233,8 @@ static void check_unreachable_object(struct object *obj)
* since this is something that is prunable.
*/
if (show_unreachable) {
- printf("unreachable %s %s\n", typename(obj->type), oid_to_hex(&obj->oid));
+ printf("unreachable %s %s\n", typename(obj->type),
+ describe_object(obj));
return;
}
@@ -234,11 +253,11 @@ static void check_unreachable_object(struct object *obj)
if (!obj->used) {
if (show_dangling)
printf("dangling %s %s\n", typename(obj->type),
- oid_to_hex(&obj->oid));
+ describe_object(obj));
if (write_lost_and_found) {
char *filename = git_pathdup("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
- oid_to_hex(&obj->oid));
+ describe_object(obj));
FILE *f;
if (safe_create_leading_directories_const(filename)) {
@@ -252,7 +271,7 @@ static void check_unreachable_object(struct object *obj)
if (stream_blob_to_fd(fileno(f), obj->oid.hash, NULL, 1))
die_errno("Could not write '%s'", filename);
} else
- fprintf(f, "%s\n", oid_to_hex(&obj->oid));
+ fprintf(f, "%s\n", describe_object(obj));
if (fclose(f))
die_errno("Could not finish '%s'",
filename);
@@ -271,7 +290,7 @@ static void check_unreachable_object(struct object *obj)
static void check_object(struct object *obj)
{
if (verbose)
- fprintf(stderr, "Checking %s\n", oid_to_hex(&obj->oid));
+ fprintf(stderr, "Checking %s\n", describe_object(obj));
if (obj->flags & REACHABLE)
check_reachable_object(obj);
@@ -307,7 +326,7 @@ static int fsck_obj(struct object *obj)
if (verbose)
fprintf(stderr, "Checking %s %s\n",
- typename(obj->type), oid_to_hex(&obj->oid));
+ typename(obj->type), describe_object(obj));
if (fsck_walk(obj, NULL, &fsck_obj_options))
objerror(obj, "broken links");
@@ -326,15 +345,17 @@ static int fsck_obj(struct object *obj)
free_commit_buffer(commit);
if (!commit->parents && show_root)
- printf("root %s\n", oid_to_hex(&commit->object.oid));
+ printf("root %s\n", describe_object(&commit->object));
}
if (obj->type == OBJ_TAG) {
struct tag *tag = (struct tag *) obj;
if (show_tags && tag->tagged) {
- printf("tagged %s %s", typename(tag->tagged->type), oid_to_hex(&tag->tagged->oid));
- printf(" (%s) in %s\n", tag->tag, oid_to_hex(&tag->object.oid));
+ printf("tagged %s %s", typename(tag->tagged->type),
+ describe_object(tag->tagged));
+ printf(" (%s) in %s\n", tag->tag,
+ describe_object(&tag->object));
}
}
@@ -356,6 +377,10 @@ static int fsck_sha1(const unsigned char *sha1)
static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
unsigned long size, void *buffer, int *eaten)
{
+ /*
+ * Note, buffer may be NULL if type is OBJ_BLOB. See
+ * verify_packfile(), data_valid variable for details.
+ */
struct object *obj;
obj = parse_object_buffer(sha1, type, size, buffer, eaten);
if (!obj) {
@@ -368,13 +393,18 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
static int default_refs;
-static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
+static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
+ unsigned long timestamp)
{
struct object *obj;
if (!is_null_sha1(sha1)) {
obj = lookup_object(sha1);
if (obj) {
+ if (timestamp && name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj,
+ xstrfmt("%s@{%ld}", refname, timestamp));
obj->used = 1;
mark_object_reachable(obj);
} else {
@@ -394,8 +424,8 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
fprintf(stderr, "Checking reflog %s->%s\n",
sha1_to_hex(osha1), sha1_to_hex(nsha1));
- fsck_handle_reflog_sha1(refname, osha1);
- fsck_handle_reflog_sha1(refname, nsha1);
+ fsck_handle_reflog_sha1(refname, osha1, 0);
+ fsck_handle_reflog_sha1(refname, nsha1, timestamp);
return 0;
}
@@ -424,6 +454,9 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,
}
default_refs++;
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj, xstrdup(refname));
mark_object_reachable(obj);
return 0;
@@ -539,6 +572,9 @@ static int fsck_cache_tree(struct cache_tree *it)
return 1;
}
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj, xstrdup(":"));
mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
err |= objerror(obj, "non-tree in cache-tree");
@@ -567,6 +603,7 @@ static struct option fsck_opts[] = {
OPT_BOOL(0, "lost-found", &write_lost_and_found,
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
OPT_END(),
};
@@ -596,6 +633,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
include_reflogs = 0;
}
+ if (name_objects)
+ fsck_walk_options.object_names =
+ xcalloc(1, sizeof(struct decoration));
+
git_config(fsck_config, NULL);
fsck_head_link();
@@ -651,6 +692,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
continue;
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj, xstrdup(arg));
mark_object_reachable(obj);
heads++;
continue;
@@ -683,6 +727,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
continue;
obj = &blob->object;
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj,
+ xstrfmt(":%s", active_cache[i]->name));
mark_object_reachable(obj);
}
if (active_cache_tree)
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index e8c71fc1d2..1d2ea583a4 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -77,6 +77,7 @@ static int strict;
static int do_fsck_object;
static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
static int verbose;
+static int show_resolving_progress;
static int show_stat;
static int check_self_contained_and_connected;
@@ -338,10 +339,10 @@ static void parse_pack_header(void)
use(sizeof(struct pack_header));
}
-static NORETURN void bad_object(unsigned long offset, const char *format,
+static NORETURN void bad_object(off_t offset, const char *format,
...) __attribute__((format (printf, 2, 3)));
-static NORETURN void bad_object(unsigned long offset, const char *format, ...)
+static NORETURN void bad_object(off_t offset, const char *format, ...)
{
va_list params;
char buf[1024];
@@ -349,7 +350,8 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
va_start(params, format);
vsnprintf(buf, sizeof(buf), format, params);
va_end(params);
- die(_("pack has bad object at offset %lu: %s"), offset, buf);
+ die(_("pack has bad object at offset %"PRIuMAX": %s"),
+ (uintmax_t)offset, buf);
}
static inline struct thread_local *get_thread_data(void)
@@ -429,7 +431,7 @@ static int is_delta_type(enum object_type type)
return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
}
-static void *unpack_entry_data(unsigned long offset, unsigned long size,
+static void *unpack_entry_data(off_t offset, unsigned long size,
enum object_type type, unsigned char *sha1)
{
static char fixed_buf[8192];
@@ -549,13 +551,13 @@ static void *unpack_data(struct object_entry *obj,
void *cb_data)
{
off_t from = obj[0].idx.offset + obj[0].hdr_size;
- unsigned long len = obj[1].idx.offset - from;
+ off_t len = obj[1].idx.offset - from;
unsigned char *data, *inbuf;
git_zstream stream;
int status;
data = xmallocz(consume ? 64*1024 : obj->size);
- inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
+ inbuf = xmalloc((len < 64*1024) ? (int)len : 64*1024);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
@@ -563,15 +565,15 @@ static void *unpack_data(struct object_entry *obj,
stream.avail_out = consume ? 64*1024 : obj->size;
do {
- ssize_t n = (len < 64*1024) ? len : 64*1024;
+ ssize_t n = (len < 64*1024) ? (ssize_t)len : 64*1024;
n = xpread(get_thread_data()->pack_fd, inbuf, n, from);
if (n < 0)
die_errno(_("cannot pread pack file"));
if (!n)
- die(Q_("premature end of pack file, %lu byte missing",
- "premature end of pack file, %lu bytes missing",
- len),
- len);
+ die(Q_("premature end of pack file, %"PRIuMAX" byte missing",
+ "premature end of pack file, %"PRIuMAX" bytes missing",
+ (unsigned int)len),
+ (uintmax_t)len);
from += n;
len -= n;
stream.next_in = inbuf;
@@ -1190,7 +1192,7 @@ static void resolve_deltas(void)
qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry),
compare_ref_delta_entry);
- if (verbose)
+ if (verbose || show_resolving_progress)
progress = start_progress(_("Resolving deltas"),
nr_ref_deltas + nr_ofs_deltas);
@@ -1625,6 +1627,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
struct pack_idx_option opts;
unsigned char pack_sha1[20];
unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */
+ int report_end_of_input = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
@@ -1694,6 +1697,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
input_len = sizeof(*hdr);
} else if (!strcmp(arg, "-v")) {
verbose = 1;
+ } else if (!strcmp(arg, "--show-resolving-progress")) {
+ show_resolving_progress = 1;
+ } else if (!strcmp(arg, "--report-end-of-input")) {
+ report_end_of_input = 1;
} else if (!strcmp(arg, "-o")) {
if (index_name || (i+1) >= argc)
usage(index_pack_usage);
@@ -1751,6 +1758,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat));
ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
parse_pack_objects(pack_sha1);
+ if (report_end_of_input)
+ write_in_full(2, "\0", 1);
resolve_deltas();
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(ofs_deltas);
diff --git a/builtin/notes.c b/builtin/notes.c
index 0572051762..f848b89692 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -91,7 +91,7 @@ static const char * const git_notes_get_ref_usage[] = {
};
static const char note_template[] =
- "\nWrite/edit the notes for the following object:\n";
+ N_("Write/edit the notes for the following object:");
struct note_data {
int given;
@@ -179,7 +179,8 @@ static void prepare_note_data(const unsigned char *object, struct note_data *d,
copy_obj_to_fd(fd, old_note);
strbuf_addch(&buf, '\n');
- strbuf_add_commented_lines(&buf, note_template, strlen(note_template));
+ strbuf_add_commented_lines(&buf, "\n", strlen("\n"));
+ strbuf_add_commented_lines(&buf, _(note_template), strlen(_(note_template)));
strbuf_addch(&buf, '\n');
write_or_die(fd, buf.buf, buf.len);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index c4c2a3c79d..4a63398960 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -343,15 +343,15 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
}
/* Return 0 if we will bust the pack-size limit */
-static unsigned long write_reuse_object(struct sha1file *f, struct object_entry *entry,
- unsigned long limit, int usable_delta)
+static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry,
+ unsigned long limit, int usable_delta)
{
struct packed_git *p = entry->in_pack;
struct pack_window *w_curs = NULL;
struct revindex_entry *revidx;
off_t offset;
enum object_type type = entry->type;
- unsigned long datalen;
+ off_t datalen;
unsigned char header[10], dheader[10];
unsigned hdrlen;
@@ -417,11 +417,12 @@ static unsigned long write_reuse_object(struct sha1file *f, struct object_entry
}
/* Return 0 if we will bust the pack-size limit */
-static unsigned long write_object(struct sha1file *f,
- struct object_entry *entry,
- off_t write_offset)
+static off_t write_object(struct sha1file *f,
+ struct object_entry *entry,
+ off_t write_offset)
{
- unsigned long limit, len;
+ unsigned long limit;
+ off_t len;
int usable_delta, to_reuse;
if (!pack_to_stdout)
@@ -493,7 +494,7 @@ static enum write_one_status write_one(struct sha1file *f,
struct object_entry *e,
off_t *offset)
{
- unsigned long size;
+ off_t size;
int recursing;
/*
diff --git a/builtin/push.c b/builtin/push.c
index 4e9e4dbab2..3bb9d6b7e6 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -353,7 +353,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -376,6 +377,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -406,13 +410,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -500,6 +507,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
@@ -533,6 +543,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_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -563,7 +574,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ 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);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 15c323a7cd..92e1213ecc 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -44,10 +44,12 @@ static struct strbuf fsck_msg_types = STRBUF_INIT;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int advertise_atomic_push = 1;
+static int advertise_push_options;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
static int use_atomic;
+static int use_push_options;
static int quiet;
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
@@ -76,6 +78,13 @@ static long nonce_stamp_slop;
static unsigned long nonce_stamp_slop_limit;
static struct ref_transaction *transaction;
+static enum {
+ KEEPALIVE_NEVER = 0,
+ KEEPALIVE_AFTER_NUL,
+ KEEPALIVE_ALWAYS
+} use_keepalive;
+static int keepalive_in_sec = 5;
+
static enum deny_action parse_deny_action(const char *var, const char *value)
{
if (value) {
@@ -193,6 +202,16 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.advertisepushoptions") == 0) {
+ advertise_push_options = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.keepalive") == 0) {
+ keepalive_in_sec = git_config_int(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -211,6 +230,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
strbuf_addstr(&cap, " ofs-delta");
if (push_cert_nonce)
strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ if (advertise_push_options)
+ strbuf_addstr(&cap, " push-options");
strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
packet_write(1, "%s %s%c%s\n",
sha1_to_hex(sha1), path, 0, cap.buf);
@@ -319,10 +340,60 @@ static void rp_error(const char *err, ...)
static int copy_to_sideband(int in, int out, void *arg)
{
char data[128];
+ int keepalive_active = 0;
+
+ if (keepalive_in_sec <= 0)
+ use_keepalive = KEEPALIVE_NEVER;
+ if (use_keepalive == KEEPALIVE_ALWAYS)
+ keepalive_active = 1;
+
while (1) {
- ssize_t sz = xread(in, data, sizeof(data));
+ ssize_t sz;
+
+ if (keepalive_active) {
+ struct pollfd pfd;
+ int ret;
+
+ pfd.fd = in;
+ pfd.events = POLLIN;
+ ret = poll(&pfd, 1, 1000 * keepalive_in_sec);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ } else if (ret == 0) {
+ /* no data; send a keepalive packet */
+ static const char buf[] = "0005\1";
+ write_or_die(1, buf, sizeof(buf) - 1);
+ continue;
+ } /* else there is actual data to read */
+ }
+
+ sz = xread(in, data, sizeof(data));
if (sz <= 0)
break;
+
+ if (use_keepalive == KEEPALIVE_AFTER_NUL && !keepalive_active) {
+ const char *p = memchr(data, '\0', sz);
+ if (p) {
+ /*
+ * The NUL tells us to start sending keepalives. Make
+ * sure we send any other data we read along
+ * with it.
+ */
+ keepalive_active = 1;
+ send_sideband(1, 2, data, p - data, use_sideband);
+ send_sideband(1, 2, p + 1, sz - (p - data + 1), use_sideband);
+ continue;
+ }
+ }
+
+ /*
+ * Either we're not looking for a NUL signal, or we didn't see
+ * it yet; just pass along the data.
+ */
send_sideband(1, 2, data, sz, use_sideband);
}
close(in);
@@ -550,8 +621,16 @@ static void prepare_push_cert_sha1(struct child_process *proc)
}
}
+struct receive_hook_feed_state {
+ struct command *cmd;
+ int skip_broken;
+ struct strbuf buf;
+ const struct string_list *push_options;
+};
+
typedef int (*feed_fn)(void *, const char **, size_t *);
-static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
+static int run_and_feed_hook(const char *hook_name, feed_fn feed,
+ struct receive_hook_feed_state *feed_state)
{
struct child_process proc = CHILD_PROCESS_INIT;
struct async muxer;
@@ -567,6 +646,16 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
+ if (feed_state->push_options) {
+ int i;
+ for (i = 0; i < feed_state->push_options->nr; i++)
+ argv_array_pushf(&proc.env_array,
+ "GIT_PUSH_OPTION_%d=%s", i,
+ feed_state->push_options->items[i].string);
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
+ feed_state->push_options->nr);
+ } else
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -606,12 +695,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
return finish_command(&proc);
}
-struct receive_hook_feed_state {
- struct command *cmd;
- int skip_broken;
- struct strbuf buf;
-};
-
static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
{
struct receive_hook_feed_state *state = state_;
@@ -634,8 +717,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
return 0;
}
-static int run_receive_hook(struct command *commands, const char *hook_name,
- int skip_broken)
+static int run_receive_hook(struct command *commands,
+ const char *hook_name,
+ int skip_broken,
+ const struct string_list *push_options)
{
struct receive_hook_feed_state state;
int status;
@@ -646,6 +731,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
+ state.push_options = push_options;
status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
strbuf_release(&state.buf);
return status;
@@ -737,7 +823,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
{
static struct lock_file shallow_lock;
struct sha1_array extra = SHA1_ARRAY_INIT;
- const char *alt_file;
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
uint32_t mask = 1 << (cmd->index % 32);
int i;
@@ -749,9 +835,8 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
!delayed_reachability_test(si, i))
sha1_array_append(&extra, si->shallow->sha1[i]);
- setup_alternate_shallow(&shallow_lock, &alt_file, &extra);
- if (check_shallow_connected(command_singleton_iterator,
- 0, cmd, alt_file)) {
+ setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
+ if (check_connected(command_singleton_iterator, cmd, &opt)) {
rollback_lock_file(&shallow_lock);
sha1_array_clear(&extra);
return -1;
@@ -1160,8 +1245,8 @@ static void set_connectivity_errors(struct command *commands,
if (shallow_update && si->shallow_ref[cmd->index])
/* to be checked in update_shallow_ref() */
continue;
- if (!check_everything_connected(command_singleton_iterator,
- 0, &singleton))
+ if (!check_connected(command_singleton_iterator, &singleton,
+ NULL))
continue;
cmd->error_string = "missing necessary objects";
}
@@ -1316,11 +1401,15 @@ cleanup:
static void execute_commands(struct command *commands,
const char *unpacker_error,
- struct shallow_info *si)
+ struct shallow_info *si,
+ const struct string_list *push_options)
{
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
struct command *cmd;
unsigned char sha1[20];
struct iterate_data data;
+ struct async muxer;
+ int err_fd = 0;
if (unpacker_error) {
for (cmd = commands; cmd; cmd = cmd->next)
@@ -1328,14 +1417,28 @@ static void execute_commands(struct command *commands,
return;
}
+ if (use_sideband) {
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ if (!start_async(&muxer))
+ err_fd = muxer.in;
+ /* ...else, continue without relaying sideband */
+ }
+
data.cmds = commands;
data.si = si;
- if (check_everything_connected(iterate_receive_command_list, 0, &data))
+ opt.err_fd = err_fd;
+ opt.progress = err_fd && !quiet;
+ if (check_connected(iterate_receive_command_list, &data, &opt))
set_connectivity_errors(commands, si);
+ if (use_sideband)
+ finish_async(&muxer);
+
reject_updates_to_hidden(commands);
- if (run_receive_hook(commands, "pre-receive", 0)) {
+ if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "pre-receive hook declined";
@@ -1439,6 +1542,9 @@ static struct command *read_head_info(struct sha1_array *shallow)
if (advertise_atomic_push
&& parse_feature_request(feature_list, "atomic"))
use_atomic = 1;
+ if (advertise_push_options
+ && parse_feature_request(feature_list, "push-options"))
+ use_push_options = 1;
}
if (!strcmp(line, "push-cert")) {
@@ -1471,6 +1577,21 @@ static struct command *read_head_info(struct sha1_array *shallow)
return commands;
}
+static void read_push_options(struct string_list *options)
+{
+ while (1) {
+ char *line;
+ int len;
+
+ line = packet_read_line(0, &len);
+
+ if (!line)
+ break;
+
+ string_list_append(options, line);
+ }
+}
+
static const char *parse_pack_header(struct pack_header *hdr)
{
switch (read_pack_header(0, hdr)) {
@@ -1548,6 +1669,10 @@ static const char *unpack(int err_fd, struct shallow_info *si)
(uintmax_t)getpid(),
hostname);
+ if (!quiet && err_fd)
+ argv_array_push(&child.args, "--show-resolving-progress");
+ if (use_sideband)
+ argv_array_push(&child.args, "--report-end-of-input");
if (fsck_objects)
argv_array_pushf(&child.args, "--strict%s",
fsck_msg_types.buf);
@@ -1577,6 +1702,7 @@ static const char *unpack_with_sideband(struct shallow_info *si)
if (!use_sideband)
return unpack(0, si);
+ use_keepalive = KEEPALIVE_AFTER_NUL;
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
muxer.in = -1;
@@ -1756,6 +1882,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if ((commands = read_head_info(&shallow)) != NULL) {
const char *unpack_status = NULL;
+ struct string_list push_options = STRING_LIST_INIT_DUP;
+
+ if (use_push_options)
+ read_push_options(&push_options);
prepare_shallow_info(&si, &shallow);
if (!si.nr_ours && !si.nr_theirs)
@@ -1764,13 +1894,18 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unpack_status = unpack_with_sideband(&si);
update_shallow_info(commands, &si, &ref);
}
- execute_commands(commands, unpack_status, &si);
+ use_keepalive = KEEPALIVE_ALWAYS;
+ execute_commands(commands, unpack_status, &si,
+ &push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
report(commands, unpack_status);
- run_receive_hook(commands, "post-receive", 1);
+ run_receive_hook(commands, "post-receive", 1,
+ &push_options);
run_update_post_hook(commands);
+ if (push_options.nr)
+ string_list_clear(&push_options, 0);
if (auto_gc) {
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
diff --git a/builtin/remote.c b/builtin/remote.c
index a4d9c1a8e9..9f6a6b3a9c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -539,10 +539,6 @@ static int add_branch_for_removal(const char *refname,
return 0;
}
- /* make sure that symrefs are deleted */
- if (flags & REF_ISSYMREF)
- return unlink(git_path("%s", refname));
-
string_list_append(branches->branches, refname);
return 0;
@@ -788,7 +784,7 @@ static int rm(int argc, const char **argv)
strbuf_release(&buf);
if (!result)
- result = delete_refs(&branches);
+ result = delete_refs(&branches, REF_NODEREF);
string_list_clear(&branches, 0);
if (skipped.nr) {
@@ -1304,7 +1300,7 @@ static int prune_remote(const char *remote, int dry_run)
string_list_sort(&refs_to_prune);
if (!dry_run)
- result |= delete_refs(&refs_to_prune);
+ result |= delete_refs(&refs_to_prune, 0);
for_each_string_list_item(item, &states.stale) {
const char *refname = item->util;
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index b82bcc3436..0ba82b1635 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -9,6 +9,7 @@
#include "log-tree.h"
#include "graph.h"
#include "bisect.h"
+#include "progress.h"
static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -49,12 +50,17 @@ static const char rev_list_usage[] =
" --bisect-all"
;
+static struct progress *progress;
+static unsigned progress_counter;
+
static void finish_commit(struct commit *commit, void *data);
static void show_commit(struct commit *commit, void *data)
{
struct rev_list_info *info = data;
struct rev_info *revs = info->revs;
+ display_progress(progress, ++progress_counter);
+
if (info->flags & REV_LIST_QUIET) {
finish_commit(commit, data);
return;
@@ -190,6 +196,7 @@ static void show_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
finish_object(obj, name, cb_data);
+ display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET)
return;
show_object_with_name(stdout, obj, name);
@@ -276,6 +283,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int bisect_show_vars = 0;
int bisect_find_all = 0;
int use_bitmap_index = 0;
+ const char *show_progress = NULL;
git_config(git_default_config, NULL);
init_revisions(&revs, prefix);
@@ -325,6 +333,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
test_bitmap_walk(&revs);
return 0;
}
+ if (skip_prefix(arg, "--progress=", &arg)) {
+ show_progress = arg;
+ continue;
+ }
usage(rev_list_usage);
}
@@ -355,6 +367,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (bisect_list)
revs.limited = 1;
+ if (show_progress)
+ progress = start_progress_delay(show_progress, 0, 0, 2);
+
if (use_bitmap_index && !revs.prune) {
if (revs.count && !revs.left_right && !revs.cherry_mark) {
uint32_t commit_count;
@@ -392,6 +407,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
traverse_commit_list(&revs, show_commit, show_object, &info);
+ stop_progress(&progress);
+
if (revs.count) {
if (revs.left_right && revs.cherry_mark)
printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same);
diff --git a/builtin/revert.c b/builtin/revert.c
index 56a2c36669..4e693808b1 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -76,7 +76,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
int cmd = 0;
- struct option options[] = {
+ struct option base_options[] = {
OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'),
OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'),
@@ -91,13 +91,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
N_("option for merge strategy"), option_parse_x),
{ OPTION_STRING, 'S', "gpg-sign", &opts->gpg_sign, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
- OPT_END(),
- OPT_END(),
- OPT_END(),
- OPT_END(),
- OPT_END(),
- OPT_END(),
+ OPT_END()
};
+ struct option *options = base_options;
if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
@@ -108,8 +104,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")),
OPT_END(),
};
- if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
- die(_("program error"));
+ options = parse_options_concat(options, cp_extra);
}
argc = parse_options(argc, argv, NULL, options, usage_str,
diff --git a/builtin/rm.c b/builtin/rm.c
index 8abb0207fa..b2fee3e90a 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -387,6 +387,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
*/
if (!index_only) {
int removed = 0, gitmodules_modified = 0;
+ struct strbuf buf = STRBUF_INIT;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
@@ -398,7 +399,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
continue;
}
} else {
- struct strbuf buf = STRBUF_INIT;
+ strbuf_reset(&buf);
strbuf_addstr(&buf, path);
if (!remove_dir_recursively(&buf, 0)) {
removed = 1;
@@ -410,7 +411,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
/* Submodule was removed by user */
if (!remove_path_from_gitmodules(path))
gitmodules_modified = 1;
- strbuf_release(&buf);
/* Fallthrough and let remove_path() fail. */
}
}
@@ -421,6 +421,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!removed)
die_errno("git rm: '%s'", path);
}
+ strbuf_release(&buf);
if (gitmodules_modified)
stage_updated_gitmodules();
}
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index b22352b6e1..6f6d67a469 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -795,7 +795,7 @@ static int update_clone_task_finished(int result,
suc->failed_clones[suc->failed_clones_nr++] = ce;
return 0;
} else {
- idx = suc->current - suc->list.nr;
+ idx -= suc->list.nr;
ce = suc->failed_clones[idx];
strbuf_addf(err, _("Failed to clone '%s' a second time, aborting"),
ce->name);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index cce555cbbc..5a41788edb 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -14,7 +14,9 @@
static const char * const worktree_usage[] = {
N_("git worktree add [<options>] <path> [<branch>]"),
N_("git worktree list [<options>]"),
+ N_("git worktree lock [<options>] <path>"),
N_("git worktree prune [<options>]"),
+ N_("git worktree unlock <path>"),
NULL
};
@@ -462,6 +464,66 @@ static int list(int ac, const char **av, const char *prefix)
return 0;
}
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+ const char *reason = "", *old_reason;
+ struct option options[] = {
+ OPT_STRING(0, "reason", &reason, N_("string"),
+ N_("reason for locking")),
+ OPT_END()
+ };
+ struct worktree **worktrees, *wt;
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac != 1)
+ usage_with_options(worktree_usage, options);
+
+ worktrees = get_worktrees();
+ wt = find_worktree(worktrees, prefix, av[0]);
+ if (!wt)
+ die(_("'%s' is not a working tree"), av[0]);
+ if (is_main_worktree(wt))
+ die(_("The main working tree cannot be locked or unlocked"));
+
+ old_reason = is_worktree_locked(wt);
+ if (old_reason) {
+ if (*old_reason)
+ die(_("'%s' is already locked, reason: %s"),
+ av[0], old_reason);
+ die(_("'%s' is already locked"), av[0]);
+ }
+
+ write_file(git_common_path("worktrees/%s/locked", wt->id),
+ "%s", reason);
+ free_worktrees(worktrees);
+ return 0;
+}
+
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+ struct worktree **worktrees, *wt;
+ int ret;
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac != 1)
+ usage_with_options(worktree_usage, options);
+
+ worktrees = get_worktrees();
+ wt = find_worktree(worktrees, prefix, av[0]);
+ if (!wt)
+ die(_("'%s' is not a working tree"), av[0]);
+ if (is_main_worktree(wt))
+ die(_("The main working tree cannot be locked or unlocked"));
+ if (!is_worktree_locked(wt))
+ die(_("'%s' is not locked"), av[0]);
+ ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+ free_worktrees(worktrees);
+ return ret;
+}
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
@@ -478,5 +540,9 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
return prune(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "list"))
return list(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "lock"))
+ return lock_worktree(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "unlock"))
+ return unlock_worktree(ac - 1, av + 1, prefix);
usage_with_options(worktree_usage, options);
}
diff --git a/cache-tree.c b/cache-tree.c
index ddf0cc9f9a..f28b1f45a4 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -319,12 +319,13 @@ static int update_one(struct cache_tree *it,
i = 0;
while (i < entries) {
const struct cache_entry *ce = cache[i];
- struct cache_tree_sub *sub;
+ struct cache_tree_sub *sub = NULL;
const char *path, *slash;
int pathlen, entlen;
const unsigned char *sha1;
unsigned mode;
int expected_missing = 0;
+ int contains_ita = 0;
path = ce->name;
pathlen = ce_namelen(ce);
@@ -341,7 +342,8 @@ static int update_one(struct cache_tree *it,
i += sub->count;
sha1 = sub->cache_tree->sha1;
mode = S_IFDIR;
- if (sub->cache_tree->entry_count < 0) {
+ contains_ita = sub->cache_tree->entry_count < 0;
+ if (contains_ita) {
to_invalidate = 1;
expected_missing = 1;
}
@@ -375,11 +377,17 @@ static int update_one(struct cache_tree *it,
* they are not part of generated trees. Invalidate up
* to root to force cache-tree users to read elsewhere.
*/
- if (ce_intent_to_add(ce)) {
+ if (!sub && ce_intent_to_add(ce)) {
to_invalidate = 1;
continue;
}
+ /*
+ * "sub" can be an empty tree if all subentries are i-t-a.
+ */
+ if (contains_ita && !hashcmp(sha1, EMPTY_TREE_SHA1_BIN))
+ continue;
+
strbuf_grow(&buffer, entlen + 100);
strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0');
strbuf_add(&buffer, sha1, 20);
diff --git a/cache.h b/cache.h
index 40671d160a..7c8051be0a 100644
--- a/cache.h
+++ b/cache.h
@@ -632,6 +632,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
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);
extern void update_index_if_able(struct index_state *, struct lock_file *);
@@ -1003,6 +1004,11 @@ int adjust_shared_perm(const char *path);
* directory while we were working. To be robust against this kind of
* race, callers might want to try invoking the function again when it
* returns SCLD_VANISHED.
+ *
+ * safe_create_leading_directories() temporarily changes path while it
+ * is working but restores it before returning.
+ * safe_create_leading_directories_const() doesn't modify path, even
+ * temporarily.
*/
enum scld_error {
SCLD_OK = 0,
@@ -1224,7 +1230,8 @@ struct date_mode {
DATE_ISO8601_STRICT,
DATE_RFC2822,
DATE_STRFTIME,
- DATE_RAW
+ DATE_RAW,
+ DATE_UNIX
} type;
const char *strftime_fmt;
int local;
@@ -1515,7 +1522,7 @@ struct object_info {
/* Request */
enum object_type *typep;
unsigned long *sizep;
- unsigned long *disk_sizep;
+ off_t *disk_sizep;
unsigned char *delta_base_sha1;
struct strbuf *typename;
@@ -1566,10 +1573,18 @@ struct git_config_source {
const char *blob;
};
+enum config_origin_type {
+ CONFIG_ORIGIN_BLOB,
+ CONFIG_ORIGIN_FILE,
+ CONFIG_ORIGIN_STDIN,
+ CONFIG_ORIGIN_SUBMODULE_BLOB,
+ CONFIG_ORIGIN_CMDLINE
+};
+
typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *);
extern int git_config_from_file(config_fn_t fn, const char *, void *);
-extern int git_config_from_mem(config_fn_t fn, const char *origin_type,
+extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
const char *name, const char *buf, size_t len, void *data);
extern void git_config_push_parameter(const char *text);
extern int git_config_from_parameters(config_fn_t fn, void *data);
@@ -1713,7 +1728,7 @@ extern int ignore_untracked_cache_config;
struct key_value_info {
const char *filename;
int linenr;
- const char *origin_type;
+ enum config_origin_type origin_type;
enum config_scope scope;
};
diff --git a/compat/mingw.h b/compat/mingw.h
index 233933ee86..95e128fcfd 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -73,6 +73,9 @@ typedef int pid_t;
#ifndef ECONNABORTED
#define ECONNABORTED WSAECONNABORTED
#endif
+#ifndef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#endif
struct passwd {
char *pw_name;
diff --git a/config.c b/config.c
index bea937e4ec..584cacf148 100644
--- a/config.c
+++ b/config.c
@@ -24,7 +24,7 @@ struct config_source {
size_t pos;
} buf;
} u;
- const char *origin_type;
+ enum config_origin_type origin_type;
const char *name;
const char *path;
int die_on_error;
@@ -245,6 +245,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)
memset(&source, 0, sizeof(source));
source.prev = cf;
+ source.origin_type = CONFIG_ORIGIN_CMDLINE;
cf = &source;
/* sq_dequote will write over it */
@@ -453,6 +454,8 @@ static int git_parse_source(config_fn_t fn, void *data)
int comment = 0;
int baselen = 0;
struct strbuf *var = &cf->var;
+ int error_return = 0;
+ char *error_msg = NULL;
/* U+FEFF Byte Order Mark in UTF8 */
const char *bomptr = utf8_bom;
@@ -507,10 +510,40 @@ static int git_parse_source(config_fn_t fn, void *data)
if (get_value(fn, data, var) < 0)
break;
}
+
+ switch (cf->origin_type) {
+ case CONFIG_ORIGIN_BLOB:
+ error_msg = xstrfmt(_("bad config line %d in blob %s"),
+ cf->linenr, cf->name);
+ break;
+ case CONFIG_ORIGIN_FILE:
+ error_msg = xstrfmt(_("bad config line %d in file %s"),
+ cf->linenr, cf->name);
+ break;
+ case CONFIG_ORIGIN_STDIN:
+ error_msg = xstrfmt(_("bad config line %d in standard input"),
+ cf->linenr);
+ break;
+ case CONFIG_ORIGIN_SUBMODULE_BLOB:
+ error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
+ cf->linenr, cf->name);
+ break;
+ case CONFIG_ORIGIN_CMDLINE:
+ error_msg = xstrfmt(_("bad config line %d in command line %s"),
+ cf->linenr, cf->name);
+ break;
+ default:
+ error_msg = xstrfmt(_("bad config line %d in %s"),
+ cf->linenr, cf->name);
+ }
+
if (cf->die_on_error)
- die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
+ die("%s", error_msg);
else
- return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
+ error_return = error("%s", error_msg);
+
+ free(error_msg);
+ return error_return;
}
static int parse_unit_factor(const char *end, uintmax_t *val)
@@ -619,16 +652,47 @@ int git_parse_ulong(const char *value, unsigned long *ret)
NORETURN
static void die_bad_number(const char *name, const char *value)
{
- const char *reason = errno == ERANGE ?
- "out of range" :
- "invalid unit";
if (!value)
value = "";
- if (cf && cf->origin_type && cf->name)
- die(_("bad numeric config value '%s' for '%s' in %s %s: %s"),
- value, name, cf->origin_type, cf->name, reason);
- die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
+ if (!(cf && cf->name))
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s': out of range")
+ : _("bad numeric config value '%s' for '%s': invalid unit"),
+ value, name);
+
+ switch (cf->origin_type) {
+ case CONFIG_ORIGIN_BLOB:
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s' in blob %s: out of range")
+ : _("bad numeric config value '%s' for '%s' in blob %s: invalid unit"),
+ value, name, cf->name);
+ case CONFIG_ORIGIN_FILE:
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s' in file %s: out of range")
+ : _("bad numeric config value '%s' for '%s' in file %s: invalid unit"),
+ value, name, cf->name);
+ case CONFIG_ORIGIN_STDIN:
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s' in standard input: out of range")
+ : _("bad numeric config value '%s' for '%s' in standard input: invalid unit"),
+ value, name);
+ case CONFIG_ORIGIN_SUBMODULE_BLOB:
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s' in submodule-blob %s: out of range")
+ : _("bad numeric config value '%s' for '%s' in submodule-blob %s: invalid unit"),
+ value, name, cf->name);
+ case CONFIG_ORIGIN_CMDLINE:
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s' in command line %s: out of range")
+ : _("bad numeric config value '%s' for '%s' in command line %s: invalid unit"),
+ value, name, cf->name);
+ default:
+ die(errno == ERANGE
+ ? _("bad numeric config value '%s' for '%s' in %s: out of range")
+ : _("bad numeric config value '%s' for '%s' in %s: invalid unit"),
+ value, name, cf->name);
+ }
}
int git_config_int(const char *name, const char *value)
@@ -1105,7 +1169,8 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
}
static int do_config_from_file(config_fn_t fn,
- const char *origin_type, const char *name, const char *path, FILE *f,
+ const enum config_origin_type origin_type,
+ const char *name, const char *path, FILE *f,
void *data)
{
struct config_source top;
@@ -1124,7 +1189,7 @@ static int do_config_from_file(config_fn_t fn,
static int git_config_from_stdin(config_fn_t fn, void *data)
{
- return do_config_from_file(fn, "standard input", "", NULL, stdin, data);
+ return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, data);
}
int git_config_from_file(config_fn_t fn, const char *filename, void *data)
@@ -1135,14 +1200,14 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
f = fopen(filename, "r");
if (f) {
flockfile(f);
- ret = do_config_from_file(fn, "file", filename, filename, f, data);
+ ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
funlockfile(f);
fclose(f);
}
return ret;
}
-int git_config_from_mem(config_fn_t fn, const char *origin_type,
+int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
const char *name, const char *buf, size_t len, void *data)
{
struct config_source top;
@@ -1179,7 +1244,7 @@ static int git_config_from_blob_sha1(config_fn_t fn,
return error("reference '%s' does not point to a blob", name);
}
- ret = git_config_from_mem(fn, "blob", name, buf, size, data);
+ ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size, data);
free(buf);
return ret;
@@ -1390,12 +1455,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
if (cf->name) {
kv_info->filename = strintern(cf->name);
kv_info->linenr = cf->linenr;
- kv_info->origin_type = strintern(cf->origin_type);
+ kv_info->origin_type = cf->origin_type;
} else {
/* for values read from `git_config_from_parameters()` */
kv_info->filename = NULL;
kv_info->linenr = -1;
- kv_info->origin_type = NULL;
+ kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
}
kv_info->scope = current_parsing_scope;
si->util = kv_info;
@@ -2476,14 +2541,28 @@ int parse_config_key(const char *var,
const char *current_config_origin_type(void)
{
- const char *type;
+ int type;
if (current_config_kvi)
type = current_config_kvi->origin_type;
else if(cf)
type = cf->origin_type;
else
die("BUG: current_config_origin_type called outside config callback");
- return type ? type : "command line";
+
+ switch (type) {
+ case CONFIG_ORIGIN_BLOB:
+ return "blob";
+ case CONFIG_ORIGIN_FILE:
+ return "file";
+ case CONFIG_ORIGIN_STDIN:
+ return "standard input";
+ case CONFIG_ORIGIN_SUBMODULE_BLOB:
+ return "submodule-blob";
+ case CONFIG_ORIGIN_CMDLINE:
+ return "command line";
+ default:
+ die("BUG: unknown config origin type");
+ }
}
const char *current_config_name(void)
diff --git a/config.mak.uname b/config.mak.uname
index a88f13989a..17fed2f43a 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -36,6 +36,8 @@ ifeq ($(uname_S),Linux)
HAVE_DEV_TTY = YesPlease
HAVE_CLOCK_GETTIME = YesPlease
HAVE_CLOCK_MONOTONIC = YesPlease
+ # -lrt is needed for clock_gettime on glibc <= 2.16
+ NEEDS_LIBRT = YesPlease
HAVE_GETDELIM = YesPlease
SANE_TEXT_GREP=-a
endif
@@ -203,6 +205,7 @@ ifeq ($(uname_S),FreeBSD)
NO_STRTOUMAX = YesPlease
endif
PYTHON_PATH = /usr/local/bin/python
+ PERL_PATH = /usr/local/bin/perl
HAVE_PATHS_H = YesPlease
GMTIME_UNRELIABLE_ERRORS = UnfortunatelyYes
HAVE_BSD_SYSCTL = YesPlease
diff --git a/configure.ac b/configure.ac
index c279025747..aa9c91d20d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1108,14 +1108,19 @@ GIT_CONF_SUBST([HAVE_BSD_SYSCTL])
AC_DEFUN([PTHREADTEST_SRC], [
AC_LANG_PROGRAM([[
#include <pthread.h>
+static void *noop(void *ignore) { return ignore; }
]], [[
pthread_mutex_t test_mutex;
pthread_key_t test_key;
+ pthread_t th;
int retcode = 0;
+ void *ret = (void *)0;
retcode |= pthread_key_create(&test_key, (void *)0);
retcode |= pthread_mutex_init(&test_mutex,(void *)0);
retcode |= pthread_mutex_lock(&test_mutex);
retcode |= pthread_mutex_unlock(&test_mutex);
+ retcode |= pthread_create(&th, ret, noop, ret);
+ retcode |= pthread_join(th, &ret);
return retcode;
]])])
diff --git a/connected.c b/connected.c
index bf1b12e7ec..8e3e4b1dc1 100644
--- a/connected.c
+++ b/connected.c
@@ -4,10 +4,6 @@
#include "connected.h"
#include "transport.h"
-int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
-{
- return check_everything_connected_with_transport(fn, quiet, cb_data, NULL);
-}
/*
* If we feed all the commits we want to verify to this command
*
@@ -19,22 +15,27 @@ int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
*
* Returns 0 if everything is connected, non-zero otherwise.
*/
-static int check_everything_connected_real(sha1_iterate_fn fn,
- int quiet,
- void *cb_data,
- struct transport *transport,
- const char *shallow_file)
+int check_connected(sha1_iterate_fn fn, void *cb_data,
+ struct check_connected_options *opt)
{
struct child_process rev_list = CHILD_PROCESS_INIT;
- const char *argv[9];
+ struct check_connected_options defaults = CHECK_CONNECTED_INIT;
char commit[41];
unsigned char sha1[20];
- int err = 0, ac = 0;
+ int err = 0;
struct packed_git *new_pack = NULL;
+ struct transport *transport;
size_t base_len;
- if (fn(cb_data, sha1))
+ if (!opt)
+ opt = &defaults;
+ transport = opt->transport;
+
+ if (fn(cb_data, sha1)) {
+ if (opt->err_fd)
+ close(opt->err_fd);
return err;
+ }
if (transport && transport->smart_options &&
transport->smart_options->self_contained_and_connected &&
@@ -47,24 +48,28 @@ static int check_everything_connected_real(sha1_iterate_fn fn,
strbuf_release(&idx_file);
}
- if (shallow_file) {
- argv[ac++] = "--shallow-file";
- argv[ac++] = shallow_file;
+ if (opt->shallow_file) {
+ argv_array_push(&rev_list.args, "--shallow-file");
+ argv_array_push(&rev_list.args, opt->shallow_file);
}
- argv[ac++] = "rev-list";
- argv[ac++] = "--objects";
- argv[ac++] = "--stdin";
- argv[ac++] = "--not";
- argv[ac++] = "--all";
- if (quiet)
- argv[ac++] = "--quiet";
- argv[ac] = NULL;
+ argv_array_push(&rev_list.args,"rev-list");
+ argv_array_push(&rev_list.args, "--objects");
+ argv_array_push(&rev_list.args, "--stdin");
+ argv_array_push(&rev_list.args, "--not");
+ argv_array_push(&rev_list.args, "--all");
+ argv_array_push(&rev_list.args, "--quiet");
+ if (opt->progress)
+ argv_array_pushf(&rev_list.args, "--progress=%s",
+ _("Checking connectivity"));
- rev_list.argv = argv;
rev_list.git_cmd = 1;
rev_list.in = -1;
rev_list.no_stdout = 1;
- rev_list.no_stderr = quiet;
+ if (opt->err_fd)
+ rev_list.err = opt->err_fd;
+ else
+ rev_list.no_stderr = opt->quiet;
+
if (start_command(&rev_list))
return error(_("Could not run 'git rev-list'"));
@@ -98,19 +103,3 @@ static int check_everything_connected_real(sha1_iterate_fn fn,
sigchain_pop(SIGPIPE);
return finish_command(&rev_list) || err;
}
-
-int check_everything_connected_with_transport(sha1_iterate_fn fn,
- int quiet,
- void *cb_data,
- struct transport *transport)
-{
- return check_everything_connected_real(fn, quiet, cb_data,
- transport, NULL);
-}
-
-int check_shallow_connected(sha1_iterate_fn fn, int quiet, void *cb_data,
- const char *shallow_file)
-{
- return check_everything_connected_real(fn, quiet, cb_data,
- NULL, shallow_file);
-}
diff --git a/connected.h b/connected.h
index 071d408f38..afa48cc052 100644
--- a/connected.h
+++ b/connected.h
@@ -11,17 +11,42 @@ struct transport;
typedef int (*sha1_iterate_fn)(void *, unsigned char [20]);
/*
+ * Named-arguments struct for check_connected. All arguments are
+ * optional, and can be left to defaults as set by CHECK_CONNECTED_INIT.
+ */
+struct check_connected_options {
+ /* Avoid printing any errors to stderr. */
+ int quiet;
+
+ /* --shallow-file to pass to rev-list sub-process */
+ const char *shallow_file;
+
+ /* Transport whose objects we are checking, if available. */
+ struct transport *transport;
+
+ /*
+ * If non-zero, send error messages to this descriptor rather
+ * than stderr. The descriptor is closed before check_connected
+ * returns.
+ */
+ int err_fd;
+
+ /* If non-zero, show progress as we traverse the objects. */
+ int progress;
+};
+
+#define CHECK_CONNECTED_INIT { 0 }
+
+/*
* Make sure that our object store has all the commits necessary to
* connect the ancestry chain to some of our existing refs, and all
* the trees and blobs that these commits use.
*
* Return 0 if Ok, non zero otherwise (i.e. some missing objects)
+ *
+ * If "opt" is NULL, behaves as if CHECK_CONNECTED_INIT was passed.
*/
-extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data);
-extern int check_shallow_connected(sha1_iterate_fn, int quiet, void *cb_data,
- const char *shallow_file);
-extern int check_everything_connected_with_transport(sha1_iterate_fn, int quiet,
- void *cb_data,
- struct transport *transport);
+int check_connected(sha1_iterate_fn fn, void *cb_data,
+ struct check_connected_options *opt);
#endif /* CONNECTED_H */
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 37888f4e57..6a187bc11b 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1136,6 +1136,7 @@ _git_clone ()
--depth
--single-branch
--branch
+ --recurse-submodules
"
return
;;
@@ -2693,7 +2694,7 @@ _git_whatchanged ()
_git_worktree ()
{
- local subcommands="add list prune"
+ local subcommands="add list lock prune unlock"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
@@ -2705,6 +2706,9 @@ _git_worktree ()
list,--*)
__gitcomp "--porcelain"
;;
+ lock,--*)
+ __gitcomp "--reason"
+ ;;
prune,--*)
__gitcomp "--dry-run --expire --verbose"
;;
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
index 1cebc328cb..225e3f0954 100644
--- a/contrib/git-jump/README
+++ b/contrib/git-jump/README
@@ -29,7 +29,7 @@ Obviously this trivial case isn't that interesting; you could just open
`foo.c` yourself. But when you have many changes scattered across a
project, you can use the editor's support to "jump" from point to point.
-Git-jump can generate three types of interesting lists:
+Git-jump can generate four types of interesting lists:
1. The beginning of any diff hunks.
@@ -37,6 +37,8 @@ Git-jump can generate three types of interesting lists:
3. Any grep matches.
+ 4. Any whitespace errors detected by `git diff --check`.
+
Using git-jump
--------------
@@ -83,7 +85,7 @@ complete list of files and line numbers for each match.
Limitations
-----------
-This scripts was written and tested with vim. Given that the quickfix
+This script was written and tested with vim. Given that the quickfix
format is the same as what gcc produces, I expect emacs users have a
similar feature for iterating through the list, but I know nothing about
how to activate it.
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index dc90cd6379..427f206a45 100755
--- a/contrib/git-jump/git-jump
+++ b/contrib/git-jump/git-jump
@@ -12,6 +12,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.
+
+ws: elements are whitespace errors. Arguments are given to diff --check.
EOF
}
@@ -25,7 +27,7 @@ mode_diff() {
perl -ne '
if (m{^\+\+\+ (.*)}) { $file = $1; next }
defined($file) or next;
- if (m/^@@ .*\+(\d+)/) { $line = $1; next }
+ if (m/^@@ .*?\+(\d+)/) { $line = $1; next }
defined($line) or next;
if (/^ /) { $line++; next }
if (/^[-+]\s*(.*)/) {
@@ -55,6 +57,10 @@ mode_grep() {
'
}
+mode_ws() {
+ git diff --check "$@"
+}
+
if test $# -lt 1; then
usage >&2
exit 1
diff --git a/contrib/persistent-https/Makefile b/contrib/persistent-https/Makefile
index 92baa3beee..52b84ba3d4 100644
--- a/contrib/persistent-https/Makefile
+++ b/contrib/persistent-https/Makefile
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-BUILD_LABEL=$(shell date +"%s")
+BUILD_LABEL=$(shell cut -d" " -f3 ../../GIT-VERSION-FILE)
TAR_OUT=$(shell go env GOOS)_$(shell go env GOARCH).tar.gz
all: git-remote-persistent-https git-remote-persistent-https--proxy \
@@ -25,8 +25,10 @@ git-remote-persistent-http: git-remote-persistent-https
ln -f -s git-remote-persistent-https git-remote-persistent-http
git-remote-persistent-https:
+ case $$(go version) in \
+ "go version go"1.[0-5].*) EQ=" " ;; *) EQ="=" ;; esac && \
go build -o git-remote-persistent-https \
- -ldflags "-X main._BUILD_EMBED_LABEL $(BUILD_LABEL)"
+ -ldflags "-X main._BUILD_EMBED_LABEL$${EQ}$(BUILD_LABEL)"
clean:
rm -f git-remote-persistent-http* *.tar.gz
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 7a39b30ad0..dec085a235 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -4,8 +4,9 @@
#
# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
#
-if [ $# -eq 0 ]; then
- set -- -h
+if test $# -eq 0
+then
+ set -- -h
fi
OPTS_SPEC="\
git subtree add --prefix=<prefix> <commit>
@@ -48,89 +49,144 @@ squash=
message=
prefix=
-debug()
-{
- if [ -n "$debug" ]; then
+debug () {
+ if test -n "$debug"
+ then
printf "%s\n" "$*" >&2
fi
}
-say()
-{
- if [ -z "$quiet" ]; then
+say () {
+ if test -z "$quiet"
+ then
printf "%s\n" "$*" >&2
fi
}
-progress()
-{
- if [ -z "$quiet" ]; then
+progress () {
+ if test -z "$quiet"
+ then
printf "%s\r" "$*" >&2
fi
}
-assert()
-{
- if "$@"; then
- :
- else
+assert () {
+ if ! "$@"
+ then
die "assertion failed: " "$@"
fi
}
-#echo "Options: $*"
-
-while [ $# -gt 0 ]; do
+while test $# -gt 0
+do
opt="$1"
shift
+
case "$opt" in
- -q) quiet=1 ;;
- -d) debug=1 ;;
- --annotate) annotate="$1"; shift ;;
- --no-annotate) annotate= ;;
- -b) branch="$1"; shift ;;
- -P) prefix="${1%/}"; shift ;;
- -m) message="$1"; shift ;;
- --no-prefix) prefix= ;;
- --onto) onto="$1"; shift ;;
- --no-onto) onto= ;;
- --rejoin) rejoin=1 ;;
- --no-rejoin) rejoin= ;;
- --ignore-joins) ignore_joins=1 ;;
- --no-ignore-joins) ignore_joins= ;;
- --squash) squash=1 ;;
- --no-squash) squash= ;;
- --) break ;;
- *) die "Unexpected option: $opt" ;;
+ -q)
+ quiet=1
+ ;;
+ -d)
+ debug=1
+ ;;
+ --annotate)
+ annotate="$1"
+ shift
+ ;;
+ --no-annotate)
+ annotate=
+ ;;
+ -b)
+ branch="$1"
+ shift
+ ;;
+ -P)
+ prefix="${1%/}"
+ shift
+ ;;
+ -m)
+ message="$1"
+ shift
+ ;;
+ --no-prefix)
+ prefix=
+ ;;
+ --onto)
+ onto="$1"
+ shift
+ ;;
+ --no-onto)
+ onto=
+ ;;
+ --rejoin)
+ rejoin=1
+ ;;
+ --no-rejoin)
+ rejoin=
+ ;;
+ --ignore-joins)
+ ignore_joins=1
+ ;;
+ --no-ignore-joins)
+ ignore_joins=
+ ;;
+ --squash)
+ squash=1
+ ;;
+ --no-squash)
+ squash=
+ ;;
+ --)
+ break
+ ;;
+ *)
+ die "Unexpected option: $opt"
+ ;;
esac
done
command="$1"
shift
+
case "$command" in
- add|merge|pull) default= ;;
- split|push) default="--default HEAD" ;;
- *) die "Unknown command '$command'" ;;
+add|merge|pull)
+ default=
+ ;;
+split|push)
+ default="--default HEAD"
+ ;;
+*)
+ die "Unknown command '$command'"
+ ;;
esac
-if [ -z "$prefix" ]; then
+if test -z "$prefix"
+then
die "You must provide the --prefix option."
fi
case "$command" in
- add) [ -e "$prefix" ] &&
- die "prefix '$prefix' already exists." ;;
- *) [ -e "$prefix" ] ||
- die "'$prefix' does not exist; use 'git subtree add'" ;;
+add)
+ test -e "$prefix" &&
+ die "prefix '$prefix' already exists."
+ ;;
+*)
+ test -e "$prefix" ||
+ die "'$prefix' does not exist; use 'git subtree add'"
+ ;;
esac
dir="$(dirname "$prefix/.")"
-if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
+if test "$command" != "pull" &&
+ test "$command" != "add" &&
+ test "$command" != "push"
+then
revs=$(git rev-parse $default --revs-only "$@") || exit $?
- dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
- if [ -n "$dirs" ]; then
+ dirs=$(git rev-parse --no-revs --no-flags "$@") || exit $?
+ if test -n "$dirs"
+ then
die "Error: Use --prefix instead of bare filenames."
fi
fi
@@ -142,78 +198,82 @@ debug "dir: {$dir}"
debug "opts: {$*}"
debug
-cache_setup()
-{
+cache_setup () {
cachedir="$GIT_DIR/subtree-cache/$$"
- rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
- mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
- mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
+ rm -rf "$cachedir" ||
+ die "Can't delete old cachedir: $cachedir"
+ mkdir -p "$cachedir" ||
+ die "Can't create new cachedir: $cachedir"
+ mkdir -p "$cachedir/notree" ||
+ die "Can't create new cachedir: $cachedir/notree"
debug "Using cachedir: $cachedir" >&2
}
-cache_get()
-{
- for oldrev in $*; do
- if [ -r "$cachedir/$oldrev" ]; then
+cache_get () {
+ for oldrev in "$@"
+ do
+ if test -r "$cachedir/$oldrev"
+ then
read newrev <"$cachedir/$oldrev"
echo $newrev
fi
done
}
-cache_miss()
-{
- for oldrev in $*; do
- if [ ! -r "$cachedir/$oldrev" ]; then
+cache_miss () {
+ for oldrev in "$@"
+ do
+ if ! test -r "$cachedir/$oldrev"
+ then
echo $oldrev
fi
done
}
-check_parents()
-{
- missed=$(cache_miss $*)
- for miss in $missed; do
- if [ ! -r "$cachedir/notree/$miss" ]; then
+check_parents () {
+ missed=$(cache_miss "$@")
+ for miss in $missed
+ do
+ if ! test -r "$cachedir/notree/$miss"
+ then
debug " incorrect order: $miss"
fi
done
}
-set_notree()
-{
+set_notree () {
echo "1" > "$cachedir/notree/$1"
}
-cache_set()
-{
+cache_set () {
oldrev="$1"
newrev="$2"
- if [ "$oldrev" != "latest_old" \
- -a "$oldrev" != "latest_new" \
- -a -e "$cachedir/$oldrev" ]; then
+ if test "$oldrev" != "latest_old" &&
+ test "$oldrev" != "latest_new" &&
+ test -e "$cachedir/$oldrev"
+ then
die "cache for $oldrev already exists!"
fi
echo "$newrev" >"$cachedir/$oldrev"
}
-rev_exists()
-{
- if git rev-parse "$1" >/dev/null 2>&1; then
+rev_exists () {
+ if git rev-parse "$1" >/dev/null 2>&1
+ then
return 0
else
return 1
fi
}
-rev_is_descendant_of_branch()
-{
+rev_is_descendant_of_branch () {
newrev="$1"
branch="$2"
- branch_hash=$(git rev-parse $branch)
- match=$(git rev-list -1 $branch_hash ^$newrev)
+ branch_hash=$(git rev-parse "$branch")
+ match=$(git rev-list -1 "$branch_hash" "^$newrev")
- if [ -z "$match" ]; then
+ if test -z "$match"
+ then
return 0
else
return 1
@@ -223,15 +283,14 @@ rev_is_descendant_of_branch()
# if a commit doesn't have a parent, this might not work. But we only want
# to remove the parent from the rev-list, and since it doesn't exist, it won't
# be there anyway, so do nothing in that case.
-try_remove_previous()
-{
- if rev_exists "$1^"; then
+try_remove_previous () {
+ if rev_exists "$1^"
+ then
echo "^$1^"
fi
}
-find_latest_squash()
-{
+find_latest_squash () {
debug "Looking for latest squash ($dir)..."
dir="$1"
sq=
@@ -239,37 +298,43 @@ find_latest_squash()
sub=
git log --grep="^git-subtree-dir: $dir/*\$" \
--pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
- while read a b junk; do
+ while read a b junk
+ do
debug "$a $b $junk"
debug "{{$sq/$main/$sub}}"
case "$a" in
- START) sq="$b" ;;
- git-subtree-mainline:) main="$b" ;;
- git-subtree-split:)
- sub="$(git rev-parse "$b^0")" ||
- die "could not rev-parse split hash $b from commit $sq"
- ;;
- END)
- if [ -n "$sub" ]; then
- if [ -n "$main" ]; then
- # a rejoin commit?
- # Pretend its sub was a squash.
- sq="$sub"
- fi
- debug "Squash found: $sq $sub"
- echo "$sq" "$sub"
- break
+ START)
+ sq="$b"
+ ;;
+ git-subtree-mainline:)
+ main="$b"
+ ;;
+ git-subtree-split:)
+ sub="$(git rev-parse "$b^0")" ||
+ die "could not rev-parse split hash $b from commit $sq"
+ ;;
+ END)
+ if test -n "$sub"
+ then
+ if test -n "$main"
+ then
+ # a rejoin commit?
+ # Pretend its sub was a squash.
+ sq="$sub"
fi
- sq=
- main=
- sub=
- ;;
+ debug "Squash found: $sq $sub"
+ echo "$sq" "$sub"
+ break
+ fi
+ sq=
+ main=
+ sub=
+ ;;
esac
done
}
-find_existing_splits()
-{
+find_existing_splits () {
debug "Looking for prior splits..."
dir="$1"
revs="$2"
@@ -277,37 +342,43 @@ find_existing_splits()
sub=
git log --grep="^git-subtree-dir: $dir/*\$" \
--pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
- while read a b junk; do
+ while read a b junk
+ do
case "$a" in
- START) sq="$b" ;;
- git-subtree-mainline:) main="$b" ;;
- git-subtree-split:)
- sub="$(git rev-parse "$b^0")" ||
- die "could not rev-parse split hash $b from commit $sq"
- ;;
- END)
- debug " Main is: '$main'"
- if [ -z "$main" -a -n "$sub" ]; then
- # squash commits refer to a subtree
- debug " Squash: $sq from $sub"
- cache_set "$sq" "$sub"
- fi
- if [ -n "$main" -a -n "$sub" ]; then
- debug " Prior: $main -> $sub"
- cache_set $main $sub
- cache_set $sub $sub
- try_remove_previous "$main"
- try_remove_previous "$sub"
- fi
- main=
- sub=
- ;;
+ START)
+ sq="$b"
+ ;;
+ git-subtree-mainline:)
+ main="$b"
+ ;;
+ git-subtree-split:)
+ sub="$(git rev-parse "$b^0")" ||
+ die "could not rev-parse split hash $b from commit $sq"
+ ;;
+ END)
+ debug " Main is: '$main'"
+ if test -z "$main" -a -n "$sub"
+ then
+ # squash commits refer to a subtree
+ debug " Squash: $sq from $sub"
+ cache_set "$sq" "$sub"
+ fi
+ if test -n "$main" -a -n "$sub"
+ then
+ debug " Prior: $main -> $sub"
+ cache_set $main $sub
+ cache_set $sub $sub
+ try_remove_previous "$main"
+ try_remove_previous "$sub"
+ fi
+ main=
+ sub=
+ ;;
esac
done
}
-copy_commit()
-{
+copy_commit () {
# We're going to set some environment vars here, so
# do it in a subshell to get rid of them safely later
debug copy_commit "{$1}" "{$2}" "{$3}"
@@ -325,66 +396,69 @@ copy_commit()
GIT_COMMITTER_NAME \
GIT_COMMITTER_EMAIL \
GIT_COMMITTER_DATE
- (printf "%s" "$annotate"; cat ) |
+ (
+ printf "%s" "$annotate"
+ cat
+ ) |
git commit-tree "$2" $3 # reads the rest of stdin
) || die "Can't copy commit $1"
}
-add_msg()
-{
+add_msg () {
dir="$1"
latest_old="$2"
latest_new="$3"
- if [ -n "$message" ]; then
+ if test -n "$message"
+ then
commit_message="$message"
else
commit_message="Add '$dir/' from commit '$latest_new'"
fi
cat <<-EOF
$commit_message
-
+
git-subtree-dir: $dir
git-subtree-mainline: $latest_old
git-subtree-split: $latest_new
EOF
}
-add_squashed_msg()
-{
- if [ -n "$message" ]; then
+add_squashed_msg () {
+ if test -n "$message"
+ then
echo "$message"
else
echo "Merge commit '$1' as '$2'"
fi
}
-rejoin_msg()
-{
+rejoin_msg () {
dir="$1"
latest_old="$2"
latest_new="$3"
- if [ -n "$message" ]; then
+ if test -n "$message"
+ then
commit_message="$message"
else
commit_message="Split '$dir/' into commit '$latest_new'"
fi
cat <<-EOF
$commit_message
-
+
git-subtree-dir: $dir
git-subtree-mainline: $latest_old
git-subtree-split: $latest_new
EOF
}
-squash_msg()
-{
+squash_msg () {
dir="$1"
oldsub="$2"
newsub="$3"
newsub_short=$(git rev-parse --short "$newsub")
-
- if [ -n "$oldsub" ]; then
+
+ if test -n "$oldsub"
+ then
oldsub_short=$(git rev-parse --short "$oldsub")
echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
echo
@@ -393,41 +467,41 @@ squash_msg()
else
echo "Squashed '$dir/' content from commit $newsub_short"
fi
-
+
echo
echo "git-subtree-dir: $dir"
echo "git-subtree-split: $newsub"
}
-toptree_for_commit()
-{
+toptree_for_commit () {
commit="$1"
git log -1 --pretty=format:'%T' "$commit" -- || exit $?
}
-subtree_for_commit()
-{
+subtree_for_commit () {
commit="$1"
dir="$2"
git ls-tree "$commit" -- "$dir" |
- while read mode type tree name; do
- assert [ "$name" = "$dir" ]
- assert [ "$type" = "tree" -o "$type" = "commit" ]
- [ "$type" = "commit" ] && continue # ignore submodules
+ while read mode type tree name
+ do
+ assert test "$name" = "$dir"
+ assert test "$type" = "tree" -o "$type" = "commit"
+ test "$type" = "commit" && continue # ignore submodules
echo $tree
break
done
}
-tree_changed()
-{
+tree_changed () {
tree=$1
shift
- if [ $# -ne 1 ]; then
+ if test $# -ne 1
+ then
return 0 # weird parents, consider it changed
else
ptree=$(toptree_for_commit $1)
- if [ "$ptree" != "$tree" ]; then
+ if test "$ptree" != "$tree"
+ then
return 0 # changed
else
return 1 # not changed
@@ -435,118 +509,127 @@ tree_changed()
fi
}
-new_squash_commit()
-{
+new_squash_commit () {
old="$1"
oldsub="$2"
newsub="$3"
tree=$(toptree_for_commit $newsub) || exit $?
- if [ -n "$old" ]; then
- squash_msg "$dir" "$oldsub" "$newsub" |
- git commit-tree "$tree" -p "$old" || exit $?
+ if test -n "$old"
+ then
+ squash_msg "$dir" "$oldsub" "$newsub" |
+ git commit-tree "$tree" -p "$old" || exit $?
else
squash_msg "$dir" "" "$newsub" |
- git commit-tree "$tree" || exit $?
+ git commit-tree "$tree" || exit $?
fi
}
-copy_or_skip()
-{
+copy_or_skip () {
rev="$1"
tree="$2"
newparents="$3"
- assert [ -n "$tree" ]
+ assert test -n "$tree"
identical=
nonidentical=
p=
gotparents=
- for parent in $newparents; do
+ for parent in $newparents
+ do
ptree=$(toptree_for_commit $parent) || exit $?
- [ -z "$ptree" ] && continue
- if [ "$ptree" = "$tree" ]; then
+ test -z "$ptree" && continue
+ if test "$ptree" = "$tree"
+ then
# an identical parent could be used in place of this rev.
identical="$parent"
else
nonidentical="$parent"
fi
-
+
# sometimes both old parents map to the same newparent;
# eliminate duplicates
is_new=1
- for gp in $gotparents; do
- if [ "$gp" = "$parent" ]; then
+ for gp in $gotparents
+ do
+ if test "$gp" = "$parent"
+ then
is_new=
break
fi
done
- if [ -n "$is_new" ]; then
+ if test -n "$is_new"
+ then
gotparents="$gotparents $parent"
p="$p -p $parent"
fi
done
copycommit=
- if [ -n "$identical" ] && [ -n "$nonidentical" ]; then
+ if test -n "$identical" && test -n "$nonidentical"
+ then
extras=$(git rev-list --count $identical..$nonidentical)
- if [ "$extras" -ne 0 ]; then
+ if test "$extras" -ne 0
+ then
# we need to preserve history along the other branch
copycommit=1
fi
fi
- if [ -n "$identical" ] && [ -z "$copycommit" ]; then
+ if test -n "$identical" && test -z "$copycommit"
+ then
echo $identical
else
- copy_commit $rev $tree "$p" || exit $?
+ copy_commit "$rev" "$tree" "$p" || exit $?
fi
}
-ensure_clean()
-{
- if ! git diff-index HEAD --exit-code --quiet 2>&1; then
+ensure_clean () {
+ if ! git diff-index HEAD --exit-code --quiet 2>&1
+ then
die "Working tree has modifications. Cannot add."
fi
- if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
+ if ! git diff-index --cached HEAD --exit-code --quiet 2>&1
+ then
die "Index has modifications. Cannot add."
fi
}
-ensure_valid_ref_format()
-{
+ensure_valid_ref_format () {
git check-ref-format "refs/heads/$1" ||
- die "'$1' does not look like a ref"
+ die "'$1' does not look like a ref"
}
-cmd_add()
-{
- if [ -e "$dir" ]; then
+cmd_add () {
+ if test -e "$dir"
+ then
die "'$dir' already exists. Cannot add."
fi
ensure_clean
-
- if [ $# -eq 1 ]; then
- git rev-parse -q --verify "$1^{commit}" >/dev/null ||
- die "'$1' does not refer to a commit"
-
- "cmd_add_commit" "$@"
- elif [ $# -eq 2 ]; then
- # Technically we could accept a refspec here but we're
- # just going to turn around and add FETCH_HEAD under the
- # specified directory. Allowing a refspec might be
- # misleading because we won't do anything with any other
- # branches fetched via the refspec.
- ensure_valid_ref_format "$2"
-
- "cmd_add_repository" "$@"
+
+ if test $# -eq 1
+ then
+ git rev-parse -q --verify "$1^{commit}" >/dev/null ||
+ die "'$1' does not refer to a commit"
+
+ cmd_add_commit "$@"
+
+ elif test $# -eq 2
+ then
+ # Technically we could accept a refspec here but we're
+ # just going to turn around and add FETCH_HEAD under the
+ # specified directory. Allowing a refspec might be
+ # misleading because we won't do anything with any other
+ # branches fetched via the refspec.
+ ensure_valid_ref_format "$2"
+
+ cmd_add_repository "$@"
else
- say "error: parameters were '$@'"
- die "Provide either a commit or a repository and commit."
+ say "error: parameters were '$@'"
+ die "Provide either a commit or a repository and commit."
fi
}
-cmd_add_repository()
-{
+cmd_add_repository () {
echo "git fetch" "$@"
repository=$1
refspec=$2
@@ -556,60 +639,63 @@ cmd_add_repository()
cmd_add_commit "$@"
}
-cmd_add_commit()
-{
+cmd_add_commit () {
revs=$(git rev-parse $default --revs-only "$@") || exit $?
set -- $revs
rev="$1"
-
+
debug "Adding $dir as '$rev'..."
git read-tree --prefix="$dir" $rev || exit $?
git checkout -- "$dir" || exit $?
tree=$(git write-tree) || exit $?
-
+
headrev=$(git rev-parse HEAD) || exit $?
- if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
+ if test -n "$headrev" && test "$headrev" != "$rev"
+ then
headp="-p $headrev"
else
headp=
fi
-
- if [ -n "$squash" ]; then
+
+ if test -n "$squash"
+ then
rev=$(new_squash_commit "" "" "$rev") || exit $?
commit=$(add_squashed_msg "$rev" "$dir" |
- git commit-tree $tree $headp -p "$rev") || exit $?
+ git commit-tree "$tree" $headp -p "$rev") || exit $?
else
revp=$(peel_committish "$rev") &&
- commit=$(add_msg "$dir" "$headrev" "$rev" |
- git commit-tree $tree $headp -p "$revp") || exit $?
+ commit=$(add_msg "$dir" $headrev "$rev" |
+ git commit-tree "$tree" $headp -p "$revp") || exit $?
fi
git reset "$commit" || exit $?
-
+
say "Added dir '$dir'"
}
-cmd_split()
-{
+cmd_split () {
debug "Splitting $dir..."
cache_setup || exit $?
-
- if [ -n "$onto" ]; then
+
+ if test -n "$onto"
+ then
debug "Reading history for --onto=$onto..."
git rev-list $onto |
- while read rev; do
+ while read rev
+ do
# the 'onto' history is already just the subdir, so
# any parent we find there can be used verbatim
debug " cache: $rev"
- cache_set $rev $rev
+ cache_set "$rev" "$rev"
done
fi
-
- if [ -n "$ignore_joins" ]; then
+
+ if test -n "$ignore_joins"
+ then
unrevs=
else
unrevs="$(find_existing_splits "$dir" "$revs")"
fi
-
+
# We can't restrict rev-list to only $dir here, because some of our
# parents have the $dir contents the root, and those won't match.
# (and rev-list --follow doesn't seem to solve this)
@@ -618,12 +704,14 @@ cmd_split()
revcount=0
createcount=0
eval "$grl" |
- while read rev parents; do
+ while read rev parents
+ do
revcount=$(($revcount + 1))
progress "$revcount/$revmax ($createcount)"
debug "Processing commit: $rev"
- exists=$(cache_get $rev)
- if [ -n "$exists" ]; then
+ exists=$(cache_get "$rev")
+ if test -n "$exists"
+ then
debug " prior: $exists"
continue
fi
@@ -631,76 +719,89 @@ cmd_split()
debug " parents: $parents"
newparents=$(cache_get $parents)
debug " newparents: $newparents"
-
- tree=$(subtree_for_commit $rev "$dir")
+
+ tree=$(subtree_for_commit "$rev" "$dir")
debug " tree is: $tree"
check_parents $parents
-
+
# ugly. is there no better way to tell if this is a subtree
# vs. a mainline commit? Does it matter?
- if [ -z $tree ]; then
- set_notree $rev
- if [ -n "$newparents" ]; then
- cache_set $rev $rev
+ if test -z "$tree"
+ then
+ set_notree "$rev"
+ if test -n "$newparents"
+ then
+ cache_set "$rev" "$rev"
fi
continue
fi
newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
debug " newrev is: $newrev"
- cache_set $rev $newrev
- cache_set latest_new $newrev
- cache_set latest_old $rev
+ cache_set "$rev" "$newrev"
+ cache_set latest_new "$newrev"
+ cache_set latest_old "$rev"
done || exit $?
+
latest_new=$(cache_get latest_new)
- if [ -z "$latest_new" ]; then
+ if test -z "$latest_new"
+ then
die "No new revisions were found"
fi
-
- if [ -n "$rejoin" ]; then
+
+ if test -n "$rejoin"
+ then
debug "Merging split branch into HEAD..."
latest_old=$(cache_get latest_old)
git merge -s ours \
- -m "$(rejoin_msg "$dir" $latest_old $latest_new)" \
- $latest_new >&2 || exit $?
- fi
- if [ -n "$branch" ]; then
- if rev_exists "refs/heads/$branch"; then
- if ! rev_is_descendant_of_branch $latest_new $branch; then
+ --allow-unrelated-histories \
+ -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \
+ "$latest_new" >&2 || exit $?
+ fi
+ if test -n "$branch"
+ then
+ if rev_exists "refs/heads/$branch"
+ then
+ if ! rev_is_descendant_of_branch "$latest_new" "$branch"
+ then
die "Branch '$branch' is not an ancestor of commit '$latest_new'."
fi
action='Updated'
else
action='Created'
fi
- git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
+ git update-ref -m 'subtree split' \
+ "refs/heads/$branch" "$latest_new" || exit $?
say "$action branch '$branch'"
fi
- echo $latest_new
+ echo "$latest_new"
exit 0
}
-cmd_merge()
-{
+cmd_merge () {
revs=$(git rev-parse $default --revs-only "$@") || exit $?
ensure_clean
-
+
set -- $revs
- if [ $# -ne 1 ]; then
+ if test $# -ne 1
+ then
die "You must provide exactly one revision. Got: '$revs'"
fi
rev="$1"
-
- if [ -n "$squash" ]; then
+
+ if test -n "$squash"
+ then
first_split="$(find_latest_squash "$dir")"
- if [ -z "$first_split" ]; then
+ if test -z "$first_split"
+ then
die "Can't squash-merge: '$dir' was never added."
fi
set $first_split
old=$1
sub=$2
- if [ "$sub" = "$rev" ]; then
+ if test "$sub" = "$rev"
+ then
say "Subtree is already at commit $rev."
exit 0
fi
@@ -710,25 +811,29 @@ cmd_merge()
fi
version=$(git version)
- if [ "$version" \< "git version 1.7" ]; then
- if [ -n "$message" ]; then
- git merge -s subtree --message="$message" $rev
+ if test "$version" \< "git version 1.7"
+ then
+ if test -n "$message"
+ then
+ git merge -s subtree --message="$message" "$rev"
else
- git merge -s subtree $rev
+ git merge -s subtree "$rev"
fi
else
- if [ -n "$message" ]; then
- git merge -Xsubtree="$prefix" --message="$message" $rev
+ if test -n "$message"
+ then
+ git merge -Xsubtree="$prefix" \
+ --message="$message" "$rev"
else
git merge -Xsubtree="$prefix" $rev
fi
fi
}
-cmd_pull()
-{
- if [ $# -ne 2 ]; then
- die "You must provide <repository> <ref>"
+cmd_pull () {
+ if test $# -ne 2
+ then
+ die "You must provide <repository> <ref>"
fi
ensure_clean
ensure_valid_ref_format "$2"
@@ -738,20 +843,21 @@ cmd_pull()
cmd_merge "$@"
}
-cmd_push()
-{
- if [ $# -ne 2 ]; then
- die "You must provide <repository> <ref>"
+cmd_push () {
+ if test $# -ne 2
+ then
+ die "You must provide <repository> <ref>"
fi
ensure_valid_ref_format "$2"
- if [ -e "$dir" ]; then
- repository=$1
- refspec=$2
- echo "git push using: " $repository $refspec
- localrev=$(git subtree split --prefix="$prefix") || die
- git push "$repository" $localrev:refs/heads/$refspec
+ if test -e "$dir"
+ then
+ repository=$1
+ refspec=$2
+ echo "git push using: " "$repository" "$refspec"
+ localrev=$(git subtree split --prefix="$prefix") || die
+ git push "$repository" "$localrev":"refs/heads/$refspec"
else
- die "'$dir' must already exist. Try 'git subtree add'."
+ die "'$dir' must already exist. Try 'git subtree add'."
fi
}
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 3bf96a9bb6..9751cfe9e6 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -16,16 +16,16 @@ export TEST_DIRECTORY
subtree_test_create_repo()
{
- test_create_repo "$1"
+ test_create_repo "$1" &&
(
- cd $1
+ cd "$1" &&
git config log.date relative
)
}
create()
{
- echo "$1" >"$1"
+ echo "$1" >"$1" &&
git add "$1"
}
@@ -71,12 +71,12 @@ join_commits()
}
test_create_commit() (
- repo=$1
- commit=$2
- cd "$repo"
- mkdir -p $(dirname "$commit") \
+ repo=$1 &&
+ commit=$2 &&
+ cd "$repo" &&
+ mkdir -p "$(dirname "$commit")" \
|| error "Could not create directory for commit"
- echo "$commit" >"$commit"
+ echo "$commit" >"$commit" &&
git add "$commit" || error "Could not add commit"
git commit -m "$commit" || error "Could not commit"
)
@@ -347,6 +347,22 @@ test_expect_success 'split sub dir/ with --rejoin' '
'
next_test
+test_expect_success 'split sub dir/ with --rejoin from scratch' '
+ subtree_test_create_repo "$subtree_test_count" &&
+ test_create_commit "$subtree_test_count" main1 &&
+ (
+ cd "$subtree_test_count" &&
+ mkdir "sub dir" &&
+ echo file >"sub dir"/file &&
+ git add "sub dir/file" &&
+ git commit -m"sub dir file" &&
+ split_hash=$(git subtree split --prefix="sub dir" --rejoin) &&
+ git subtree split --prefix="sub dir" --rejoin &&
+ check_equal "$(last_commit_message)" "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
+ )
+ '
+
+next_test
test_expect_success 'split sub dir/ with --rejoin and --message' '
subtree_test_create_repo "$subtree_test_count" &&
subtree_test_create_repo "$subtree_test_count/sub proj" &&
diff --git a/convert.c b/convert.c
index b1614bf7ff..67d69b5c0e 100644
--- a/convert.c
+++ b/convert.c
@@ -176,7 +176,9 @@ static enum eol output_eol(enum crlf_action crlf_action)
return EOL_LF;
case CRLF_UNDEFINED:
case CRLF_AUTO_CRLF:
+ return EOL_CRLF;
case CRLF_AUTO_INPUT:
+ return EOL_LF;
case CRLF_TEXT:
case CRLF_AUTO:
/* fall through */
@@ -254,17 +256,15 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
if (convert_is_binary(len, &stats))
return 0;
-
- if (crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
- /*
- * If the file in the index has any CR in it, do not convert.
- * This is the new safer autocrlf handling.
- */
- if (has_cr_in_index(path))
- return 0;
- }
+ /*
+ * If the file in the index has any CR in it, do not convert.
+ * This is the new safer autocrlf handling.
+ */
+ if (checksafe == SAFE_CRLF_RENORMALIZE)
+ checksafe = SAFE_CRLF_FALSE;
+ else if (has_cr_in_index(path))
+ return 0;
}
-
check_safe_crlf(path, crlf_action, &stats, checksafe);
/* Optimization: No CRLF? Nothing to convert, regardless. */
@@ -320,12 +320,10 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
return 0;
if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
- if (crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
- /* If we have any CR or CRLF line endings, we do not touch it */
- /* This is the new safer autocrlf-handling */
- if (stats.lonecr || stats.crlf )
- return 0;
- }
+ /* If we have any CR or CRLF line endings, we do not touch it */
+ /* This is the new safer autocrlf-handling */
+ if (stats.lonecr || stats.crlf )
+ return 0;
if (convert_is_binary(len, &stats))
return 0;
@@ -786,7 +784,11 @@ static void convert_attrs(struct conv_attrs *ca, const char *path)
ca->drv = git_path_check_convert(ccheck + 2);
if (ca->crlf_action != CRLF_BINARY) {
enum eol eol_attr = git_path_check_eol(ccheck + 3);
- if (eol_attr == EOL_LF)
+ if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)
+ ca->crlf_action = CRLF_AUTO_INPUT;
+ else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)
+ ca->crlf_action = CRLF_AUTO_CRLF;
+ else if (eol_attr == EOL_LF)
ca->crlf_action = CRLF_TEXT_INPUT;
else if (eol_attr == EOL_CRLF)
ca->crlf_action = CRLF_TEXT_CRLF;
@@ -845,9 +847,9 @@ const char *get_convert_attr_ascii(const char *path)
case CRLF_AUTO:
return "text=auto";
case CRLF_AUTO_CRLF:
- return "text=auto eol=crlf"; /* This is not supported yet */
+ return "text=auto eol=crlf";
case CRLF_AUTO_INPUT:
- return "text=auto eol=lf"; /* This is not supported yet */
+ return "text=auto eol=lf";
}
return "";
}
@@ -949,7 +951,7 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str
src = dst->buf;
len = dst->len;
}
- return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_FALSE);
+ return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_RENORMALIZE);
}
/*****************************************************************
diff --git a/convert.h b/convert.h
index ccf436bfbf..82871a11d5 100644
--- a/convert.h
+++ b/convert.h
@@ -7,7 +7,8 @@
enum safe_crlf {
SAFE_CRLF_FALSE = 0,
SAFE_CRLF_FAIL = 1,
- SAFE_CRLF_WARN = 2
+ SAFE_CRLF_WARN = 2,
+ SAFE_CRLF_RENORMALIZE = 3
};
extern enum safe_crlf safe_crlf;
diff --git a/daemon.c b/daemon.c
index e647254c19..425aad0507 100644
--- a/daemon.c
+++ b/daemon.c
@@ -672,9 +672,11 @@ static void set_keep_alive(int sockfd)
{
int ka = 1;
- if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
- logerror("unable to set SO_KEEPALIVE on socket: %s",
- strerror(errno));
+ if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0) {
+ if (errno != ENOTSOCK)
+ logerror("unable to set SO_KEEPALIVE on socket: %s",
+ strerror(errno));
+ }
}
static int execute(void)
diff --git a/date.c b/date.c
index 4c7aa9ba85..a996331f5b 100644
--- a/date.c
+++ b/date.c
@@ -177,6 +177,12 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
struct tm *tm;
static struct strbuf timebuf = STRBUF_INIT;
+ if (mode->type == DATE_UNIX) {
+ strbuf_reset(&timebuf);
+ strbuf_addf(&timebuf, "%lu", time);
+ return timebuf.buf;
+ }
+
if (mode->local)
tz = local_tzoffset(time);
@@ -792,6 +798,8 @@ static enum date_mode_type parse_date_type(const char *format, const char **end)
return DATE_NORMAL;
if (skip_prefix(format, "raw", end))
return DATE_RAW;
+ if (skip_prefix(format, "unix", end))
+ return DATE_UNIX;
if (skip_prefix(format, "format", end))
return DATE_STRFTIME;
diff --git a/diff.c b/diff.c
index 7d03419880..b43d3dd2ec 100644
--- a/diff.c
+++ b/diff.c
@@ -2683,6 +2683,13 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1))
return 0;
+ /*
+ * Similarly, if we'd have to convert the file contents anyway, that
+ * makes the optimization not worthwhile.
+ */
+ if (!want_file && would_convert_to_git(name))
+ return 0;
+
len = strlen(name);
pos = cache_name_pos(name, len);
if (pos < 0)
diff --git a/dir-iterator.c b/dir-iterator.c
new file mode 100644
index 0000000000..34182a9a1c
--- /dev/null
+++ b/dir-iterator.c
@@ -0,0 +1,202 @@
+#include "cache.h"
+#include "dir.h"
+#include "iterator.h"
+#include "dir-iterator.h"
+
+struct dir_iterator_level {
+ int initialized;
+
+ DIR *dir;
+
+ /*
+ * The length of the directory part of path at this level
+ * (including a trailing '/'):
+ */
+ size_t prefix_len;
+
+ /*
+ * The last action that has been taken with the current entry
+ * (needed for directories, which have to be included in the
+ * iteration and also iterated into):
+ */
+ enum {
+ DIR_STATE_ITER,
+ DIR_STATE_RECURSE
+ } dir_state;
+};
+
+/*
+ * The full data structure used to manage the internal directory
+ * iteration state. It includes members that are not part of the
+ * public interface.
+ */
+struct dir_iterator_int {
+ struct dir_iterator base;
+
+ /*
+ * The number of levels currently on the stack. This is always
+ * at least 1, because when it becomes zero the iteration is
+ * ended and this struct is freed.
+ */
+ size_t levels_nr;
+
+ /* The number of levels that have been allocated on the stack */
+ size_t levels_alloc;
+
+ /*
+ * A stack of levels. levels[0] is the uppermost directory
+ * that will be included in this iteration.
+ */
+ struct dir_iterator_level *levels;
+};
+
+int dir_iterator_advance(struct dir_iterator *dir_iterator)
+{
+ struct dir_iterator_int *iter =
+ (struct dir_iterator_int *)dir_iterator;
+
+ while (1) {
+ struct dir_iterator_level *level =
+ &iter->levels[iter->levels_nr - 1];
+ struct dirent *de;
+
+ if (!level->initialized) {
+ /*
+ * Note: dir_iterator_begin() ensures that
+ * path is not the empty string.
+ */
+ if (!is_dir_sep(iter->base.path.buf[iter->base.path.len - 1]))
+ strbuf_addch(&iter->base.path, '/');
+ level->prefix_len = iter->base.path.len;
+
+ level->dir = opendir(iter->base.path.buf);
+ if (!level->dir && errno != ENOENT) {
+ warning("error opening directory %s: %s",
+ iter->base.path.buf, strerror(errno));
+ /* Popping the level is handled below */
+ }
+
+ level->initialized = 1;
+ } else if (S_ISDIR(iter->base.st.st_mode)) {
+ if (level->dir_state == DIR_STATE_ITER) {
+ /*
+ * The directory was just iterated
+ * over; now prepare to iterate into
+ * it.
+ */
+ level->dir_state = DIR_STATE_RECURSE;
+ ALLOC_GROW(iter->levels, iter->levels_nr + 1,
+ iter->levels_alloc);
+ level = &iter->levels[iter->levels_nr++];
+ level->initialized = 0;
+ continue;
+ } else {
+ /*
+ * The directory has already been
+ * iterated over and iterated into;
+ * we're done with it.
+ */
+ }
+ }
+
+ if (!level->dir) {
+ /*
+ * This level is exhausted (or wasn't opened
+ * successfully); pop up a level.
+ */
+ if (--iter->levels_nr == 0)
+ return dir_iterator_abort(dir_iterator);
+
+ continue;
+ }
+
+ /*
+ * Loop until we find an entry that we can give back
+ * to the caller:
+ */
+ while (1) {
+ strbuf_setlen(&iter->base.path, level->prefix_len);
+ errno = 0;
+ de = readdir(level->dir);
+
+ if (!de) {
+ /* This level is exhausted; pop up a level. */
+ if (errno) {
+ warning("error reading directory %s: %s",
+ iter->base.path.buf, strerror(errno));
+ } else if (closedir(level->dir))
+ warning("error closing directory %s: %s",
+ iter->base.path.buf, strerror(errno));
+
+ level->dir = NULL;
+ if (--iter->levels_nr == 0)
+ return dir_iterator_abort(dir_iterator);
+ break;
+ }
+
+ if (is_dot_or_dotdot(de->d_name))
+ continue;
+
+ strbuf_addstr(&iter->base.path, de->d_name);
+ if (lstat(iter->base.path.buf, &iter->base.st) < 0) {
+ if (errno != ENOENT)
+ warning("error reading path '%s': %s",
+ iter->base.path.buf,
+ strerror(errno));
+ continue;
+ }
+
+ /*
+ * We have to set these each time because
+ * the path strbuf might have been realloc()ed.
+ */
+ iter->base.relative_path =
+ iter->base.path.buf + iter->levels[0].prefix_len;
+ iter->base.basename =
+ iter->base.path.buf + level->prefix_len;
+ level->dir_state = DIR_STATE_ITER;
+
+ return ITER_OK;
+ }
+ }
+}
+
+int dir_iterator_abort(struct dir_iterator *dir_iterator)
+{
+ struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator;
+
+ for (; iter->levels_nr; iter->levels_nr--) {
+ struct dir_iterator_level *level =
+ &iter->levels[iter->levels_nr - 1];
+
+ if (level->dir && closedir(level->dir)) {
+ strbuf_setlen(&iter->base.path, level->prefix_len);
+ warning("error closing directory %s: %s",
+ iter->base.path.buf, strerror(errno));
+ }
+ }
+
+ free(iter->levels);
+ strbuf_release(&iter->base.path);
+ free(iter);
+ return ITER_DONE;
+}
+
+struct dir_iterator *dir_iterator_begin(const char *path)
+{
+ struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter));
+ struct dir_iterator *dir_iterator = &iter->base;
+
+ if (!path || !*path)
+ die("BUG: empty path passed to dir_iterator_begin()");
+
+ strbuf_init(&iter->base.path, PATH_MAX);
+ strbuf_addstr(&iter->base.path, path);
+
+ ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
+
+ iter->levels_nr = 1;
+ iter->levels[0].initialized = 0;
+
+ return dir_iterator;
+}
diff --git a/dir-iterator.h b/dir-iterator.h
new file mode 100644
index 0000000000..27739e6c29
--- /dev/null
+++ b/dir-iterator.h
@@ -0,0 +1,87 @@
+#ifndef DIR_ITERATOR_H
+#define DIR_ITERATOR_H
+
+/*
+ * Iterate over a directory tree.
+ *
+ * Iterate over a directory tree, recursively, including paths of all
+ * types and hidden paths. Skip "." and ".." entries and don't follow
+ * symlinks except for the original path.
+ *
+ * Every time dir_iterator_advance() is called, update the members of
+ * the dir_iterator structure to reflect the next path in the
+ * iteration. The order that paths are iterated over within a
+ * directory is undefined, but directory paths are always iterated
+ * over before the subdirectory contents.
+ *
+ * A typical iteration looks like this:
+ *
+ * int ok;
+ * struct iterator *iter = dir_iterator_begin(path);
+ *
+ * while ((ok = dir_iterator_advance(iter)) == ITER_OK) {
+ * if (want_to_stop_iteration()) {
+ * ok = dir_iterator_abort(iter);
+ * break;
+ * }
+ *
+ * // Access information about the current path:
+ * if (S_ISDIR(iter->st.st_mode))
+ * printf("%s is a directory\n", iter->relative_path);
+ * }
+ *
+ * if (ok != ITER_DONE)
+ * handle_error();
+ *
+ * Callers are allowed to modify iter->path while they are working,
+ * but they must restore it to its original contents before calling
+ * dir_iterator_advance() again.
+ */
+
+struct dir_iterator {
+ /* The current path: */
+ struct strbuf path;
+
+ /*
+ * The current path relative to the starting path. This part
+ * of the path always uses "/" characters to separate path
+ * components:
+ */
+ const char *relative_path;
+
+ /* The current basename: */
+ const char *basename;
+
+ /* The result of calling lstat() on path: */
+ struct stat st;
+};
+
+/*
+ * Start a directory iteration over path. Return a dir_iterator that
+ * holds the internal state of the iteration.
+ *
+ * The iteration includes all paths under path, not including path
+ * itself and not including "." or ".." entries.
+ *
+ * path is the starting directory. An internal copy will be made.
+ */
+struct dir_iterator *dir_iterator_begin(const char *path);
+
+/*
+ * Advance the iterator to the first or next item and return ITER_OK.
+ * If the iteration is exhausted, free the dir_iterator and any
+ * resources associated with it and return ITER_DONE. On error, free
+ * dir_iterator and associated resources and return ITER_ERROR. It is
+ * a bug to use iterator or call this function again after it has
+ * returned ITER_DONE or ITER_ERROR.
+ */
+int dir_iterator_advance(struct dir_iterator *iterator);
+
+/*
+ * End the iteration before it has been exhausted. Free the
+ * dir_iterator and any associated resources and return ITER_DONE. On
+ * error, free the dir_iterator and return ITER_ERROR.
+ */
+int dir_iterator_abort(struct dir_iterator *iterator);
+
+#endif
diff --git a/dir.c b/dir.c
index 6172b3438d..0ea235f3d6 100644
--- a/dir.c
+++ b/dir.c
@@ -2364,7 +2364,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
varint_len = encode_varint(untracked->ident.len, varbuf);
strbuf_add(out, varbuf, varint_len);
- strbuf_add(out, untracked->ident.buf, untracked->ident.len);
+ strbuf_addbuf(out, &untracked->ident);
strbuf_add(out, ouc, ouc_size(len));
free(ouc);
diff --git a/dir.h b/dir.h
index bfde698c48..da1a858b3a 100644
--- a/dir.h
+++ b/dir.h
@@ -262,9 +262,32 @@ extern int is_empty_dir(const char *dir);
extern void setup_standard_excludes(struct dir_struct *dir);
+
+/* Constants for remove_dir_recursively: */
+
+/*
+ * If a non-directory is found within path, stop and return an error.
+ * (In this case some empty directories might already have been
+ * removed.)
+ */
#define REMOVE_DIR_EMPTY_ONLY 01
+
+/*
+ * If any Git work trees are found within path, skip them without
+ * considering it an error.
+ */
#define REMOVE_DIR_KEEP_NESTED_GIT 02
+
+/* Remove the contents of path, but leave path itself. */
#define REMOVE_DIR_KEEP_TOPLEVEL 04
+
+/*
+ * Remove path and its contents, recursively. flags is a combination
+ * of the above REMOVE_DIR_* constants. Return 0 on success.
+ *
+ * This function uses path as temporary scratch space, but restores it
+ * before returning.
+ */
extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */
diff --git a/fetch-pack.c b/fetch-pack.c
index b501d5c320..85e77af61d 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -243,16 +243,21 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
#define INITIAL_FLUSH 16
#define PIPESAFE_FLUSH 32
-#define LARGE_FLUSH 1024
+#define LARGE_FLUSH 16384
static int next_flush(struct fetch_pack_args *args, int count)
{
- int flush_limit = args->stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH;
-
- if (count < flush_limit)
- count <<= 1;
- else
- count += flush_limit;
+ if (args->stateless_rpc) {
+ if (count < LARGE_FLUSH)
+ count <<= 1;
+ else
+ count = count * 11 / 10;
+ } else {
+ if (count < PIPESAFE_FLUSH)
+ count <<= 1;
+ else
+ count += PIPESAFE_FLUSH;
+ }
return count;
}
diff --git a/fsck.c b/fsck.c
index 05315451c5..c9cf3de8d3 100644
--- a/fsck.c
+++ b/fsck.c
@@ -9,6 +9,7 @@
#include "refs.h"
#include "utf8.h"
#include "sha1-array.h"
+#include "decorate.h"
#define FSCK_FATAL -1
#define FSCK_INFO -2
@@ -290,35 +291,87 @@ static int report(struct fsck_options *options, struct object *object,
va_start(ap, fmt);
strbuf_vaddf(&sb, fmt, ap);
- result = options->error_func(object, msg_type, sb.buf);
+ result = options->error_func(options, object, msg_type, sb.buf);
strbuf_release(&sb);
va_end(ap);
return result;
}
+static char *get_object_name(struct fsck_options *options, struct object *obj)
+{
+ if (!options->object_names)
+ return NULL;
+ return lookup_decoration(options->object_names, obj);
+}
+
+static void put_object_name(struct fsck_options *options, struct object *obj,
+ const char *fmt, ...)
+{
+ va_list ap;
+ struct strbuf buf = STRBUF_INIT;
+ char *existing;
+
+ if (!options->object_names)
+ return;
+ existing = lookup_decoration(options->object_names, obj);
+ if (existing)
+ return;
+ va_start(ap, fmt);
+ strbuf_vaddf(&buf, fmt, ap);
+ add_decoration(options->object_names, obj, strbuf_detach(&buf, NULL));
+ va_end(ap);
+}
+
+static const char *describe_object(struct fsck_options *o, struct object *obj)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ char *name;
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, oid_to_hex(&obj->oid));
+ if (o->object_names && (name = lookup_decoration(o->object_names, obj)))
+ strbuf_addf(&buf, " (%s)", name);
+
+ return buf.buf;
+}
+
static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options)
{
struct tree_desc desc;
struct name_entry entry;
int res = 0;
+ const char *name;
if (parse_tree(tree))
return -1;
+ name = get_object_name(options, &tree->object);
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
+ struct object *obj;
int result;
if (S_ISGITLINK(entry.mode))
continue;
- if (S_ISDIR(entry.mode))
- result = options->walk(&lookup_tree(entry.oid->hash)->object, OBJ_TREE, data, options);
- else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
- result = options->walk(&lookup_blob(entry.oid->hash)->object, OBJ_BLOB, data, options);
+
+ if (S_ISDIR(entry.mode)) {
+ obj = &lookup_tree(entry.oid->hash)->object;
+ if (name)
+ put_object_name(options, obj, "%s%s/", name,
+ entry.path);
+ result = options->walk(obj, OBJ_TREE, data, options);
+ }
+ else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) {
+ obj = &lookup_blob(entry.oid->hash)->object;
+ if (name)
+ put_object_name(options, obj, "%s%s", name,
+ entry.path);
+ result = options->walk(obj, OBJ_BLOB, data, options);
+ }
else {
result = error("in tree %s: entry %s has bad mode %.6o",
- oid_to_hex(&tree->object.oid), entry.path, entry.mode);
+ describe_object(options, &tree->object), entry.path, entry.mode);
}
if (result < 0)
return result;
@@ -330,20 +383,55 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_options *options)
{
+ int counter = 0, generation = 0, name_prefix_len = 0;
struct commit_list *parents;
int res;
int result;
+ const char *name;
if (parse_commit(commit))
return -1;
+ name = get_object_name(options, &commit->object);
+ if (name)
+ put_object_name(options, &commit->tree->object, "%s:", name);
+
result = options->walk((struct object *)commit->tree, OBJ_TREE, data, options);
if (result < 0)
return result;
res = result;
parents = commit->parents;
+ if (name && parents) {
+ int len = strlen(name), power;
+
+ if (len && name[len - 1] == '^') {
+ generation = 1;
+ name_prefix_len = len - 1;
+ }
+ else { /* parse ~<generation> suffix */
+ for (generation = 0, power = 1;
+ len && isdigit(name[len - 1]);
+ power *= 10)
+ generation += power * (name[--len] - '0');
+ if (power > 1 && len && name[len - 1] == '~')
+ name_prefix_len = len - 1;
+ }
+ }
+
while (parents) {
+ if (name) {
+ struct object *obj = &parents->item->object;
+
+ if (++counter > 1)
+ put_object_name(options, obj, "%s^%d",
+ name, counter);
+ else if (generation > 0)
+ put_object_name(options, obj, "%.*s~%d",
+ name_prefix_len, name, generation + 1);
+ else
+ put_object_name(options, obj, "%s^", name);
+ }
result = options->walk((struct object *)parents->item, OBJ_COMMIT, data, options);
if (result < 0)
return result;
@@ -356,8 +444,12 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio
static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options)
{
+ char *name = get_object_name(options, &tag->object);
+
if (parse_tag(tag))
return -1;
+ if (name)
+ put_object_name(options, tag->tagged, "%s", name);
return options->walk(tag->tagged, OBJ_ANY, data, options);
}
@@ -375,7 +467,7 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
case OBJ_TAG:
return fsck_walk_tag((struct tag *)obj, data, options);
default:
- error("Unknown object type for %s", oid_to_hex(&obj->oid));
+ error("Unknown object type for %s", describe_object(options, obj));
return -1;
}
}
@@ -818,12 +910,13 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
obj->type);
}
-int fsck_error_function(struct object *obj, int msg_type, const char *message)
+int fsck_error_function(struct fsck_options *o,
+ struct object *obj, int msg_type, const char *message)
{
if (msg_type == FSCK_WARN) {
- warning("object %s: %s", oid_to_hex(&obj->oid), message);
+ warning("object %s: %s", describe_object(o, obj), message);
return 0;
}
- error("object %s: %s", oid_to_hex(&obj->oid), message);
+ error("object %s: %s", describe_object(o, obj), message);
return 1;
}
diff --git a/fsck.h b/fsck.h
index dded84b5f9..1891c1863b 100644
--- a/fsck.h
+++ b/fsck.h
@@ -23,9 +23,11 @@ int is_valid_msg_type(const char *msg_id, const char *msg_type);
typedef int (*fsck_walk_func)(struct object *obj, int type, void *data, struct fsck_options *options);
/* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */
-typedef int (*fsck_error)(struct object *obj, int type, const char *message);
+typedef int (*fsck_error)(struct fsck_options *o,
+ struct object *obj, int type, const char *message);
-int fsck_error_function(struct object *obj, int type, const char *message);
+int fsck_error_function(struct fsck_options *o,
+ struct object *obj, int type, const char *message);
struct fsck_options {
fsck_walk_func walk;
@@ -33,6 +35,7 @@ struct fsck_options {
unsigned strict:1;
int *msg_type;
struct sha1_array *skiplist;
+ struct decoration *object_names;
};
#define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL }
diff --git a/git-difftool.perl b/git-difftool.perl
index ebd13baa6e..a5790d03a0 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -37,14 +37,6 @@ USAGE
exit($exitcode);
}
-sub find_worktree
-{
- # Git->repository->wc_path() does not honor changes to the working
- # tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'
- # config variable.
- return Git::command_oneline('rev-parse', '--show-toplevel');
-}
-
sub print_tool_help
{
# See the comment at the bottom of file_diff() for the reason behind
@@ -67,14 +59,14 @@ sub exit_cleanup
sub use_wt_file
{
- my ($repo, $workdir, $file, $sha1) = @_;
+ my ($workdir, $file, $sha1) = @_;
my $null_sha1 = '0' x 40;
if (-l "$workdir/$file" || ! -e _) {
return (0, $null_sha1);
}
- my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file");
+ my $wt_sha1 = Git::command_oneline('hash-object', "$workdir/$file");
my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1);
return ($use, $wt_sha1);
}
@@ -83,20 +75,17 @@ sub changed_files
{
my ($repo_path, $index, $worktree) = @_;
$ENV{GIT_INDEX_FILE} = $index;
- $ENV{GIT_WORK_TREE} = $worktree;
- my $must_unset_git_dir = 0;
- if (not defined($ENV{GIT_DIR})) {
- $must_unset_git_dir = 1;
- $ENV{GIT_DIR} = $repo_path;
- }
- my @refreshargs = qw/update-index --really-refresh -q --unmerged/;
- my @gitargs = qw/diff-files --name-only -z/;
+ my @gitargs = ('--git-dir', $repo_path, '--work-tree', $worktree);
+ my @refreshargs = (
+ @gitargs, 'update-index',
+ '--really-refresh', '-q', '--unmerged');
try {
Git::command_oneline(@refreshargs);
} catch Git::Error::Command with {};
- my $line = Git::command_oneline(@gitargs);
+ my @diffargs = (@gitargs, 'diff-files', '--name-only', '-z');
+ my $line = Git::command_oneline(@diffargs);
my @files;
if (defined $line) {
@files = split('\0', $line);
@@ -105,26 +94,15 @@ sub changed_files
}
delete($ENV{GIT_INDEX_FILE});
- delete($ENV{GIT_WORK_TREE});
- delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
return map { $_ => 1 } @files;
}
sub setup_dir_diff
{
- my ($repo, $workdir, $symlinks) = @_;
-
- # Run the diff; exit immediately if no diff found
- # 'Repository' and 'WorkingCopy' must be explicitly set to insure that
- # if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used
- # by Git->repository->command*.
- my $repo_path = $repo->repo_path();
- my %repo_args = (Repository => $repo_path, WorkingCopy => $workdir);
- my $diffrepo = Git->repository(%repo_args);
-
+ my ($workdir, $symlinks) = @_;
my @gitargs = ('diff', '--raw', '--no-abbrev', '-z', @ARGV);
- my $diffrtn = $diffrepo->command_oneline(@gitargs);
+ my $diffrtn = Git::command_oneline(@gitargs);
exit(0) unless defined($diffrtn);
# Build index info for left and right sides of the diff
@@ -176,12 +154,12 @@ EOF
if ($lmode eq $symlink_mode) {
$symlink{$src_path}{left} =
- $diffrepo->command_oneline('show', "$lsha1");
+ Git::command_oneline('show', $lsha1);
}
if ($rmode eq $symlink_mode) {
$symlink{$dst_path}{right} =
- $diffrepo->command_oneline('show', "$rsha1");
+ Git::command_oneline('show', $rsha1);
}
if ($lmode ne $null_mode and $status !~ /^C/) {
@@ -193,8 +171,8 @@ EOF
if ($working_tree_dups{$dst_path}++) {
next;
}
- my ($use, $wt_sha1) = use_wt_file($repo, $workdir,
- $dst_path, $rsha1);
+ my ($use, $wt_sha1) =
+ use_wt_file($workdir, $dst_path, $rsha1);
if ($use) {
push @working_tree, $dst_path;
$wtindex .= "$rmode $wt_sha1\t$dst_path\0";
@@ -211,44 +189,34 @@ EOF
mkpath($ldir) or exit_cleanup($tmpdir, 1);
mkpath($rdir) or exit_cleanup($tmpdir, 1);
- # If $GIT_DIR is not set prior to calling 'git update-index' and
- # 'git checkout-index', then those commands will fail if difftool
- # is called from a directory other than the repo root.
- my $must_unset_git_dir = 0;
- if (not defined($ENV{GIT_DIR})) {
- $must_unset_git_dir = 1;
- $ENV{GIT_DIR} = $repo_path;
- }
-
# Populate the left and right directories based on each index file
my ($inpipe, $ctx);
$ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
($inpipe, $ctx) =
- $repo->command_input_pipe(qw(update-index -z --index-info));
+ Git::command_input_pipe('update-index', '-z', '--index-info');
print($inpipe $lindex);
- $repo->command_close_pipe($inpipe, $ctx);
+ Git::command_close_pipe($inpipe, $ctx);
my $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
exit_cleanup($tmpdir, $rc) if $rc != 0;
$ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
($inpipe, $ctx) =
- $repo->command_input_pipe(qw(update-index -z --index-info));
+ Git::command_input_pipe('update-index', '-z', '--index-info');
print($inpipe $rindex);
- $repo->command_close_pipe($inpipe, $ctx);
+ Git::command_close_pipe($inpipe, $ctx);
$rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
exit_cleanup($tmpdir, $rc) if $rc != 0;
$ENV{GIT_INDEX_FILE} = "$tmpdir/wtindex";
($inpipe, $ctx) =
- $repo->command_input_pipe(qw(update-index --info-only -z --index-info));
+ Git::command_input_pipe('update-index', '--info-only', '-z', '--index-info');
print($inpipe $wtindex);
- $repo->command_close_pipe($inpipe, $ctx);
+ Git::command_close_pipe($inpipe, $ctx);
# If $GIT_DIR was explicitly set just for the update/checkout
# commands, then it should be unset before continuing.
- delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
delete($ENV{GIT_INDEX_FILE});
# Changes in the working tree need special treatment since they are
@@ -415,9 +383,9 @@ sub dir_diff
my $rc;
my $error = 0;
my $repo = Git->repository();
- my $workdir = find_worktree();
- my ($a, $b, $tmpdir, @worktree) =
- setup_dir_diff($repo, $workdir, $symlinks);
+ my $repo_path = $repo->repo_path();
+ my $workdir = $repo->wc_path();
+ my ($a, $b, $tmpdir, @worktree) = setup_dir_diff($workdir, $symlinks);
if (defined($extcmd)) {
$rc = system($extcmd, $a, $b);
@@ -443,10 +411,10 @@ sub dir_diff
next if ! -f "$b/$file";
if (!$indices_loaded) {
- %wt_modified = changed_files($repo->repo_path(),
- "$tmpdir/wtindex", "$workdir");
- %tmp_modified = changed_files($repo->repo_path(),
- "$tmpdir/wtindex", "$b");
+ %wt_modified = changed_files(
+ $repo_path, "$tmpdir/wtindex", $workdir);
+ %tmp_modified = changed_files(
+ $repo_path, "$tmpdir/wtindex", $b);
$indices_loaded = 1;
}
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index ded4595638..e2da524f5a 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -121,7 +121,7 @@ mark_action_done () {
sed -e 1q < "$todo" >> "$done"
sed -e 1d < "$todo" >> "$todo".new
mv -f "$todo".new "$todo"
- new_count=$(git stripspace --strip-comments <"$done" | wc -l)
+ new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) ))
echo $new_count >"$msgnum"
total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l)))
echo $total >"$end"
diff --git a/git-submodule.sh b/git-submodule.sh
index 4ec7546f1e..c90dc335d1 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -49,7 +49,7 @@ die_if_unmatched ()
{
if test "$1" = "#unmatched"
then
- exit 1
+ exit ${2:-1}
fi
}
@@ -312,11 +312,11 @@ cmd_foreach()
{
git submodule--helper list --prefix "$wt_prefix" ||
- echo "#unmatched"
+ echo "#unmatched" $?
} |
while read mode sha1 stage sm_path
do
- die_if_unmatched "$mode"
+ die_if_unmatched "$mode" "$sha1"
if test -e "$sm_path"/.git
then
displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
@@ -379,8 +379,6 @@ cmd_init()
#
# Unregister submodules from .git/config and remove their work tree
#
-# $@ = requested paths (use '.' to deinit all submodules)
-#
cmd_deinit()
{
# parse $args after "submodule ... deinit".
@@ -423,11 +421,11 @@ cmd_deinit()
{
git submodule--helper list --prefix "$wt_prefix" "$@" ||
- echo "#unmatched"
+ echo "#unmatched" $?
} |
while read mode sha1 stage sm_path
do
- die_if_unmatched "$mode"
+ die_if_unmatched "$mode" "$sha1"
name=$(git submodule--helper name "$sm_path") || exit
displaypath=$(git submodule--helper relative-path "$sm_path" "$wt_prefix")
@@ -581,12 +579,12 @@ cmd_update()
${depth:+--depth "$depth"} \
${recommend_shallow:+"$recommend_shallow"} \
${jobs:+$jobs} \
- "$@" || echo "#unmatched"
+ "$@" || echo "#unmatched" $?
} | {
err=
while read mode sha1 stage just_cloned sm_path
do
- die_if_unmatched "$mode"
+ die_if_unmatched "$mode" "$sha1"
name=$(git submodule--helper name "$sm_path") || exit
url=$(git config submodule."$name".url)
@@ -994,11 +992,11 @@ cmd_status()
{
git submodule--helper list --prefix "$wt_prefix" "$@" ||
- echo "#unmatched"
+ echo "#unmatched" $?
} |
while read mode sha1 stage sm_path
do
- die_if_unmatched "$mode"
+ die_if_unmatched "$mode" "$sha1"
name=$(git submodule--helper name "$sm_path") || exit
url=$(git config submodule."$name".url)
displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
@@ -1075,11 +1073,11 @@ cmd_sync()
cd_to_toplevel
{
git submodule--helper list --prefix "$wt_prefix" "$@" ||
- echo "#unmatched"
+ echo "#unmatched" $?
} |
while read mode sha1 stage sm_path
do
- die_if_unmatched "$mode"
+ die_if_unmatched "$mode" "$sha1"
name=$(git submodule--helper name "$sm_path")
url=$(git config -f .gitmodules --get submodule."$name".url)
diff --git a/git-svn.perl b/git-svn.perl
index f609e54ce3..4d41d220a0 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -339,7 +339,7 @@ if ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
die "failed to open $ENV{GIT_DIR}: $!\n";
$ENV{GIT_DIR} = $1 if <$fh> =~ /^gitdir: (.+)$/;
}
-} else {
+} elsif ($cmd) {
my ($git_dir, $cdup);
git_cmd_try {
$git_dir = command_oneline([qw/rev-parse --git-dir/]);
@@ -356,7 +356,7 @@ if ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
-read_git_config(\%opts);
+read_git_config(\%opts) if $ENV{GIT_DIR};
if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
Getopt::Long::Configure('pass_through');
}
diff --git a/grep.c b/grep.c
index 394c8569db..58d599e647 100644
--- a/grep.c
+++ b/grep.c
@@ -163,17 +163,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)
color_set(opt->color_sep, def->color_sep);
}
-void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
-{
- if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
- grep_set_pattern_type_option(pattern_type, opt);
- else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
- grep_set_pattern_type_option(opt->pattern_type_option, opt);
- else if (opt->extended_regexp_option)
- grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
-}
-
-void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
+static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
{
switch (pattern_type) {
case GREP_PATTERN_TYPE_UNSPECIFIED:
@@ -205,6 +195,16 @@ void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct gr
}
}
+void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
+{
+ if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
+ grep_set_pattern_type_option(pattern_type, opt);
+ else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
+ grep_set_pattern_type_option(opt->pattern_type_option, opt);
+ else if (opt->extended_regexp_option)
+ grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
+}
+
static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
const char *origin, int no,
enum grep_pat_token t,
diff --git a/grep.h b/grep.h
index cee4357b17..5856a23e46 100644
--- a/grep.h
+++ b/grep.h
@@ -145,7 +145,6 @@ struct grep_opt {
extern void init_grep_defaults(void);
extern int grep_config(const char *var, const char *value, void *);
extern void grep_init(struct grep_opt *, const char *prefix);
-void grep_set_pattern_type_option(enum grep_pattern_type, struct grep_opt *opt);
void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt);
extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
diff --git a/http-walker.c b/http-walker.c
index 2c721f0c30..0b2425531a 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -2,6 +2,7 @@
#include "commit.h"
#include "walker.h"
#include "http.h"
+#include "list.h"
struct alt_base {
char *base;
@@ -23,7 +24,7 @@ struct object_request {
struct alt_base *repo;
enum object_request_state state;
struct http_object_request *req;
- struct object_request *next;
+ struct list_head node;
};
struct alternates_request {
@@ -41,7 +42,7 @@ struct walker_data {
struct alt_base *alt;
};
-static struct object_request *object_queue_head;
+static LIST_HEAD(object_queue_head);
static void fetch_alternates(struct walker *walker, const char *base);
@@ -110,19 +111,10 @@ static void process_object_response(void *callback_data)
static void release_object_request(struct object_request *obj_req)
{
- struct object_request *entry = object_queue_head;
-
if (obj_req->req !=NULL && obj_req->req->localfile != -1)
error("fd leakage in release: %d", obj_req->req->localfile);
- if (obj_req == object_queue_head) {
- object_queue_head = obj_req->next;
- } else {
- while (entry->next != NULL && entry->next != obj_req)
- entry = entry->next;
- if (entry->next == obj_req)
- entry->next = entry->next->next;
- }
+ list_del(&obj_req->node);
free(obj_req);
}
@@ -130,8 +122,10 @@ static void release_object_request(struct object_request *obj_req)
static int fill_active_slot(struct walker *walker)
{
struct object_request *obj_req;
+ struct list_head *pos, *tmp, *head = &object_queue_head;
- for (obj_req = object_queue_head; obj_req; obj_req = obj_req->next) {
+ list_for_each_safe(pos, tmp, head) {
+ obj_req = list_entry(pos, struct object_request, node);
if (obj_req->state == WAITING) {
if (has_sha1_file(obj_req->sha1))
obj_req->state = COMPLETE;
@@ -148,7 +142,6 @@ static int fill_active_slot(struct walker *walker)
static void prefetch(struct walker *walker, unsigned char *sha1)
{
struct object_request *newreq;
- struct object_request *tail;
struct walker_data *data = walker->data;
newreq = xmalloc(sizeof(*newreq));
@@ -157,18 +150,9 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
newreq->repo = data->alt;
newreq->state = WAITING;
newreq->req = NULL;
- newreq->next = NULL;
http_is_verbose = walker->get_verbosely;
-
- if (object_queue_head == NULL) {
- object_queue_head = newreq;
- } else {
- tail = object_queue_head;
- while (tail->next != NULL)
- tail = tail->next;
- tail->next = newreq;
- }
+ list_add_tail(&newreq->node, &object_queue_head);
#ifdef USE_CURL_MULTI
fill_active_slots();
@@ -447,15 +431,19 @@ static void abort_object_request(struct object_request *obj_req)
release_object_request(obj_req);
}
-static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
+static int fetch_object(struct walker *walker, unsigned char *sha1)
{
char *hex = sha1_to_hex(sha1);
int ret = 0;
- struct object_request *obj_req = object_queue_head;
+ struct object_request *obj_req = NULL;
struct http_object_request *req;
+ struct list_head *pos, *head = &object_queue_head;
- while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
- obj_req = obj_req->next;
+ list_for_each(pos, head) {
+ obj_req = list_entry(pos, struct object_request, node);
+ if (!hashcmp(obj_req->sha1, sha1))
+ break;
+ }
if (obj_req == NULL)
return error("Couldn't find request for %s in the queue", hex);
@@ -488,6 +476,15 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c
req->localfile = -1;
}
+ /*
+ * we turned off CURLOPT_FAILONERROR to avoid losing a
+ * persistent connection and got CURLE_OK.
+ */
+ if (req->http_code == 404 && req->curl_result == CURLE_OK &&
+ (starts_with(req->url, "http://") ||
+ starts_with(req->url, "https://")))
+ req->curl_result = CURLE_HTTP_RETURNED_ERROR;
+
if (obj_req->state == ABORTED) {
ret = error("Request for %s aborted", hex);
} else if (req->curl_result != CURLE_OK &&
@@ -518,7 +515,7 @@ static int fetch(struct walker *walker, unsigned char *sha1)
struct walker_data *data = walker->data;
struct alt_base *altbase = data->alt;
- if (!fetch_object(walker, altbase, sha1))
+ if (!fetch_object(walker, sha1))
return 0;
while (altbase) {
if (!http_fetch_pack(walker, altbase, sha1))
diff --git a/http.c b/http.c
index d8b2bec861..e81dd132d4 100644
--- a/http.c
+++ b/http.c
@@ -1975,8 +1975,19 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
unsigned char expn[4096];
size_t size = eltsize * nmemb;
int posn = 0;
- struct http_object_request *freq =
- (struct http_object_request *)data;
+ struct http_object_request *freq = data;
+ struct active_request_slot *slot = freq->slot;
+
+ if (slot) {
+ CURLcode c = curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE,
+ &slot->http_code);
+ if (c != CURLE_OK)
+ die("BUG: curl_easy_getinfo for HTTP code failed: %s",
+ curl_easy_strerror(c));
+ if (slot->http_code >= 400)
+ return size;
+ }
+
do {
ssize_t retval = xwrite(freq->localfile,
(char *) ptr + posn, size - posn);
@@ -2097,6 +2108,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
freq->slot = get_active_slot();
curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq);
+ curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0);
curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr);
curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url);
diff --git a/iterator.h b/iterator.h
new file mode 100644
index 0000000000..0f6900e43a
--- /dev/null
+++ b/iterator.h
@@ -0,0 +1,81 @@
+#ifndef ITERATOR_H
+#define ITERATOR_H
+
+/*
+ * Generic constants related to iterators.
+ */
+
+/*
+ * The attempt to advance the iterator was successful; the iterator
+ * reflects the new current entry.
+ */
+#define ITER_OK 0
+
+/*
+ * The iterator is exhausted and has been freed.
+ */
+#define ITER_DONE -1
+
+/*
+ * The iterator experienced an error. The iteration has been aborted
+ * and the iterator has been freed.
+ */
+#define ITER_ERROR -2
+
+/*
+ * Return values for selector functions for merge iterators. The
+ * numerical values of these constants are important and must be
+ * compatible with ITER_DONE and ITER_ERROR.
+ */
+enum iterator_selection {
+ /* End the iteration without an error: */
+ ITER_SELECT_DONE = ITER_DONE,
+
+ /* Report an error and abort the iteration: */
+ ITER_SELECT_ERROR = ITER_ERROR,
+
+ /*
+ * The next group of constants are masks that are useful
+ * mainly internally.
+ */
+
+ /* The LSB selects whether iter0/iter1 is the "current" iterator: */
+ ITER_CURRENT_SELECTION_MASK = 0x01,
+
+ /* iter0 is the "current" iterator this round: */
+ ITER_CURRENT_SELECTION_0 = 0x00,
+
+ /* iter1 is the "current" iterator this round: */
+ ITER_CURRENT_SELECTION_1 = 0x01,
+
+ /* Yield the value from the current iterator? */
+ ITER_YIELD_CURRENT = 0x02,
+
+ /* Discard the value from the secondary iterator? */
+ ITER_SKIP_SECONDARY = 0x04,
+
+ /*
+ * The constants that a selector function should usually
+ * return.
+ */
+
+ /* Yield the value from iter0: */
+ ITER_SELECT_0 = ITER_CURRENT_SELECTION_0 | ITER_YIELD_CURRENT,
+
+ /* Yield the value from iter0 and discard the one from iter1: */
+ ITER_SELECT_0_SKIP_1 = ITER_SELECT_0 | ITER_SKIP_SECONDARY,
+
+ /* Discard the value from iter0 without yielding anything this round: */
+ ITER_SKIP_0 = ITER_CURRENT_SELECTION_1 | ITER_SKIP_SECONDARY,
+
+ /* Yield the value from iter1: */
+ ITER_SELECT_1 = ITER_CURRENT_SELECTION_1 | ITER_YIELD_CURRENT,
+
+ /* Yield the value from iter1 and discard the one from iter0: */
+ ITER_SELECT_1_SKIP_0 = ITER_SELECT_1 | ITER_SKIP_SECONDARY,
+
+ /* Discard the value from iter1 without yielding anything this round: */
+ ITER_SKIP_1 = ITER_CURRENT_SELECTION_0 | ITER_SKIP_SECONDARY
+};
+
+#endif /* ITERATOR_H */
diff --git a/list.h b/list.h
new file mode 100644
index 0000000000..a226a870dc
--- /dev/null
+++ b/list.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2002 Free Software Foundation, Inc.
+ * (originally part of the GNU C Library and Userspace RCU)
+ * Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
+ *
+ * Copyright (C) 2009 Pierre-Marc Fournier
+ * Conversion to RCU list.
+ * Copyright (C) 2010 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIST_H
+#define LIST_H 1
+
+/*
+ * The definitions of this file are adopted from those which can be
+ * found in the Linux kernel headers to enable people familiar with the
+ * latter find their way in these sources as well.
+ */
+
+/* Basic type for the double-link list. */
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+/* avoid conflicts with BSD-only sys/queue.h */
+#undef LIST_HEAD
+/* Define a variable with the head and tail of the list. */
+#define LIST_HEAD(name) \
+ struct list_head name = { &(name), &(name) }
+
+/* Initialize a new list head. */
+#define INIT_LIST_HEAD(ptr) \
+ (ptr)->next = (ptr)->prev = (ptr)
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+/* Add new element at the head of the list. */
+static inline void list_add(struct list_head *newp, struct list_head *head)
+{
+ head->next->prev = newp;
+ newp->next = head->next;
+ newp->prev = head;
+ head->next = newp;
+}
+
+/* Add new element at the tail of the list. */
+static inline void list_add_tail(struct list_head *newp, struct list_head *head)
+{
+ head->prev->next = newp;
+ newp->next = head;
+ newp->prev = head->prev;
+ head->prev = newp;
+}
+
+/* Remove element from list. */
+static inline void __list_del(struct list_head *prev, struct list_head *next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/* Remove element from list. */
+static inline void list_del(struct list_head *elem)
+{
+ __list_del(elem->prev, elem->next);
+}
+
+/* Remove element from list, initializing the element's list pointers. */
+static inline void list_del_init(struct list_head *elem)
+{
+ list_del(elem);
+ INIT_LIST_HEAD(elem);
+}
+
+/* Delete from list, add to another list as head. */
+static inline void list_move(struct list_head *elem, struct list_head *head)
+{
+ __list_del(elem->prev, elem->next);
+ list_add(elem, head);
+}
+
+/* Replace an old entry. */
+static inline void list_replace(struct list_head *old, struct list_head *newp)
+{
+ newp->next = old->next;
+ newp->prev = old->prev;
+ newp->prev->next = newp;
+ newp->next->prev = newp;
+}
+
+/* Join two lists. */
+static inline void list_splice(struct list_head *add, struct list_head *head)
+{
+ /* Do nothing if the list which gets added is empty. */
+ if (add != add->next) {
+ add->next->prev = head;
+ add->prev->next = head->next;
+ head->next->prev = add->prev;
+ head->next = add->next;
+ }
+}
+
+/* Get typed element from list at a given position. */
+#define list_entry(ptr, type, member) \
+ ((type *) ((char *) (ptr) - offsetof(type, member)))
+
+/* Get first entry from a list. */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/* Iterate forward over the elements of the list. */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/*
+ * Iterate forward over the elements list. The list elements can be
+ * removed from the list while doing this.
+ */
+#define list_for_each_safe(pos, p, head) \
+ for (pos = (head)->next, p = pos->next; \
+ pos != (head); \
+ pos = p, p = pos->next)
+
+/* Iterate backward over the elements of the list. */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/*
+ * Iterate backwards over the elements list. The list elements can be
+ * removed from the list while doing this.
+ */
+#define list_for_each_prev_safe(pos, p, head) \
+ for (pos = (head)->prev, p = pos->prev; \
+ pos != (head); \
+ pos = p, p = pos->prev)
+
+static inline int list_empty(struct list_head *head)
+{
+ return head == head->next;
+}
+
+static inline void list_replace_init(struct list_head *old,
+ struct list_head *newp)
+{
+ struct list_head *head = old->next;
+
+ list_del(old);
+ list_add_tail(newp, head);
+ INIT_LIST_HEAD(old);
+}
+
+#endif /* LIST_H */
diff --git a/merge-recursive.c b/merge-recursive.c
index 48fe7e7386..a4a1195f61 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -202,12 +202,21 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
const char *path, int stage, int refresh, int options)
{
struct cache_entry *ce;
- ce = make_cache_entry(mode, oid ? oid->hash : null_sha1, path, stage,
- (refresh ? (CE_MATCH_REFRESH |
- CE_MATCH_IGNORE_MISSING) : 0 ));
+ int ret;
+
+ ce = make_cache_entry(mode, oid ? oid->hash : null_sha1, path, stage, 0);
if (!ce)
return error(_("addinfo_cache failed for path '%s'"), path);
- return add_cache_entry(ce, options);
+
+ ret = add_cache_entry(ce, options);
+ if (refresh) {
+ struct cache_entry *nce;
+
+ nce = refresh_cache_entry(ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING);
+ if (nce != ce)
+ ret = add_cache_entry(nce, options);
+ }
+ return ret;
}
static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
diff --git a/notes-merge.c b/notes-merge.c
index 1b58a14ebc..97fc42f64b 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -298,12 +298,8 @@ static void write_buf_to_worktree(const unsigned char *obj,
char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
if (safe_create_leading_directories_const(path))
die_errno("unable to create directory for '%s'", path);
- if (file_exists(path))
- die("found existing file at '%s'", path);
- fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0666);
- if (fd < 0)
- die_errno("failed to open '%s'", path);
+ fd = xopen(path, O_WRONLY | O_EXCL | O_CREAT, 0666);
while (size > 0) {
long ret = write_in_full(fd, buf, size);
diff --git a/pack-check.c b/pack-check.c
index 1da89a41ce..d123846ea2 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -105,6 +105,8 @@ static int verify_packfile(struct packed_git *p,
void *data;
enum object_type type;
unsigned long size;
+ off_t curpos;
+ int data_valid;
if (p->index_version > 1) {
off_t offset = entries[i].offset;
@@ -116,8 +118,25 @@ static int verify_packfile(struct packed_git *p,
sha1_to_hex(entries[i].sha1),
p->pack_name, (uintmax_t)offset);
}
- data = unpack_entry(p, entries[i].offset, &type, &size);
- if (!data)
+
+ curpos = entries[i].offset;
+ type = unpack_object_header(p, w_curs, &curpos, &size);
+ unuse_pack(w_curs);
+
+ if (type == OBJ_BLOB && big_file_threshold <= size) {
+ /*
+ * Let check_sha1_signature() check it with
+ * the streaming interface; no point slurping
+ * the data in-core only to discard.
+ */
+ data = NULL;
+ data_valid = 0;
+ } else {
+ data = unpack_entry(p, entries[i].offset, &type, &size);
+ data_valid = 1;
+ }
+
+ if (data_valid && !data)
err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
sha1_to_hex(entries[i].sha1), p->pack_name,
(uintmax_t)entries[i].offset);
diff --git a/pack.h b/pack.h
index 3223f5a038..0e77429df5 100644
--- a/pack.h
+++ b/pack.h
@@ -74,6 +74,7 @@ struct pack_idx_entry {
struct progress;
+/* Note, the data argument could be NULL if object type is blob */
typedef int (*verify_fn)(const unsigned char*, enum object_type, unsigned long, void*, int*);
extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, const unsigned char *sha1);
diff --git a/parse-options-cb.c b/parse-options-cb.c
index ba5acf3111..9667bc75a0 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -117,19 +117,24 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
return 0;
}
-int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
+struct option *parse_options_concat(struct option *a, struct option *b)
{
- int i, j;
-
- for (i = 0; i < dst_size; i++)
- if (dst[i].type == OPTION_END)
- break;
- for (j = 0; i < dst_size; i++, j++) {
- dst[i] = src[j];
- if (src[j].type == OPTION_END)
- return 0;
- }
- return -1;
+ struct option *ret;
+ size_t i, a_len = 0, b_len = 0;
+
+ for (i = 0; a[i].type != OPTION_END; i++)
+ a_len++;
+ for (i = 0; b[i].type != OPTION_END; i++)
+ b_len++;
+
+ ALLOC_ARRAY(ret, st_add3(a_len, b_len, 1));
+ for (i = 0; i < a_len; i++)
+ ret[i] = a[i];
+ for (i = 0; i < b_len; i++)
+ ret[a_len + i] = b[i];
+ ret[a_len + b_len] = b[b_len]; /* final OPTION_END */
+
+ return ret;
}
int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
diff --git a/parse-options.h b/parse-options.h
index ea4af92a51..78f8384c56 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -215,7 +215,7 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,
extern int parse_options_end(struct parse_opt_ctx_t *ctx);
-extern int parse_options_concat(struct option *dst, size_t, struct option *src);
+extern struct option *parse_options_concat(struct option *a, struct option *b);
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
diff --git a/path.c b/path.c
index 259aeed846..17551c4834 100644
--- a/path.c
+++ b/path.c
@@ -483,7 +483,7 @@ static void do_submodule_path(struct strbuf *buf, const char *path,
strbuf_addstr(buf, git_dir);
}
strbuf_addch(buf, '/');
- strbuf_addstr(&git_submodule_dir, buf->buf);
+ strbuf_addbuf(&git_submodule_dir, buf);
strbuf_vaddf(buf, fmt, args);
diff --git a/read-cache.c b/read-cache.c
index db27766055..491e52d120 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -19,9 +19,6 @@
#include "split-index.h"
#include "utf8.h"
-static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
- unsigned int options);
-
/* Mask for the name length in ce_flags in the on-disk index */
#define CE_NAMEMASK (0x0fff)
@@ -1257,7 +1254,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
return has_errors;
}
-static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
+struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
unsigned int options)
{
return refresh_cache_ent(&the_index, ce, options, NULL, NULL);
diff --git a/refs.c b/refs.c
index 87dc82f1d8..814cad3163 100644
--- a/refs.c
+++ b/refs.c
@@ -120,25 +120,33 @@ int check_refname_format(const char *refname, int flags)
int refname_is_safe(const char *refname)
{
- if (starts_with(refname, "refs/")) {
+ const char *rest;
+
+ if (skip_prefix(refname, "refs/", &rest)) {
char *buf;
int result;
+ size_t restlen = strlen(rest);
+
+ /* rest must not be empty, or start or end with "/" */
+ if (!restlen || *rest == '/' || rest[restlen - 1] == '/')
+ return 0;
- buf = xmallocz(strlen(refname));
/*
* Does the refname try to escape refs/?
* For example: refs/foo/../bar is safe but refs/foo/../../bar
* is not.
*/
- result = !normalize_path_copy(buf, refname + strlen("refs/"));
+ buf = xmallocz(restlen);
+ result = !normalize_path_copy(buf, rest) && !strcmp(buf, rest);
free(buf);
return result;
}
- while (*refname) {
+
+ do {
if (!isupper(*refname) && *refname != '_')
return 0;
refname++;
- }
+ } while (*refname);
return 1;
}
@@ -496,7 +504,7 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
filename = git_path("%s", pseudoref);
fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
if (fd < 0) {
- strbuf_addf(err, "Could not open '%s' for writing: %s",
+ strbuf_addf(err, "could not open '%s' for writing: %s",
filename, strerror(errno));
return -1;
}
@@ -507,14 +515,14 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
if (read_ref(pseudoref, actual_old_sha1))
die("could not read ref '%s'", pseudoref);
if (hashcmp(actual_old_sha1, old_sha1)) {
- strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
+ strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
rollback_lock_file(&lock);
goto done;
}
}
if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
- strbuf_addf(err, "Could not write to '%s'", filename);
+ strbuf_addf(err, "could not write to '%s'", filename);
rollback_lock_file(&lock);
goto done;
}
@@ -758,13 +766,33 @@ void ref_transaction_free(struct ref_transaction *transaction)
free(transaction);
}
-static struct ref_update *add_update(struct ref_transaction *transaction,
- const char *refname)
+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 char *msg)
{
struct ref_update *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;
+
+ update->flags = flags;
+
+ if (flags & REF_HAVE_NEW)
+ hashcpy(update->new_sha1, new_sha1);
+ if (flags & REF_HAVE_OLD)
+ hashcpy(update->old_sha1, old_sha1);
+ if (msg)
+ update->msg = xstrdup(msg);
return update;
}
@@ -775,32 +803,20 @@ int ref_transaction_update(struct ref_transaction *transaction,
unsigned int flags, const char *msg,
struct strbuf *err)
{
- struct ref_update *update;
-
assert(err);
- if (transaction->state != REF_TRANSACTION_OPEN)
- die("BUG: update called for transaction that is not open");
-
- if (new_sha1 && !is_null_sha1(new_sha1) &&
- check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- strbuf_addf(err, "refusing to update ref with bad name %s",
+ if ((new_sha1 && !is_null_sha1(new_sha1)) ?
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
+ !refname_is_safe(refname)) {
+ strbuf_addf(err, "refusing to update ref with bad name '%s'",
refname);
return -1;
}
- update = add_update(transaction, refname);
- if (new_sha1) {
- hashcpy(update->new_sha1, new_sha1);
- flags |= REF_HAVE_NEW;
- }
- if (old_sha1) {
- hashcpy(update->old_sha1, old_sha1);
- flags |= REF_HAVE_OLD;
- }
- update->flags = flags;
- if (msg)
- update->msg = xstrdup(msg);
+ flags |= (new_sha1 ? REF_HAVE_NEW : 0) | (old_sha1 ? REF_HAVE_OLD : 0);
+
+ ref_transaction_add_update(transaction, refname, flags,
+ new_sha1, old_sha1, msg);
return 0;
}
@@ -1104,6 +1120,26 @@ int head_ref(each_ref_fn fn, void *cb_data)
return head_ref_submodule(NULL, fn, cb_data);
}
+/*
+ * Call fn for each reference in the specified submodule for which the
+ * refname begins with prefix. If trim is non-zero, then trim that
+ * many characters off the beginning of each refname before passing
+ * the refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to
+ * include broken references in the iteration. If fn ever returns a
+ * non-zero value, stop the iteration and return that value;
+ * otherwise, return 0.
+ */
+static int do_for_each_ref(const char *submodule, const char *prefix,
+ each_ref_fn fn, int trim, int flags, void *cb_data)
+{
+ struct ref_iterator *iter;
+
+ iter = files_ref_iterator_begin(submodule, prefix, flags);
+ iter = prefix_ref_iterator_begin(iter, prefix, trim);
+
+ return do_for_each_ref_iterator(iter, fn, cb_data);
+}
+
int for_each_ref(each_ref_fn fn, void *cb_data)
{
return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
diff --git a/refs.h b/refs.h
index 56089d5724..1b02043758 100644
--- a/refs.h
+++ b/refs.h
@@ -52,19 +52,19 @@
#define RESOLVE_REF_NO_RECURSE 0x02
#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
-extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
- unsigned char *sha1, int *flags);
+const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
+ unsigned char *sha1, int *flags);
-extern char *resolve_refdup(const char *refname, int resolve_flags,
- unsigned char *sha1, int *flags);
+char *resolve_refdup(const char *refname, int resolve_flags,
+ unsigned char *sha1, int *flags);
-extern int read_ref_full(const char *refname, int resolve_flags,
- unsigned char *sha1, int *flags);
-extern int read_ref(const char *refname, unsigned char *sha1);
+int read_ref_full(const char *refname, int resolve_flags,
+ unsigned char *sha1, int *flags);
+int read_ref(const char *refname, unsigned char *sha1);
-extern int ref_exists(const char *refname);
+int ref_exists(const char *refname);
-extern int is_branch(const char *refname);
+int is_branch(const char *refname);
/*
* If refname is a non-symbolic reference that refers to a tag object,
@@ -74,24 +74,25 @@ extern int is_branch(const char *refname);
* Symbolic references are considered unpeelable, even if they
* ultimately resolve to a peelable tag.
*/
-extern int peel_ref(const char *refname, unsigned char *sha1);
+int peel_ref(const char *refname, unsigned char *sha1);
/**
* Resolve refname in the nested "gitlink" repository that is located
* at path. If the resolution is successful, return 0 and set sha1 to
* the name of the object; otherwise, return a non-zero value.
*/
-extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
+int resolve_gitlink_ref(const char *path, const char *refname,
+ unsigned char *sha1);
/*
* Return true iff abbrev_name is a possible abbreviation for
* full_name according to the rules defined by ref_rev_parse_rules in
* refs.c.
*/
-extern int refname_match(const char *abbrev_name, const char *full_name);
+int refname_match(const char *abbrev_name, const char *full_name);
-extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
-extern int dwim_log(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);
/*
* A ref_transaction represents a collection of ref updates
@@ -140,7 +141,9 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
struct ref_transaction;
/*
- * Bit values set in the flags argument passed to each_ref_fn():
+ * Bit values set in the flags argument passed to each_ref_fn() and
+ * stored in ref_iterator::flags. Other bits are for internal use
+ * only:
*/
/* Reference is a symbolic reference. */
@@ -182,38 +185,45 @@ typedef int each_ref_fn(const char *refname,
* modifies the reference also returns a nonzero value to immediately
* stop the iteration.
*/
-extern int head_ref(each_ref_fn fn, void *cb_data);
-extern int for_each_ref(each_ref_fn fn, void *cb_data);
-extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
-extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);
-extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);
-extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);
-extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
-extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
-extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
-extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
-
-extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
-extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
-extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+int head_ref(each_ref_fn fn, void *cb_data);
+int for_each_ref(each_ref_fn fn, void *cb_data);
+int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
+ unsigned int broken);
+int for_each_tag_ref(each_ref_fn fn, void *cb_data);
+int for_each_branch_ref(each_ref_fn fn, void *cb_data);
+int for_each_remote_ref(each_ref_fn fn, void *cb_data);
+int for_each_replace_ref(each_ref_fn fn, void *cb_data);
+int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
+int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
+ const char *prefix, void *cb_data);
+
+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+int for_each_ref_submodule(const char *submodule,
+ each_ref_fn fn, void *cb_data);
+int for_each_ref_in_submodule(const char *submodule, const char *prefix,
each_ref_fn fn, void *cb_data);
-extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
-extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
-extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+int for_each_tag_ref_submodule(const char *submodule,
+ each_ref_fn fn, void *cb_data);
+int for_each_branch_ref_submodule(const char *submodule,
+ each_ref_fn fn, void *cb_data);
+int for_each_remote_ref_submodule(const char *submodule,
+ each_ref_fn fn, void *cb_data);
-extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);
-extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
+int head_ref_namespaced(each_ref_fn fn, void *cb_data);
+int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
/* can be used to learn about broken ref and symref */
-extern int for_each_rawref(each_ref_fn fn, void *cb_data);
+int for_each_rawref(each_ref_fn fn, void *cb_data);
static inline const char *has_glob_specials(const char *pattern)
{
return strpbrk(pattern, "?*[");
}
-extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
-extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames);
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
+void warn_dangling_symrefs(FILE *fp, const char *msg_fmt,
+ const struct string_list *refnames);
/*
* Flags for controlling behaviour of pack_refs()
@@ -245,13 +255,13 @@ int pack_refs(unsigned int flags);
int safe_create_reflog(const char *refname, int force_create, struct strbuf *err);
/** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *refname, unsigned int flags,
- unsigned long at_time, int cnt,
- unsigned char *sha1, char **msg,
- unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
+int read_ref_at(const char *refname, unsigned int flags,
+ unsigned long at_time, int cnt,
+ unsigned char *sha1, char **msg,
+ unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
/** Check if a particular reflog exists */
-extern int reflog_exists(const char *refname);
+int reflog_exists(const char *refname);
/*
* Delete the specified reference. If old_sha1 is non-NULL, then
@@ -260,21 +270,26 @@ extern int reflog_exists(const char *refname);
* exists, regardless of its old value. It is an error for old_sha1 to
* be NULL_SHA1. flags is passed through to ref_transaction_delete().
*/
-extern int delete_ref(const char *refname, const unsigned char *old_sha1,
- unsigned int flags);
+int delete_ref(const char *refname, const unsigned char *old_sha1,
+ unsigned int flags);
/*
* Delete the specified references. If there are any problems, emit
* errors but attempt to keep going (i.e., the deletes are not done in
- * an all-or-nothing transaction).
+ * an all-or-nothing transaction). flags is passed through to
+ * ref_transaction_delete().
*/
-extern int delete_refs(struct string_list *refnames);
+int delete_refs(struct string_list *refnames, unsigned int flags);
/** Delete a reflog */
-extern int delete_reflog(const char *refname);
+int delete_reflog(const char *refname);
/* iterate over reflog entries */
-typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
+typedef int each_reflog_ent_fn(
+ unsigned char *old_sha1, unsigned char *new_sha1,
+ const char *committer, unsigned long timestamp,
+ int tz, const char *msg, void *cb_data);
+
int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
@@ -282,7 +297,7 @@ int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void
* Calls the specified function for each reflog file until it returns nonzero,
* and returns the value
*/
-extern int for_each_reflog(each_ref_fn, void *);
+int for_each_reflog(each_ref_fn fn, void *cb_data);
#define REFNAME_ALLOW_ONELEVEL 1
#define REFNAME_REFSPEC_PATTERN 2
@@ -295,16 +310,16 @@ extern int for_each_reflog(each_ref_fn, void *);
* allow a single "*" wildcard character in the refspec. No leading or
* repeated slashes are accepted.
*/
-extern int check_refname_format(const char *refname, int flags);
+int check_refname_format(const char *refname, int flags);
-extern const char *prettify_refname(const char *refname);
+const char *prettify_refname(const char *refname);
-extern char *shorten_unambiguous_ref(const char *refname, int strict);
+char *shorten_unambiguous_ref(const char *refname, int strict);
/** rename ref, return 0 on success **/
-extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
+int rename_ref(const char *oldref, const char *newref, const char *logmsg);
-extern int create_symref(const char *refname, const char *target, const char *logmsg);
+int create_symref(const char *refname, const char *target, const char *logmsg);
/*
* Update HEAD of the specified gitdir.
@@ -313,7 +328,7 @@ extern int create_symref(const char *refname, const char *target, const char *lo
* $GIT_DIR points to.
* Return 0 if successful, non-zero otherwise.
* */
-extern int set_worktree_head_symref(const char *gitdir, const char *target);
+int set_worktree_head_symref(const char *gitdir, const char *target);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
@@ -463,7 +478,7 @@ 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);
-extern int parse_hide_refs_config(const char *var, const char *value, const char *);
+int parse_hide_refs_config(const char *var, const char *value, const char *);
/*
* Check whether a ref is hidden. If no namespace is set, both the first and
@@ -473,7 +488,7 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char
* the ref is outside that namespace, the first parameter is NULL. The second
* parameter always points to the full ref name.
*/
-extern int ref_is_hidden(const char *, const char *);
+int ref_is_hidden(const char *, const char *);
enum ref_type {
REF_TYPE_PER_WORKTREE,
@@ -522,11 +537,11 @@ typedef void reflog_expiry_cleanup_fn(void *cb_data);
* enum expire_reflog_flags. The three function pointers are described
* above. On success, return zero.
*/
-extern int reflog_expire(const char *refname, const unsigned char *sha1,
- 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,
+ 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);
#endif /* REFS_H */
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dac3a22297..12290d2496 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1,13 +1,14 @@
#include "../cache.h"
#include "../refs.h"
#include "refs-internal.h"
+#include "../iterator.h"
+#include "../dir-iterator.h"
#include "../lockfile.h"
#include "../object.h"
#include "../dir.h"
struct ref_lock {
char *ref_name;
- char *orig_ref_name;
struct lock_file *lk;
struct object_id old_oid;
};
@@ -514,68 +515,36 @@ static void sort_ref_dir(struct ref_dir *dir)
}
/*
- * Return true iff the reference described by entry can be resolved to
- * an object in the database. Emit a warning if the referred-to
- * object does not exist.
+ * Return true if refname, which has the specified oid and flags, can
+ * be resolved to an object in the database. If the referred-to object
+ * does not exist, emit a warning and return false.
*/
-static int ref_resolves_to_object(struct ref_entry *entry)
+static int ref_resolves_to_object(const char *refname,
+ const struct object_id *oid,
+ unsigned int flags)
{
- if (entry->flag & REF_ISBROKEN)
+ if (flags & REF_ISBROKEN)
return 0;
- if (!has_sha1_file(entry->u.value.oid.hash)) {
- error("%s does not point to a valid object!", entry->name);
+ if (!has_sha1_file(oid->hash)) {
+ error("%s does not point to a valid object!", refname);
return 0;
}
return 1;
}
/*
- * current_ref is a performance hack: when iterating over references
- * using the for_each_ref*() functions, current_ref is set to the
- * current reference's entry before calling the callback function. If
- * the callback function calls peel_ref(), then peel_ref() first
- * checks whether the reference to be peeled is the current reference
- * (it usually is) and if so, returns that reference's peeled version
- * if it is available. This avoids a refname lookup in a common case.
- */
-static struct ref_entry *current_ref;
-
-typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
-
-struct ref_entry_cb {
- const char *base;
- int trim;
- int flags;
- each_ref_fn *fn;
- void *cb_data;
-};
-
-/*
- * Handle one reference in a do_for_each_ref*()-style iteration,
- * calling an each_ref_fn for each entry.
+ * Return true if the reference described by entry can be resolved to
+ * an object in the database; otherwise, emit a warning and return
+ * false.
*/
-static int do_one_ref(struct ref_entry *entry, void *cb_data)
+static int entry_resolves_to_object(struct ref_entry *entry)
{
- struct ref_entry_cb *data = cb_data;
- struct ref_entry *old_current_ref;
- int retval;
-
- if (!starts_with(entry->name, data->base))
- return 0;
-
- if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
- !ref_resolves_to_object(entry))
- return 0;
-
- /* Store the old value, in case this is a recursive call: */
- old_current_ref = current_ref;
- current_ref = entry;
- retval = data->fn(entry->name + data->trim, &entry->u.value.oid,
- entry->flag, data->cb_data);
- current_ref = old_current_ref;
- return retval;
+ return ref_resolves_to_object(entry->name,
+ &entry->u.value.oid, entry->flag);
}
+typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
+
/*
* Call fn for each reference in dir that has index in the range
* offset <= index < dir->nr. Recurse into subdirectories that are in
@@ -605,78 +574,6 @@ static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset,
}
/*
- * Call fn for each reference in the union of dir1 and dir2, in order
- * by refname. Recurse into subdirectories. If a value entry appears
- * in both dir1 and dir2, then only process the version that is in
- * dir2. The input dirs must already be sorted, but subdirs will be
- * sorted as needed. fn is called for all references, including
- * broken ones.
- */
-static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
- struct ref_dir *dir2,
- each_ref_entry_fn fn, void *cb_data)
-{
- int retval;
- int i1 = 0, i2 = 0;
-
- assert(dir1->sorted == dir1->nr);
- assert(dir2->sorted == dir2->nr);
- while (1) {
- struct ref_entry *e1, *e2;
- int cmp;
- if (i1 == dir1->nr) {
- return do_for_each_entry_in_dir(dir2, i2, fn, cb_data);
- }
- if (i2 == dir2->nr) {
- return do_for_each_entry_in_dir(dir1, i1, fn, cb_data);
- }
- e1 = dir1->entries[i1];
- e2 = dir2->entries[i2];
- cmp = strcmp(e1->name, e2->name);
- if (cmp == 0) {
- if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) {
- /* Both are directories; descend them in parallel. */
- struct ref_dir *subdir1 = get_ref_dir(e1);
- struct ref_dir *subdir2 = get_ref_dir(e2);
- sort_ref_dir(subdir1);
- sort_ref_dir(subdir2);
- retval = do_for_each_entry_in_dirs(
- subdir1, subdir2, fn, cb_data);
- i1++;
- i2++;
- } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
- /* Both are references; ignore the one from dir1. */
- retval = fn(e2, cb_data);
- i1++;
- i2++;
- } else {
- die("conflict between reference and directory: %s",
- e1->name);
- }
- } else {
- struct ref_entry *e;
- if (cmp < 0) {
- e = e1;
- i1++;
- } else {
- e = e2;
- i2++;
- }
- if (e->flag & REF_DIR) {
- struct ref_dir *subdir = get_ref_dir(e);
- sort_ref_dir(subdir);
- retval = do_for_each_entry_in_dir(
- subdir, 0, fn, cb_data);
- } else {
- retval = fn(e, cb_data);
- }
- }
- if (retval)
- return retval;
- }
-}
-
-/*
* Load all of the refs from the dir into our in-memory cache. The hard work
* of loading loose refs is done by get_ref_dir(), so we just need to recurse
* through all of the sub-directories. We do not even need to care about
@@ -692,6 +589,153 @@ static void prime_ref_dir(struct ref_dir *dir)
}
}
+/*
+ * A level in the reference hierarchy that is currently being iterated
+ * through.
+ */
+struct cache_ref_iterator_level {
+ /*
+ * The ref_dir being iterated over at this level. The ref_dir
+ * is sorted before being stored here.
+ */
+ struct ref_dir *dir;
+
+ /*
+ * The index of the current entry within dir (which might
+ * itself be a directory). If index == -1, then the iteration
+ * hasn't yet begun. If index == dir->nr, then the iteration
+ * through this level is over.
+ */
+ int index;
+};
+
+/*
+ * Represent an iteration through a ref_dir in the memory cache. The
+ * iteration recurses through subdirectories.
+ */
+struct cache_ref_iterator {
+ struct ref_iterator base;
+
+ /*
+ * The number of levels currently on the stack. This is always
+ * at least 1, because when it becomes zero the iteration is
+ * ended and this struct is freed.
+ */
+ size_t levels_nr;
+
+ /* The number of levels that have been allocated on the stack */
+ size_t levels_alloc;
+
+ /*
+ * A stack of levels. levels[0] is the uppermost level that is
+ * being iterated over in this iteration. (This is not
+ * necessary the top level in the references hierarchy. If we
+ * are iterating through a subtree, then levels[0] will hold
+ * the ref_dir for that subtree, and subsequent levels will go
+ * on from there.)
+ */
+ struct cache_ref_iterator_level *levels;
+};
+
+static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct cache_ref_iterator *iter =
+ (struct cache_ref_iterator *)ref_iterator;
+
+ while (1) {
+ struct cache_ref_iterator_level *level =
+ &iter->levels[iter->levels_nr - 1];
+ struct ref_dir *dir = level->dir;
+ struct ref_entry *entry;
+
+ if (level->index == -1)
+ sort_ref_dir(dir);
+
+ if (++level->index == level->dir->nr) {
+ /* This level is exhausted; pop up a level */
+ if (--iter->levels_nr == 0)
+ return ref_iterator_abort(ref_iterator);
+
+ continue;
+ }
+
+ entry = dir->entries[level->index];
+
+ if (entry->flag & REF_DIR) {
+ /* push down a level */
+ ALLOC_GROW(iter->levels, iter->levels_nr + 1,
+ iter->levels_alloc);
+
+ level = &iter->levels[iter->levels_nr++];
+ level->dir = get_ref_dir(entry);
+ level->index = -1;
+ } else {
+ iter->base.refname = entry->name;
+ iter->base.oid = &entry->u.value.oid;
+ iter->base.flags = entry->flag;
+ return ITER_OK;
+ }
+ }
+}
+
+static enum peel_status peel_entry(struct ref_entry *entry, int repeel);
+
+static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ struct cache_ref_iterator *iter =
+ (struct cache_ref_iterator *)ref_iterator;
+ struct cache_ref_iterator_level *level;
+ struct ref_entry *entry;
+
+ level = &iter->levels[iter->levels_nr - 1];
+
+ if (level->index == -1)
+ die("BUG: peel called before advance for cache iterator");
+
+ entry = level->dir->entries[level->index];
+
+ if (peel_entry(entry, 0))
+ return -1;
+ hashcpy(peeled->hash, entry->u.value.peeled.hash);
+ return 0;
+}
+
+static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct cache_ref_iterator *iter =
+ (struct cache_ref_iterator *)ref_iterator;
+
+ free(iter->levels);
+ base_ref_iterator_free(ref_iterator);
+ return ITER_DONE;
+}
+
+static struct ref_iterator_vtable cache_ref_iterator_vtable = {
+ cache_ref_iterator_advance,
+ cache_ref_iterator_peel,
+ cache_ref_iterator_abort
+};
+
+static struct ref_iterator *cache_ref_iterator_begin(struct ref_dir *dir)
+{
+ struct cache_ref_iterator *iter;
+ struct ref_iterator *ref_iterator;
+ struct cache_ref_iterator_level *level;
+
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+ base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
+ ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
+
+ iter->levels_nr = 1;
+ level = &iter->levels[0];
+ level->index = -1;
+ level->dir = dir;
+
+ return ref_iterator;
+}
+
struct nonmatching_ref_data {
const struct string_list *skip;
const char *conflicting_refname;
@@ -955,15 +999,26 @@ static struct ref_cache *lookup_ref_cache(const char *submodule)
/*
* Return a pointer to a ref_cache for the specified submodule. For
- * the main repository, use submodule==NULL. The returned structure
- * will be allocated and initialized but not necessarily populated; it
- * should not be freed.
+ * the main repository, use submodule==NULL; such a call cannot fail.
+ * For a submodule, the submodule must exist and be a nonbare
+ * repository, otherwise return NULL.
+ *
+ * The returned structure will be allocated and initialized but not
+ * necessarily populated; it should not be freed.
*/
static struct ref_cache *get_ref_cache(const char *submodule)
{
struct ref_cache *refs = lookup_ref_cache(submodule);
- if (!refs)
- refs = create_ref_cache(submodule);
+
+ if (!refs) {
+ struct strbuf submodule_sb = STRBUF_INIT;
+
+ strbuf_addstr(&submodule_sb, submodule);
+ if (is_nonbare_repository_dir(&submodule_sb))
+ refs = create_ref_cache(submodule);
+ strbuf_release(&submodule_sb);
+ }
+
return refs;
}
@@ -1342,13 +1397,10 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh
return -1;
strbuf_add(&submodule, path, len);
- refs = lookup_ref_cache(submodule.buf);
+ refs = get_ref_cache(submodule.buf);
if (!refs) {
- if (!is_nonbare_repository_dir(&submodule)) {
- strbuf_release(&submodule);
- return -1;
- }
- refs = create_ref_cache(submodule.buf);
+ strbuf_release(&submodule);
+ return -1;
}
strbuf_release(&submodule);
@@ -1388,38 +1440,8 @@ static int resolve_missing_loose_ref(const char *refname,
return -1;
}
-/*
- * Read a raw ref from the filesystem or packed refs file.
- *
- * If the ref is a sha1, fill in sha1 and return 0.
- *
- * If the ref is symbolic, fill in *symref with the referrent
- * (e.g. "refs/heads/master") and return 0. The caller is responsible
- * for validating the referrent. Set REF_ISSYMREF in flags.
- *
- * 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 flags, set errno to EINVAL, and return
- * -1.
- *
- * If there is another error reading the ref, set errno appropriately and
- * return -1.
- *
- * Backend-specific flags might be set in flags as well, regardless of
- * outcome.
- *
- * sb_path is workspace: the caller should allocate and free it.
- *
- * It is OK for refname to point into symref. In this case:
- * - if the function succeeds with REF_ISSYMREF, symref will be
- * overwritten and the memory pointed to by refname might be changed
- * or even freed.
- * - in all other cases, symref will be untouched, and therefore
- * refname will still be valid and unchanged.
- */
int read_raw_ref(const char *refname, unsigned char *sha1,
- struct strbuf *symref, unsigned int *flags)
+ struct strbuf *referent, unsigned int *type)
{
struct strbuf sb_contents = STRBUF_INIT;
struct strbuf sb_path = STRBUF_INIT;
@@ -1430,6 +1452,7 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
int ret = -1;
int save_errno;
+ *type = 0;
strbuf_reset(&sb_path);
strbuf_git_path(&sb_path, "%s", refname);
path = sb_path.buf;
@@ -1448,7 +1471,7 @@ stat_ref:
if (lstat(path, &st) < 0) {
if (errno != ENOENT)
goto out;
- if (resolve_missing_loose_ref(refname, sha1, flags)) {
+ if (resolve_missing_loose_ref(refname, sha1, type)) {
errno = ENOENT;
goto out;
}
@@ -1468,8 +1491,8 @@ stat_ref:
}
if (starts_with(sb_contents.buf, "refs/") &&
!check_refname_format(sb_contents.buf, 0)) {
- strbuf_swap(&sb_contents, symref);
- *flags |= REF_ISSYMREF;
+ strbuf_swap(&sb_contents, referent);
+ *type |= REF_ISSYMREF;
ret = 0;
goto out;
}
@@ -1477,7 +1500,16 @@ stat_ref:
/* Is it a directory? */
if (S_ISDIR(st.st_mode)) {
- errno = EISDIR;
+ /*
+ * Even though there is a directory where the loose
+ * ref is supposed to be, there could still be a
+ * packed ref:
+ */
+ if (resolve_missing_loose_ref(refname, sha1, type)) {
+ errno = EISDIR;
+ goto out;
+ }
+ ret = 0;
goto out;
}
@@ -1508,9 +1540,9 @@ stat_ref:
while (isspace(*buf))
buf++;
- strbuf_reset(symref);
- strbuf_addstr(symref, buf);
- *flags |= REF_ISSYMREF;
+ strbuf_reset(referent);
+ strbuf_addstr(referent, buf);
+ *type |= REF_ISSYMREF;
ret = 0;
goto out;
}
@@ -1521,7 +1553,7 @@ stat_ref:
*/
if (get_sha1_hex(buf, sha1) ||
(buf[40] != '\0' && !isspace(buf[40]))) {
- *flags |= REF_ISBROKEN;
+ *type |= REF_ISBROKEN;
errno = EINVAL;
goto out;
}
@@ -1536,6 +1568,241 @@ out:
return ret;
}
+static void unlock_ref(struct ref_lock *lock)
+{
+ /* Do not free lock->lk -- atexit() still looks at them */
+ if (lock->lk)
+ rollback_lock_file(lock->lk);
+ free(lock->ref_name);
+ free(lock);
+}
+
+/*
+ * Lock refname, without following symrefs, and set *lock_p to point
+ * at a newly-allocated lock object. Fill in lock->old_oid, referent,
+ * and type similarly to read_raw_ref().
+ *
+ * The caller must verify that refname is a "safe" reference name (in
+ * the sense of refname_is_safe()) before calling this function.
+ *
+ * If the reference doesn't already exist, verify that refname doesn't
+ * have a D/F conflict with any existing references. extras and skip
+ * are passed to verify_refname_available_dir() for this check.
+ *
+ * If mustexist is not set and the reference is not found or is
+ * broken, lock the reference anyway but clear sha1.
+ *
+ * Return 0 on success. On failure, write an error message to err and
+ * return TRANSACTION_NAME_CONFLICT or TRANSACTION_GENERIC_ERROR.
+ *
+ * Implementation note: This function is basically
+ *
+ * lock reference
+ * read_raw_ref()
+ *
+ * but it includes a lot more code to
+ * - Deal with possible races with other processes
+ * - Avoid calling verify_refname_available_dir() when it can be
+ * avoided, namely if we were successfully able to read the ref
+ * - Generate informative error messages in the case of failure
+ */
+static int lock_raw_ref(const char *refname, int mustexist,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ struct ref_lock **lock_p,
+ struct strbuf *referent,
+ unsigned int *type,
+ struct strbuf *err)
+{
+ struct ref_lock *lock;
+ struct strbuf ref_file = STRBUF_INIT;
+ int attempts_remaining = 3;
+ int ret = TRANSACTION_GENERIC_ERROR;
+
+ assert(err);
+ *type = 0;
+
+ /* First lock the file so it can't change out from under us. */
+
+ *lock_p = lock = xcalloc(1, sizeof(*lock));
+
+ lock->ref_name = xstrdup(refname);
+ strbuf_git_path(&ref_file, "%s", refname);
+
+retry:
+ switch (safe_create_leading_directories(ref_file.buf)) {
+ case SCLD_OK:
+ break; /* success */
+ case SCLD_EXISTS:
+ /*
+ * Suppose refname is "refs/foo/bar". We just failed
+ * to create the containing directory, "refs/foo",
+ * because there was a non-directory in the way. This
+ * indicates a D/F conflict, probably because of
+ * another reference such as "refs/foo". There is no
+ * reason to expect this error to be transitory.
+ */
+ if (verify_refname_available(refname, extras, skip, err)) {
+ if (mustexist) {
+ /*
+ * To the user the relevant error is
+ * that the "mustexist" reference is
+ * missing:
+ */
+ strbuf_reset(err);
+ strbuf_addf(err, "unable to resolve reference '%s'",
+ refname);
+ } else {
+ /*
+ * The error message set by
+ * verify_refname_available_dir() is OK.
+ */
+ ret = TRANSACTION_NAME_CONFLICT;
+ }
+ } else {
+ /*
+ * The file that is in the way isn't a loose
+ * reference. Report it as a low-level
+ * failure.
+ */
+ strbuf_addf(err, "unable to create lock file %s.lock; "
+ "non-directory in the way",
+ ref_file.buf);
+ }
+ goto error_return;
+ case SCLD_VANISHED:
+ /* Maybe another process was tidying up. Try again. */
+ if (--attempts_remaining > 0)
+ goto retry;
+ /* fall through */
+ default:
+ strbuf_addf(err, "unable to create directory for %s",
+ ref_file.buf);
+ goto error_return;
+ }
+
+ if (!lock->lk)
+ lock->lk = xcalloc(1, sizeof(struct lock_file));
+
+ if (hold_lock_file_for_update(lock->lk, ref_file.buf, LOCK_NO_DEREF) < 0) {
+ if (errno == ENOENT && --attempts_remaining > 0) {
+ /*
+ * Maybe somebody just deleted one of the
+ * directories leading to ref_file. Try
+ * again:
+ */
+ goto retry;
+ } else {
+ unable_to_lock_message(ref_file.buf, errno, err);
+ goto error_return;
+ }
+ }
+
+ /*
+ * Now we hold the lock and can read the reference without
+ * fear that its value will change.
+ */
+
+ if (read_raw_ref(refname, lock->old_oid.hash, referent, type)) {
+ if (errno == ENOENT) {
+ if (mustexist) {
+ /* Garden variety missing reference. */
+ strbuf_addf(err, "unable to resolve reference '%s'",
+ refname);
+ goto error_return;
+ } else {
+ /*
+ * Reference is missing, but that's OK. We
+ * know that there is not a conflict with
+ * another loose reference because
+ * (supposing that we are trying to lock
+ * reference "refs/foo/bar"):
+ *
+ * - We were successfully able to create
+ * the lockfile refs/foo/bar.lock, so we
+ * know there cannot be a loose reference
+ * named "refs/foo".
+ *
+ * - We got ENOENT and not EISDIR, so we
+ * know that there cannot be a loose
+ * reference named "refs/foo/bar/baz".
+ */
+ }
+ } else if (errno == EISDIR) {
+ /*
+ * There is a directory in the way. It might have
+ * contained references that have been deleted. If
+ * we don't require that the reference already
+ * exists, try to remove the directory so that it
+ * doesn't cause trouble when we want to rename the
+ * lockfile into place later.
+ */
+ if (mustexist) {
+ /* Garden variety missing reference. */
+ strbuf_addf(err, "unable to resolve reference '%s'",
+ refname);
+ goto error_return;
+ } else if (remove_dir_recursively(&ref_file,
+ REMOVE_DIR_EMPTY_ONLY)) {
+ if (verify_refname_available_dir(
+ refname, extras, skip,
+ get_loose_refs(&ref_cache),
+ err)) {
+ /*
+ * The error message set by
+ * verify_refname_available() is OK.
+ */
+ ret = TRANSACTION_NAME_CONFLICT;
+ goto error_return;
+ } else {
+ /*
+ * We can't delete the directory,
+ * but we also don't know of any
+ * references that it should
+ * contain.
+ */
+ strbuf_addf(err, "there is a non-empty directory '%s' "
+ "blocking reference '%s'",
+ ref_file.buf, refname);
+ goto error_return;
+ }
+ }
+ } else if (errno == EINVAL && (*type & REF_ISBROKEN)) {
+ strbuf_addf(err, "unable to resolve reference '%s': "
+ "reference broken", refname);
+ goto error_return;
+ } else {
+ strbuf_addf(err, "unable to resolve reference '%s': %s",
+ refname, strerror(errno));
+ goto error_return;
+ }
+
+ /*
+ * If the ref did not exist and we are creating it,
+ * make sure there is no existing packed ref whose
+ * name begins with our refname, nor a packed ref
+ * whose name is a proper prefix of our refname.
+ */
+ if (verify_refname_available_dir(
+ refname, extras, skip,
+ get_packed_refs(&ref_cache),
+ err)) {
+ goto error_return;
+ }
+ }
+
+ ret = 0;
+ goto out;
+
+error_return:
+ unlock_ref(lock);
+ *lock_p = NULL;
+
+out:
+ strbuf_release(&ref_file);
+ return ret;
+}
+
/*
* Peel the entry (if possible) and return its new peel_status. If
* repeel is true, re-peel the entry even if there is an old peeled
@@ -1576,11 +1843,12 @@ int peel_ref(const char *refname, unsigned char *sha1)
int flag;
unsigned char base[20];
- if (current_ref && (current_ref->name == refname
- || !strcmp(current_ref->name, refname))) {
- if (peel_entry(current_ref, 0))
+ 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, current_ref->u.value.peeled.hash);
+ hashcpy(sha1, peeled.hash);
return 0;
}
@@ -1608,100 +1876,137 @@ int peel_ref(const char *refname, unsigned char *sha1)
return peel_object(base, sha1);
}
-/*
- * Call fn for each reference in the specified ref_cache, omitting
- * references not in the containing_dir of base. fn is called for all
- * references, including broken ones. If fn ever returns a non-zero
- * value, stop the iteration and return that value; otherwise, return
- * 0.
- */
-static int do_for_each_entry(struct ref_cache *refs, const char *base,
- each_ref_entry_fn fn, void *cb_data)
-{
+struct files_ref_iterator {
+ struct ref_iterator base;
+
struct packed_ref_cache *packed_ref_cache;
- struct ref_dir *loose_dir;
- struct ref_dir *packed_dir;
- int retval = 0;
+ struct ref_iterator *iter0;
+ unsigned int flags;
+};
- /*
- * We must make sure that all loose refs are read before accessing the
- * packed-refs file; this avoids a race condition in which loose refs
- * are migrated to the packed-refs file by a simultaneous process, but
- * our in-memory view is from before the migration. get_packed_ref_cache()
- * takes care of making sure our view is up to date with what is on
- * disk.
- */
- loose_dir = get_loose_refs(refs);
- if (base && *base) {
- loose_dir = find_containing_dir(loose_dir, base, 0);
- }
- if (loose_dir)
- prime_ref_dir(loose_dir);
+static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct files_ref_iterator *iter =
+ (struct files_ref_iterator *)ref_iterator;
+ int ok;
- packed_ref_cache = get_packed_ref_cache(refs);
- acquire_packed_ref_cache(packed_ref_cache);
- packed_dir = get_packed_ref_dir(packed_ref_cache);
- if (base && *base) {
- packed_dir = find_containing_dir(packed_dir, base, 0);
- }
-
- if (packed_dir && loose_dir) {
- sort_ref_dir(packed_dir);
- sort_ref_dir(loose_dir);
- retval = do_for_each_entry_in_dirs(
- packed_dir, loose_dir, fn, cb_data);
- } else if (packed_dir) {
- sort_ref_dir(packed_dir);
- retval = do_for_each_entry_in_dir(
- packed_dir, 0, fn, cb_data);
- } else if (loose_dir) {
- sort_ref_dir(loose_dir);
- retval = do_for_each_entry_in_dir(
- loose_dir, 0, fn, cb_data);
+ while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
+ if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ !ref_resolves_to_object(iter->iter0->refname,
+ iter->iter0->oid,
+ iter->iter0->flags))
+ continue;
+
+ iter->base.refname = iter->iter0->refname;
+ iter->base.oid = iter->iter0->oid;
+ iter->base.flags = iter->iter0->flags;
+ return ITER_OK;
}
- release_packed_ref_cache(packed_ref_cache);
- return retval;
+ iter->iter0 = NULL;
+ if (ref_iterator_abort(ref_iterator) != ITER_DONE)
+ ok = ITER_ERROR;
+
+ return ok;
}
-/*
- * Call fn for each reference in the specified ref_cache for which the
- * refname begins with base. If trim is non-zero, then trim that many
- * characters off the beginning of each refname before passing the
- * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include
- * broken references in the iteration. If fn ever returns a non-zero
- * value, stop the iteration and return that value; otherwise, return
- * 0.
- */
-int do_for_each_ref(const char *submodule, const char *base,
- each_ref_fn fn, int trim, int flags, void *cb_data)
+static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
{
- struct ref_entry_cb data;
- struct ref_cache *refs;
+ struct files_ref_iterator *iter =
+ (struct files_ref_iterator *)ref_iterator;
+
+ return ref_iterator_peel(iter->iter0, peeled);
+}
+
+static int files_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct files_ref_iterator *iter =
+ (struct files_ref_iterator *)ref_iterator;
+ int ok = ITER_DONE;
+
+ if (iter->iter0)
+ ok = ref_iterator_abort(iter->iter0);
- refs = get_ref_cache(submodule);
- data.base = base;
- data.trim = trim;
- data.flags = flags;
- data.fn = fn;
- data.cb_data = cb_data;
+ release_packed_ref_cache(iter->packed_ref_cache);
+ base_ref_iterator_free(ref_iterator);
+ return ok;
+}
+
+static struct ref_iterator_vtable files_ref_iterator_vtable = {
+ files_ref_iterator_advance,
+ files_ref_iterator_peel,
+ files_ref_iterator_abort
+};
+
+struct ref_iterator *files_ref_iterator_begin(
+ const char *submodule,
+ const char *prefix, unsigned int flags)
+{
+ struct ref_cache *refs = get_ref_cache(submodule);
+ struct ref_dir *loose_dir, *packed_dir;
+ struct ref_iterator *loose_iter, *packed_iter;
+ struct files_ref_iterator *iter;
+ struct ref_iterator *ref_iterator;
+
+ if (!refs)
+ return empty_ref_iterator_begin();
if (ref_paranoia < 0)
ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
if (ref_paranoia)
- data.flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+ flags |= DO_FOR_EACH_INCLUDE_BROKEN;
- return do_for_each_entry(refs, base, do_one_ref, &data);
-}
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+ base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable);
-static void unlock_ref(struct ref_lock *lock)
-{
- /* Do not free lock->lk -- atexit() still looks at them */
- if (lock->lk)
- rollback_lock_file(lock->lk);
- free(lock->ref_name);
- free(lock->orig_ref_name);
- free(lock);
+ /*
+ * We must make sure that all loose refs are read before
+ * accessing the packed-refs file; this avoids a race
+ * condition if loose refs are migrated to the packed-refs
+ * file by a simultaneous process, but our in-memory view is
+ * from before the migration. We ensure this as follows:
+ * First, we call prime_ref_dir(), which pre-reads the loose
+ * references for the subtree into the cache. (If they've
+ * already been read, that's OK; we only need to guarantee
+ * that they're read before the packed refs, not *how much*
+ * before.) After that, we call get_packed_ref_cache(), which
+ * internally checks whether the packed-ref cache is up to
+ * date with what is on disk, and re-reads it if not.
+ */
+
+ loose_dir = get_loose_refs(refs);
+
+ if (prefix && *prefix)
+ loose_dir = find_containing_dir(loose_dir, prefix, 0);
+
+ if (loose_dir) {
+ prime_ref_dir(loose_dir);
+ loose_iter = cache_ref_iterator_begin(loose_dir);
+ } else {
+ /* There's nothing to iterate over. */
+ loose_iter = empty_ref_iterator_begin();
+ }
+
+ iter->packed_ref_cache = get_packed_ref_cache(refs);
+ acquire_packed_ref_cache(iter->packed_ref_cache);
+ packed_dir = get_packed_ref_dir(iter->packed_ref_cache);
+
+ if (prefix && *prefix)
+ packed_dir = find_containing_dir(packed_dir, prefix, 0);
+
+ if (packed_dir) {
+ packed_iter = cache_ref_iterator_begin(packed_dir);
+ } else {
+ /* There's nothing to iterate over. */
+ packed_iter = empty_ref_iterator_begin();
+ }
+
+ iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
+ iter->flags = flags;
+
+ return ref_iterator;
}
/*
@@ -1721,7 +2026,7 @@ static int verify_lock(struct ref_lock *lock,
lock->old_oid.hash, NULL)) {
if (old_sha1) {
int save_errno = errno;
- strbuf_addf(err, "can't verify ref %s", lock->ref_name);
+ strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
errno = save_errno;
return -1;
} else {
@@ -1730,7 +2035,7 @@ static int verify_lock(struct ref_lock *lock,
}
}
if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
- strbuf_addf(err, "ref %s is at %s but expected %s",
+ 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));
@@ -1758,19 +2063,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1,
const struct string_list *extras,
const struct string_list *skip,
- unsigned int flags, int *type_p,
+ unsigned int flags, int *type,
struct strbuf *err)
{
struct strbuf ref_file = STRBUF_INIT;
- struct strbuf orig_ref_file = STRBUF_INIT;
- const char *orig_refname = refname;
struct ref_lock *lock;
int last_errno = 0;
- int type;
- int lflags = 0;
+ int lflags = LOCK_NO_DEREF;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
- int resolve_flags = 0;
+ int resolve_flags = RESOLVE_REF_NO_RECURSE;
int attempts_remaining = 3;
+ int resolved;
assert(err);
@@ -1780,48 +2083,39 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
resolve_flags |= RESOLVE_REF_READING;
if (flags & REF_DELETING)
resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
- if (flags & REF_NODEREF) {
- resolve_flags |= RESOLVE_REF_NO_RECURSE;
- lflags |= LOCK_NO_DEREF;
- }
- refname = resolve_ref_unsafe(refname, resolve_flags,
- lock->old_oid.hash, &type);
- if (!refname && errno == EISDIR) {
+ strbuf_git_path(&ref_file, "%s", refname);
+ resolved = !!resolve_ref_unsafe(refname, resolve_flags,
+ lock->old_oid.hash, type);
+ if (!resolved && errno == EISDIR) {
/*
* we are trying to lock foo but we used to
* have foo/bar which now does not exist;
* it is normal for the empty directory 'foo'
* to remain.
*/
- strbuf_git_path(&orig_ref_file, "%s", orig_refname);
- if (remove_empty_directories(&orig_ref_file)) {
+ if (remove_empty_directories(&ref_file)) {
last_errno = errno;
- if (!verify_refname_available_dir(orig_refname, extras, skip,
+ if (!verify_refname_available_dir(refname, extras, skip,
get_loose_refs(&ref_cache), err))
strbuf_addf(err, "there are still refs under '%s'",
- orig_refname);
+ refname);
goto error_return;
}
- refname = resolve_ref_unsafe(orig_refname, resolve_flags,
- lock->old_oid.hash, &type);
+ resolved = !!resolve_ref_unsafe(refname, resolve_flags,
+ lock->old_oid.hash, type);
}
- if (type_p)
- *type_p = type;
- if (!refname) {
+ if (!resolved) {
last_errno = errno;
if (last_errno != ENOTDIR ||
- !verify_refname_available_dir(orig_refname, extras, skip,
+ !verify_refname_available_dir(refname, extras, skip,
get_loose_refs(&ref_cache), err))
- strbuf_addf(err, "unable to resolve reference %s: %s",
- orig_refname, strerror(last_errno));
+ strbuf_addf(err, "unable to resolve reference '%s': %s",
+ refname, strerror(last_errno));
goto error_return;
}
- if (flags & REF_NODEREF)
- refname = orig_refname;
-
/*
* If the ref did not exist and we are creating it, make sure
* there is no existing packed ref whose name begins with our
@@ -1838,8 +2132,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
lock->lk = xcalloc(1, sizeof(struct lock_file));
lock->ref_name = xstrdup(refname);
- lock->orig_ref_name = xstrdup(orig_refname);
- strbuf_git_path(&ref_file, "%s", refname);
retry:
switch (safe_create_leading_directories_const(ref_file.buf)) {
@@ -1851,7 +2143,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
/* fall through */
default:
last_errno = errno;
- strbuf_addf(err, "unable to create directory for %s",
+ strbuf_addf(err, "unable to create directory for '%s'",
ref_file.buf);
goto error_return;
}
@@ -1882,7 +2174,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
out:
strbuf_release(&ref_file);
- strbuf_release(&orig_ref_file);
errno = last_errno;
return lock;
}
@@ -2036,7 +2327,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
return 0;
/* Do not pack symbolic or broken refs: */
- if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
+ if ((entry->flag & REF_ISSYMREF) || !entry_resolves_to_object(entry))
return 0;
/* Add a packed ref cache entry equivalent to the loose entry. */
@@ -2110,7 +2401,7 @@ static void prune_ref(struct ref_to_prune *r)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_delete(transaction, r->name, r->sha1,
- REF_ISPRUNING, NULL, &err) ||
+ REF_ISPRUNING | REF_NODEREF, NULL, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -2222,7 +2513,7 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
return 0;
}
-int delete_refs(struct string_list *refnames)
+int delete_refs(struct string_list *refnames, unsigned int flags)
{
struct strbuf err = STRBUF_INIT;
int i, result = 0;
@@ -2251,7 +2542,7 @@ int delete_refs(struct string_list *refnames)
for (i = 0; i < refnames->nr; i++) {
const char *refname = refnames->items[i].string;
- if (delete_ref(refname, NULL, 0))
+ if (delete_ref(refname, NULL, flags))
result |= error(_("could not remove reference %s"), refname);
}
@@ -2322,8 +2613,8 @@ out:
}
int verify_refname_available(const char *newname,
- struct string_list *extras,
- struct string_list *skip,
+ const struct string_list *extras,
+ const struct string_list *skip,
struct strbuf *err)
{
struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
@@ -2342,7 +2633,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
const unsigned char *sha1, struct strbuf *err);
static int commit_ref_update(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg,
- int flags, struct strbuf *err);
+ struct strbuf *err);
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
@@ -2351,20 +2642,18 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
struct ref_lock *lock;
struct stat loginfo;
int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
- const char *symref = NULL;
struct strbuf err = STRBUF_INIT;
if (log && S_ISLNK(loginfo.st_mode))
return error("reflog for %s is a symlink", oldrefname);
- symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,
- orig_sha1, &flag);
+ if (!resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ orig_sha1, &flag))
+ return error("refname %s not found", oldrefname);
+
if (flag & REF_ISSYMREF)
return error("refname %s is a symbolic ref, renaming it is not supported",
oldrefname);
- if (!symref)
- return error("refname %s not found", oldrefname);
-
if (!rename_ref_available(oldrefname, newrefname))
return 1;
@@ -2377,8 +2666,16 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
goto rollback;
}
- if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&
- delete_ref(newrefname, sha1, REF_NODEREF)) {
+ /*
+ * Since we are doing a shallow lookup, sha1 is not the
+ * correct value to pass to delete_ref as old_sha1. But that
+ * doesn't matter, because an old_sha1 check wouldn't add to
+ * the safety anyway; we want to delete the reference whatever
+ * its current value.
+ */
+ if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ sha1, NULL) &&
+ delete_ref(newrefname, NULL, REF_NODEREF)) {
if (errno==EISDIR) {
struct strbuf path = STRBUF_INIT;
int result;
@@ -2402,7 +2699,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
logmoved = log;
- lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err);
+ lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, REF_NODEREF,
+ NULL, &err);
if (!lock) {
error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
strbuf_release(&err);
@@ -2411,7 +2709,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
hashcpy(lock->old_oid.hash, orig_sha1);
if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
- commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) {
+ commit_ref_update(lock, orig_sha1, logmsg, &err)) {
error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
goto rollback;
@@ -2420,7 +2718,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err);
+ lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, REF_NODEREF,
+ NULL, &err);
if (!lock) {
error("unable to lock %s for rollback: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -2430,7 +2729,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
flag = log_all_ref_updates;
log_all_ref_updates = 0;
if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
- commit_ref_update(lock, orig_sha1, NULL, 0, &err)) {
+ commit_ref_update(lock, orig_sha1, NULL, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
}
@@ -2457,6 +2756,30 @@ static int close_ref(struct ref_lock *lock)
static int commit_ref(struct ref_lock *lock)
{
+ char *path = get_locked_file_path(lock->lk);
+ struct stat st;
+
+ if (!lstat(path, &st) && S_ISDIR(st.st_mode)) {
+ /*
+ * There is a directory at the path we want to rename
+ * the lockfile to. Hopefully it is empty; try to
+ * delete it.
+ */
+ size_t len = strlen(path);
+ struct strbuf sb_path = STRBUF_INIT;
+
+ strbuf_attach(&sb_path, path, len, len);
+
+ /*
+ * If this fails, commit_lock_file() will also fail
+ * and will report the problem.
+ */
+ remove_empty_directories(&sb_path);
+ strbuf_release(&sb_path);
+ } else {
+ free(path);
+ }
+
if (commit_lock_file(lock->lk))
return -1;
return 0;
@@ -2475,7 +2798,7 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str
strbuf_git_path(logfile, "logs/%s", refname);
if (force_create || should_autocreate_reflog(refname)) {
if (safe_create_leading_directories(logfile->buf) < 0) {
- strbuf_addf(err, "unable to create directory for %s: "
+ strbuf_addf(err, "unable to create directory for '%s': "
"%s", logfile->buf, strerror(errno));
return -1;
}
@@ -2489,7 +2812,7 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str
if (errno == EISDIR) {
if (remove_empty_directories(logfile)) {
- strbuf_addf(err, "There are still logs under "
+ strbuf_addf(err, "there are still logs under "
"'%s'", logfile->buf);
return -1;
}
@@ -2497,7 +2820,7 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str
}
if (logfd < 0) {
- strbuf_addf(err, "unable to append to %s: %s",
+ strbuf_addf(err, "unable to append to '%s': %s",
logfile->buf, strerror(errno));
return -1;
}
@@ -2566,13 +2889,13 @@ static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
result = log_ref_write_fd(logfd, old_sha1, new_sha1,
git_committer_info(0), msg);
if (result) {
- strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
+ strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
strerror(errno));
close(logfd);
return -1;
}
if (close(logfd)) {
- strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
+ strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
strerror(errno));
return -1;
}
@@ -2613,14 +2936,14 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
o = parse_object(sha1);
if (!o) {
strbuf_addf(err,
- "Trying to write ref %s with nonexistent object %s",
+ "trying to write ref '%s' with nonexistent object %s",
lock->ref_name, sha1_to_hex(sha1));
unlock_ref(lock);
return -1;
}
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
strbuf_addf(err,
- "Trying to write non-commit object %s to branch %s",
+ "trying to write non-commit object %s to branch '%s'",
sha1_to_hex(sha1), lock->ref_name);
unlock_ref(lock);
return -1;
@@ -2630,7 +2953,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
write_in_full(fd, &term, 1) != 1 ||
close_ref(lock) < 0) {
strbuf_addf(err,
- "Couldn't write %s", get_lock_file_path(lock->lk));
+ "couldn't write '%s'", get_lock_file_path(lock->lk));
unlock_ref(lock);
return -1;
}
@@ -2644,20 +2967,19 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
*/
static int commit_ref_update(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg,
- int flags, struct strbuf *err)
+ struct strbuf *err)
{
clear_loose_ref_cache(&ref_cache);
- if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 ||
- (strcmp(lock->ref_name, lock->orig_ref_name) &&
- log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) {
+ if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) {
char *old_msg = strbuf_detach(err, NULL);
- strbuf_addf(err, "Cannot update the ref '%s': %s",
+ strbuf_addf(err, "cannot update the ref '%s': %s",
lock->ref_name, old_msg);
free(old_msg);
unlock_ref(lock);
return -1;
}
- if (strcmp(lock->orig_ref_name, "HEAD") != 0) {
+
+ if (strcmp(lock->ref_name, "HEAD") != 0) {
/*
* Special hack: If a branch is updated directly and HEAD
* points to it (may happen on the remote side of a push
@@ -2673,6 +2995,7 @@ static int commit_ref_update(struct ref_lock *lock,
unsigned char head_sha1[20];
int head_flag;
const char *head_ref;
+
head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
head_sha1, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
@@ -2685,8 +3008,9 @@ static int commit_ref_update(struct ref_lock *lock,
}
}
}
+
if (commit_ref(lock)) {
- error("Couldn't set %s", lock->ref_name);
+ strbuf_addf(err, "couldn't set '%s'", lock->ref_name);
unlock_ref(lock);
return -1;
}
@@ -2790,7 +3114,6 @@ int set_worktree_head_symref(const char *gitdir, const char *target)
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lk = &head_lock;
lock->ref_name = xstrdup(head_rel);
- lock->orig_ref_name = xstrdup(head_rel);
ret = create_symref_locked(lock, head_rel, target, NULL);
@@ -2969,60 +3292,88 @@ int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_dat
strbuf_release(&sb);
return ret;
}
-/*
- * Call fn for each reflog in the namespace indicated by name. name
- * must be empty or end with '/'. Name will be used as a scratch
- * space, but its contents will be restored before return.
- */
-static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data)
-{
- DIR *d = opendir(git_path("logs/%s", name->buf));
- int retval = 0;
- struct dirent *de;
- int oldlen = name->len;
- if (!d)
- return name->len ? errno : 0;
+struct files_reflog_iterator {
+ struct ref_iterator base;
- while ((de = readdir(d)) != NULL) {
- struct stat st;
+ struct dir_iterator *dir_iterator;
+ struct object_id oid;
+};
- if (de->d_name[0] == '.')
+static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct files_reflog_iterator *iter =
+ (struct files_reflog_iterator *)ref_iterator;
+ struct dir_iterator *diter = iter->dir_iterator;
+ int ok;
+
+ while ((ok = dir_iterator_advance(diter)) == ITER_OK) {
+ int flags;
+
+ if (!S_ISREG(diter->st.st_mode))
continue;
- if (ends_with(de->d_name, ".lock"))
+ if (diter->basename[0] == '.')
+ continue;
+ if (ends_with(diter->basename, ".lock"))
continue;
- strbuf_addstr(name, de->d_name);
- if (stat(git_path("logs/%s", name->buf), &st) < 0) {
- ; /* silently ignore */
- } else {
- if (S_ISDIR(st.st_mode)) {
- strbuf_addch(name, '/');
- retval = do_for_each_reflog(name, fn, cb_data);
- } else {
- struct object_id oid;
- if (read_ref_full(name->buf, 0, oid.hash, NULL))
- retval = error("bad ref for %s", name->buf);
- else
- retval = fn(name->buf, &oid, 0, cb_data);
- }
- if (retval)
- break;
+ if (read_ref_full(diter->relative_path, 0,
+ iter->oid.hash, &flags)) {
+ error("bad ref for %s", diter->path.buf);
+ continue;
}
- strbuf_setlen(name, oldlen);
+
+ iter->base.refname = diter->relative_path;
+ iter->base.oid = &iter->oid;
+ iter->base.flags = flags;
+ return ITER_OK;
}
- closedir(d);
- return retval;
+
+ iter->dir_iterator = NULL;
+ if (ref_iterator_abort(ref_iterator) == ITER_ERROR)
+ ok = ITER_ERROR;
+ return ok;
+}
+
+static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ die("BUG: ref_iterator_peel() called for reflog_iterator");
+}
+
+static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct files_reflog_iterator *iter =
+ (struct files_reflog_iterator *)ref_iterator;
+ int ok = ITER_DONE;
+
+ if (iter->dir_iterator)
+ ok = dir_iterator_abort(iter->dir_iterator);
+
+ base_ref_iterator_free(ref_iterator);
+ return ok;
+}
+
+static struct ref_iterator_vtable files_reflog_iterator_vtable = {
+ files_reflog_iterator_advance,
+ files_reflog_iterator_peel,
+ files_reflog_iterator_abort
+};
+
+struct ref_iterator *files_reflog_iterator_begin(void)
+{
+ struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter));
+ struct ref_iterator *ref_iterator = &iter->base;
+
+ base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
+ iter->dir_iterator = dir_iterator_begin(git_path("logs"));
+ return ref_iterator;
}
int for_each_reflog(each_ref_fn fn, void *cb_data)
{
- int retval;
- struct strbuf name;
- strbuf_init(&name, PATH_MAX);
- retval = do_for_each_reflog(&name, fn, cb_data);
- strbuf_release(&name);
- return retval;
+ return do_for_each_ref_iterator(files_reflog_iterator_begin(),
+ fn, cb_data);
}
static int ref_update_reject_duplicates(struct string_list *refnames,
@@ -3035,36 +3386,352 @@ static int ref_update_reject_duplicates(struct string_list *refnames,
for (i = 1; i < n; i++)
if (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) {
strbuf_addf(err,
- "Multiple updates for ref '%s' not allowed.",
+ "multiple updates for ref '%s' not allowed.",
refnames->items[i].string);
return 1;
}
return 0;
}
+/*
+ * If update is a direct update of head_ref (the reference pointed to
+ * by HEAD), then add an extra REF_LOG_ONLY update for HEAD.
+ */
+static int split_head_update(struct ref_update *update,
+ struct ref_transaction *transaction,
+ const char *head_ref,
+ struct string_list *affected_refnames,
+ struct strbuf *err)
+{
+ struct string_list_item *item;
+ struct ref_update *new_update;
+
+ if ((update->flags & REF_LOG_ONLY) ||
+ (update->flags & REF_ISPRUNING) ||
+ (update->flags & REF_UPDATE_VIA_HEAD))
+ return 0;
+
+ if (strcmp(update->refname, head_ref))
+ return 0;
+
+ /*
+ * First make sure that HEAD is not already in the
+ * transaction. This insertion is O(N) in the transaction
+ * size, but it happens at most once per transaction.
+ */
+ item = string_list_insert(affected_refnames, "HEAD");
+ if (item->util) {
+ /* An entry already existed */
+ strbuf_addf(err,
+ "multiple updates for 'HEAD' (including one "
+ "via its referent '%s') are not allowed",
+ update->refname);
+ return TRANSACTION_NAME_CONFLICT;
+ }
+
+ new_update = ref_transaction_add_update(
+ transaction, "HEAD",
+ update->flags | REF_LOG_ONLY | REF_NODEREF,
+ update->new_sha1, update->old_sha1,
+ update->msg);
+
+ item->util = new_update;
+
+ return 0;
+}
+
+/*
+ * 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
+ * - 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.
+ */
+static int split_symref_update(struct ref_update *update,
+ const char *referent,
+ struct ref_transaction *transaction,
+ struct string_list *affected_refnames,
+ struct strbuf *err)
+{
+ struct string_list_item *item;
+ struct ref_update *new_update;
+ unsigned int new_flags;
+
+ /*
+ * First make sure that referent is not already in the
+ * transaction. This insertion is O(N) in the transaction
+ * size, but it happens at most once per symref in a
+ * transaction.
+ */
+ item = string_list_insert(affected_refnames, referent);
+ if (item->util) {
+ /* An entry already existed */
+ strbuf_addf(err,
+ "multiple updates for '%s' (including one "
+ "via symref '%s') are not allowed",
+ referent, update->refname);
+ return TRANSACTION_NAME_CONFLICT;
+ }
+
+ new_flags = update->flags;
+ if (!strcmp(update->refname, "HEAD")) {
+ /*
+ * Record that the new update came via HEAD, so that
+ * when we process it, split_head_update() doesn't try
+ * to add another reflog update for HEAD. Note that
+ * this bit will be propagated if the new_update
+ * itself needs to be split.
+ */
+ new_flags |= REF_UPDATE_VIA_HEAD;
+ }
+
+ new_update = ref_transaction_add_update(
+ transaction, referent, new_flags,
+ update->new_sha1, update->old_sha1,
+ 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
+ * done when new_update is processed.
+ */
+ update->flags |= REF_LOG_ONLY | REF_NODEREF;
+ update->flags &= ~REF_HAVE_OLD;
+
+ item->util = new_update;
+
+ return 0;
+}
+
+/*
+ * Return the refname under which update was originally requested.
+ */
+static const char *original_update_refname(struct ref_update *update)
+{
+ while (update->parent_update)
+ update = update->parent_update;
+
+ return update->refname;
+}
+
+/*
+ * Check whether the REF_HAVE_OLD and old_oid values stored in update
+ * are consistent with oid, which is the reference's current value. If
+ * everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+static int check_old_oid(struct ref_update *update, struct object_id *oid,
+ struct strbuf *err)
+{
+ if (!(update->flags & REF_HAVE_OLD) ||
+ !hashcmp(oid->hash, update->old_sha1))
+ return 0;
+
+ if (is_null_sha1(update->old_sha1))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference already exists",
+ original_update_refname(update));
+ else if (is_null_oid(oid))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
+ sha1_to_hex(update->old_sha1));
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
+ oid_to_hex(oid),
+ sha1_to_hex(update->old_sha1));
+
+ return -1;
+}
+
+/*
+ * 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
+ * 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
+ * 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
+ * update of HEAD.
+ */
+static int lock_ref_for_update(struct ref_update *update,
+ struct ref_transaction *transaction,
+ const char *head_ref,
+ struct string_list *affected_refnames,
+ struct strbuf *err)
+{
+ struct strbuf referent = STRBUF_INIT;
+ int mustexist = (update->flags & REF_HAVE_OLD) &&
+ !is_null_sha1(update->old_sha1);
+ int ret;
+ struct ref_lock *lock;
+
+ if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1))
+ update->flags |= REF_DELETING;
+
+ if (head_ref) {
+ ret = split_head_update(update, transaction, head_ref,
+ affected_refnames, err);
+ if (ret)
+ return ret;
+ }
+
+ ret = lock_raw_ref(update->refname, mustexist,
+ affected_refnames, NULL,
+ &update->lock, &referent,
+ &update->type, err);
+
+ if (ret) {
+ char *reason;
+
+ reason = strbuf_detach(err, NULL);
+ strbuf_addf(err, "cannot lock ref '%s': %s",
+ original_update_refname(update), reason);
+ free(reason);
+ return ret;
+ }
+
+ lock = update->lock;
+
+ if (update->type & REF_ISSYMREF) {
+ if (update->flags & REF_NODEREF) {
+ /*
+ * 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:
+ */
+ if (read_ref_full(referent.buf, 0,
+ lock->old_oid.hash, NULL)) {
+ if (update->flags & REF_HAVE_OLD) {
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "error reading reference",
+ original_update_refname(update));
+ return -1;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
+ return TRANSACTION_GENERIC_ERROR;
+ }
+ } else {
+ /*
+ * 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
+ * of processing the split-off update, so we
+ * don't have to do it here.
+ */
+ ret = split_symref_update(update, referent.buf, transaction,
+ affected_refnames, err);
+ if (ret)
+ return ret;
+ }
+ } else {
+ struct ref_update *parent_update;
+
+ if (check_old_oid(update, &lock->old_oid, err))
+ return TRANSACTION_GENERIC_ERROR;
+
+ /*
+ * If this update is happening indirectly because of a
+ * symref update, record the old SHA-1 in the parent
+ * update:
+ */
+ for (parent_update = update->parent_update;
+ parent_update;
+ parent_update = parent_update->parent_update) {
+ oidcpy(&parent_update->lock->old_oid, &lock->old_oid);
+ }
+ }
+
+ if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
+ if (!(update->type & REF_ISSYMREF) &&
+ !hashcmp(lock->old_oid.hash, update->new_sha1)) {
+ /*
+ * The reference already has the desired
+ * value, so we don't need to write it.
+ */
+ } else if (write_ref_to_lockfile(lock, update->new_sha1,
+ err)) {
+ char *write_err = strbuf_detach(err, NULL);
+
+ /*
+ * The lock was freed upon failure of
+ * write_ref_to_lockfile():
+ */
+ update->lock = NULL;
+ strbuf_addf(err,
+ "cannot update ref '%s': %s",
+ update->refname, write_err);
+ free(write_err);
+ return TRANSACTION_GENERIC_ERROR;
+ } else {
+ update->flags |= REF_NEEDS_COMMIT;
+ }
+ }
+ if (!(update->flags & REF_NEEDS_COMMIT)) {
+ /*
+ * We didn't call write_ref_to_lockfile(), so
+ * the lockfile is still open. Close it to
+ * free up the file descriptor:
+ */
+ if (close_ref(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ return TRANSACTION_GENERIC_ERROR;
+ }
+ }
+ return 0;
+}
+
int ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
int ret = 0, i;
- int n = transaction->nr;
- struct ref_update **updates = transaction->updates;
struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
struct string_list_item *ref_to_delete;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+ char *head_ref = NULL;
+ int head_type;
+ struct object_id head_oid;
assert(err);
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: commit called for transaction that is not open");
- if (!n) {
+ if (!transaction->nr) {
transaction->state = REF_TRANSACTION_CLOSED;
return 0;
}
- /* Fail if a refname appears more than once in the transaction: */
- for (i = 0; i < n; i++)
- string_list_append(&affected_refnames, updates[i]->refname);
+ /*
+ * Fail if a refname appears more than once in the
+ * 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.)
+ */
+ 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);
+
+ /*
+ * We store a pointer to update in item->util, but at
+ * the moment we never use the value of this field
+ * except to check whether it is non-NULL.
+ */
+ item->util = update;
+ }
string_list_sort(&affected_refnames);
if (ref_update_reject_duplicates(&affected_refnames, err)) {
ret = TRANSACTION_GENERIC_ERROR;
@@ -3072,105 +3739,84 @@ int ref_transaction_commit(struct ref_transaction *transaction,
}
/*
+ * Special hack: If a branch is updated directly and HEAD
+ * points to it (may happen on the remote side of a push
+ * for example) then logically the HEAD reflog should be
+ * updated too.
+ *
+ * A generic solution would require reverse symref lookups,
+ * but finding all symrefs pointing to a given branch would be
+ * rather costly for this rare event (the direct update of a
+ * branch) to be worth it. So let's cheat and check with HEAD
+ * only, which should cover 99% of all usage scenarios (even
+ * 100% of the default ones).
+ *
+ * So if HEAD is a symbolic reference, then record the name of
+ * the reference that it points to. If we see an update of
+ * head_ref within the transaction, then split_head_update()
+ * arranges for the reflog of HEAD to be updated, too.
+ */
+ head_ref = resolve_refdup("HEAD", RESOLVE_REF_NO_RECURSE,
+ head_oid.hash, &head_type);
+
+ if (head_ref && !(head_type & REF_ISSYMREF)) {
+ free(head_ref);
+ head_ref = NULL;
+ }
+
+ /*
* Acquire all locks, verify old values if provided, check
* that new values are valid, and write new values to the
* lockfiles, ready to be activated. Only keep one lockfile
* open at a time to avoid running out of file descriptors.
*/
- for (i = 0; i < n; i++) {
- struct ref_update *update = updates[i];
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
- if ((update->flags & REF_HAVE_NEW) &&
- is_null_sha1(update->new_sha1))
- update->flags |= REF_DELETING;
- update->lock = lock_ref_sha1_basic(
- update->refname,
- ((update->flags & REF_HAVE_OLD) ?
- update->old_sha1 : NULL),
- &affected_refnames, NULL,
- update->flags,
- &update->type,
- err);
- if (!update->lock) {
- char *reason;
-
- ret = (errno == ENOTDIR)
- ? TRANSACTION_NAME_CONFLICT
- : TRANSACTION_GENERIC_ERROR;
- reason = strbuf_detach(err, NULL);
- strbuf_addf(err, "cannot lock ref '%s': %s",
- update->refname, reason);
- free(reason);
+ ret = lock_ref_for_update(update, transaction, head_ref,
+ &affected_refnames, err);
+ if (ret)
goto cleanup;
- }
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING)) {
- int overwriting_symref = ((update->type & REF_ISSYMREF) &&
- (update->flags & REF_NODEREF));
-
- if (!overwriting_symref &&
- !hashcmp(update->lock->old_oid.hash, update->new_sha1)) {
- /*
- * The reference already has the desired
- * value, so we don't need to write it.
- */
- } else if (write_ref_to_lockfile(update->lock,
- update->new_sha1,
- err)) {
- char *write_err = strbuf_detach(err, NULL);
+ }
- /*
- * The lock was freed upon failure of
- * write_ref_to_lockfile():
- */
+ /* Perform updates first so live commits remain referenced */
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+ struct ref_lock *lock = update->lock;
+
+ if (update->flags & REF_NEEDS_COMMIT ||
+ update->flags & REF_LOG_ONLY) {
+ if (log_ref_write(lock->ref_name, lock->old_oid.hash,
+ update->new_sha1,
+ update->msg, update->flags, err)) {
+ char *old_msg = strbuf_detach(err, NULL);
+
+ strbuf_addf(err, "cannot update the ref '%s': %s",
+ lock->ref_name, old_msg);
+ free(old_msg);
+ unlock_ref(lock);
update->lock = NULL;
- strbuf_addf(err,
- "cannot update the ref '%s': %s",
- update->refname, write_err);
- free(write_err);
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
- } else {
- update->flags |= REF_NEEDS_COMMIT;
- }
- }
- if (!(update->flags & REF_NEEDS_COMMIT)) {
- /*
- * We didn't have to write anything to the lockfile.
- * Close it to free up the file descriptor:
- */
- if (close_ref(update->lock)) {
- strbuf_addf(err, "Couldn't close %s.lock",
- update->refname);
- goto cleanup;
}
}
- }
-
- /* Perform updates first so live commits remain referenced */
- for (i = 0; i < n; i++) {
- struct ref_update *update = updates[i];
-
if (update->flags & REF_NEEDS_COMMIT) {
- if (commit_ref_update(update->lock,
- update->new_sha1, update->msg,
- update->flags, err)) {
- /* freed by commit_ref_update(): */
+ clear_loose_ref_cache(&ref_cache);
+ if (commit_ref(lock)) {
+ strbuf_addf(err, "couldn't set '%s'", lock->ref_name);
+ unlock_ref(lock);
update->lock = NULL;
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
- } else {
- /* freed by commit_ref_update(): */
- update->lock = NULL;
}
}
}
-
/* Perform deletes now that updates are safely completed */
- for (i = 0; i < n; i++) {
- struct ref_update *update = updates[i];
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
- if (update->flags & REF_DELETING) {
+ if (update->flags & REF_DELETING &&
+ !(update->flags & REF_LOG_ONLY)) {
if (delete_ref_loose(update->lock, update->type, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
@@ -3193,11 +3839,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
cleanup:
transaction->state = REF_TRANSACTION_CLOSED;
- for (i = 0; i < n; i++)
- if (updates[i]->lock)
- unlock_ref(updates[i]->lock);
+ for (i = 0; i < transaction->nr; i++)
+ if (transaction->updates[i]->lock)
+ unlock_ref(transaction->updates[i]->lock);
string_list_clear(&refs_to_delete, 0);
+ free(head_ref);
string_list_clear(&affected_refnames, 0);
+
return ret;
}
@@ -3213,8 +3861,6 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
int ret = 0, i;
- int n = transaction->nr;
- struct ref_update **updates = transaction->updates;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
assert(err);
@@ -3223,8 +3869,9 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
die("BUG: commit called for transaction that is not open");
/* Fail if a refname appears more than once in the transaction: */
- for (i = 0; i < n; i++)
- string_list_append(&affected_refnames, updates[i]->refname);
+ for (i = 0; i < transaction->nr; i++)
+ string_list_append(&affected_refnames,
+ transaction->updates[i]->refname);
string_list_sort(&affected_refnames);
if (ref_update_reject_duplicates(&affected_refnames, err)) {
ret = TRANSACTION_GENERIC_ERROR;
@@ -3246,8 +3893,8 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
if (for_each_rawref(ref_present, &affected_refnames))
die("BUG: initial ref transaction called with existing refs");
- for (i = 0; i < n; i++) {
- struct ref_update *update = updates[i];
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
if ((update->flags & REF_HAVE_OLD) &&
!is_null_sha1(update->old_sha1))
@@ -3267,8 +3914,8 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
goto cleanup;
}
- for (i = 0; i < n; i++) {
- struct ref_update *update = updates[i];
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
if ((update->flags & REF_HAVE_NEW) &&
!is_null_sha1(update->new_sha1))
diff --git a/refs/iterator.c b/refs/iterator.c
new file mode 100644
index 0000000000..bce1f192f7
--- /dev/null
+++ b/refs/iterator.c
@@ -0,0 +1,384 @@
+/*
+ * Generic reference iterator infrastructure. See refs-internal.h for
+ * documentation about the design and use of reference iterators.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "refs/refs-internal.h"
+#include "iterator.h"
+
+int ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ return ref_iterator->vtable->advance(ref_iterator);
+}
+
+int ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ return ref_iterator->vtable->peel(ref_iterator, peeled);
+}
+
+int ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ return ref_iterator->vtable->abort(ref_iterator);
+}
+
+void base_ref_iterator_init(struct ref_iterator *iter,
+ struct ref_iterator_vtable *vtable)
+{
+ iter->vtable = vtable;
+ iter->refname = NULL;
+ iter->oid = NULL;
+ iter->flags = 0;
+}
+
+void base_ref_iterator_free(struct ref_iterator *iter)
+{
+ /* Help make use-after-free bugs fail quickly: */
+ iter->vtable = NULL;
+ free(iter);
+}
+
+struct empty_ref_iterator {
+ struct ref_iterator base;
+};
+
+static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ return ref_iterator_abort(ref_iterator);
+}
+
+static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ die("BUG: peel called for empty iterator");
+}
+
+static int empty_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ base_ref_iterator_free(ref_iterator);
+ return ITER_DONE;
+}
+
+static struct ref_iterator_vtable empty_ref_iterator_vtable = {
+ empty_ref_iterator_advance,
+ empty_ref_iterator_peel,
+ empty_ref_iterator_abort
+};
+
+struct ref_iterator *empty_ref_iterator_begin(void)
+{
+ struct empty_ref_iterator *iter = xcalloc(1, sizeof(*iter));
+ struct ref_iterator *ref_iterator = &iter->base;
+
+ base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable);
+ return ref_iterator;
+}
+
+int is_empty_ref_iterator(struct ref_iterator *ref_iterator)
+{
+ return ref_iterator->vtable == &empty_ref_iterator_vtable;
+}
+
+struct merge_ref_iterator {
+ struct ref_iterator base;
+
+ struct ref_iterator *iter0, *iter1;
+
+ ref_iterator_select_fn *select;
+ void *cb_data;
+
+ /*
+ * A pointer to iter0 or iter1 (whichever is supplying the
+ * current value), or NULL if advance has not yet been called.
+ */
+ struct ref_iterator **current;
+};
+
+static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct merge_ref_iterator *iter =
+ (struct merge_ref_iterator *)ref_iterator;
+ int ok;
+
+ if (!iter->current) {
+ /* Initialize: advance both iterators to their first entries */
+ if ((ok = ref_iterator_advance(iter->iter0)) != ITER_OK) {
+ iter->iter0 = NULL;
+ if (ok == ITER_ERROR)
+ goto error;
+ }
+ if ((ok = ref_iterator_advance(iter->iter1)) != ITER_OK) {
+ iter->iter1 = NULL;
+ if (ok == ITER_ERROR)
+ goto error;
+ }
+ } else {
+ /*
+ * Advance the current iterator past the just-used
+ * entry:
+ */
+ if ((ok = ref_iterator_advance(*iter->current)) != ITER_OK) {
+ *iter->current = NULL;
+ if (ok == ITER_ERROR)
+ goto error;
+ }
+ }
+
+ /* Loop until we find an entry that we can yield. */
+ while (1) {
+ struct ref_iterator **secondary;
+ enum iterator_selection selection =
+ iter->select(iter->iter0, iter->iter1, iter->cb_data);
+
+ if (selection == ITER_SELECT_DONE) {
+ return ref_iterator_abort(ref_iterator);
+ } else if (selection == ITER_SELECT_ERROR) {
+ ref_iterator_abort(ref_iterator);
+ return ITER_ERROR;
+ }
+
+ if ((selection & ITER_CURRENT_SELECTION_MASK) == 0) {
+ iter->current = &iter->iter0;
+ secondary = &iter->iter1;
+ } else {
+ iter->current = &iter->iter1;
+ secondary = &iter->iter0;
+ }
+
+ if (selection & ITER_SKIP_SECONDARY) {
+ if ((ok = ref_iterator_advance(*secondary)) != ITER_OK) {
+ *secondary = NULL;
+ if (ok == ITER_ERROR)
+ goto error;
+ }
+ }
+
+ if (selection & ITER_YIELD_CURRENT) {
+ iter->base.refname = (*iter->current)->refname;
+ iter->base.oid = (*iter->current)->oid;
+ iter->base.flags = (*iter->current)->flags;
+ return ITER_OK;
+ }
+ }
+
+error:
+ ref_iterator_abort(ref_iterator);
+ return ITER_ERROR;
+}
+
+static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ struct merge_ref_iterator *iter =
+ (struct merge_ref_iterator *)ref_iterator;
+
+ if (!iter->current) {
+ die("BUG: peel called before advance for merge iterator");
+ }
+ return ref_iterator_peel(*iter->current, peeled);
+}
+
+static int merge_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct merge_ref_iterator *iter =
+ (struct merge_ref_iterator *)ref_iterator;
+ int ok = ITER_DONE;
+
+ if (iter->iter0) {
+ if (ref_iterator_abort(iter->iter0) != ITER_DONE)
+ ok = ITER_ERROR;
+ }
+ if (iter->iter1) {
+ if (ref_iterator_abort(iter->iter1) != ITER_DONE)
+ ok = ITER_ERROR;
+ }
+ base_ref_iterator_free(ref_iterator);
+ return ok;
+}
+
+static struct ref_iterator_vtable merge_ref_iterator_vtable = {
+ merge_ref_iterator_advance,
+ merge_ref_iterator_peel,
+ merge_ref_iterator_abort
+};
+
+struct ref_iterator *merge_ref_iterator_begin(
+ struct ref_iterator *iter0, struct ref_iterator *iter1,
+ ref_iterator_select_fn *select, void *cb_data)
+{
+ struct merge_ref_iterator *iter = xcalloc(1, sizeof(*iter));
+ struct ref_iterator *ref_iterator = &iter->base;
+
+ /*
+ * We can't do the same kind of is_empty_ref_iterator()-style
+ * optimization here as overlay_ref_iterator_begin() does,
+ * because we don't know the semantics of the select function.
+ * It might, for example, implement "intersect" by passing
+ * references through only if they exist in both iterators.
+ */
+
+ base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable);
+ iter->iter0 = iter0;
+ iter->iter1 = iter1;
+ iter->select = select;
+ iter->cb_data = cb_data;
+ iter->current = NULL;
+ return ref_iterator;
+}
+
+/*
+ * A ref_iterator_select_fn that overlays the items from front on top
+ * of those from back (like loose refs over packed refs). See
+ * overlay_ref_iterator_begin().
+ */
+static enum iterator_selection overlay_iterator_select(
+ struct ref_iterator *front, struct ref_iterator *back,
+ void *cb_data)
+{
+ int cmp;
+
+ if (!back)
+ return front ? ITER_SELECT_0 : ITER_SELECT_DONE;
+ else if (!front)
+ return ITER_SELECT_1;
+
+ cmp = strcmp(front->refname, back->refname);
+
+ if (cmp < 0)
+ return ITER_SELECT_0;
+ else if (cmp > 0)
+ return ITER_SELECT_1;
+ else
+ return ITER_SELECT_0_SKIP_1;
+}
+
+struct ref_iterator *overlay_ref_iterator_begin(
+ struct ref_iterator *front, struct ref_iterator *back)
+{
+ /*
+ * Optimization: if one of the iterators is empty, return the
+ * other one rather than incurring the overhead of wrapping
+ * them.
+ */
+ if (is_empty_ref_iterator(front)) {
+ ref_iterator_abort(front);
+ return back;
+ } else if (is_empty_ref_iterator(back)) {
+ ref_iterator_abort(back);
+ return front;
+ }
+
+ return merge_ref_iterator_begin(front, back,
+ overlay_iterator_select, NULL);
+}
+
+struct prefix_ref_iterator {
+ struct ref_iterator base;
+
+ struct ref_iterator *iter0;
+ char *prefix;
+ int trim;
+};
+
+static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct prefix_ref_iterator *iter =
+ (struct prefix_ref_iterator *)ref_iterator;
+ int ok;
+
+ while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
+ if (!starts_with(iter->iter0->refname, iter->prefix))
+ continue;
+
+ iter->base.refname = iter->iter0->refname + iter->trim;
+ iter->base.oid = iter->iter0->oid;
+ iter->base.flags = iter->iter0->flags;
+ return ITER_OK;
+ }
+
+ iter->iter0 = NULL;
+ if (ref_iterator_abort(ref_iterator) != ITER_DONE)
+ return ITER_ERROR;
+ return ok;
+}
+
+static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ struct prefix_ref_iterator *iter =
+ (struct prefix_ref_iterator *)ref_iterator;
+
+ return ref_iterator_peel(iter->iter0, peeled);
+}
+
+static int prefix_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct prefix_ref_iterator *iter =
+ (struct prefix_ref_iterator *)ref_iterator;
+ int ok = ITER_DONE;
+
+ if (iter->iter0)
+ ok = ref_iterator_abort(iter->iter0);
+ free(iter->prefix);
+ base_ref_iterator_free(ref_iterator);
+ return ok;
+}
+
+static struct ref_iterator_vtable prefix_ref_iterator_vtable = {
+ prefix_ref_iterator_advance,
+ prefix_ref_iterator_peel,
+ prefix_ref_iterator_abort
+};
+
+struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
+ const char *prefix,
+ int trim)
+{
+ struct prefix_ref_iterator *iter;
+ struct ref_iterator *ref_iterator;
+
+ if (!*prefix && !trim)
+ return iter0; /* optimization: no need to wrap iterator */
+
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+
+ base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable);
+
+ iter->iter0 = iter0;
+ iter->prefix = xstrdup(prefix);
+ iter->trim = trim;
+
+ return ref_iterator;
+}
+
+struct ref_iterator *current_ref_iter = NULL;
+
+int do_for_each_ref_iterator(struct ref_iterator *iter,
+ each_ref_fn fn, void *cb_data)
+{
+ int retval = 0, ok;
+ struct ref_iterator *old_ref_iter = current_ref_iter;
+
+ current_ref_iter = iter;
+ while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
+ retval = fn(iter->refname, iter->oid, iter->flags, cb_data);
+ if (retval) {
+ /*
+ * If ref_iterator_abort() returns ITER_ERROR,
+ * we ignore that error in deference to the
+ * callback function's return value.
+ */
+ ref_iterator_abort(iter);
+ goto out;
+ }
+ }
+
+out:
+ current_ref_iter = old_ref_iter;
+ if (ok == ITER_ERROR)
+ return -1;
+ return retval;
+}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 3a4f634cb4..efe584701b 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -15,7 +15,7 @@
/*
* Used as a flag in ref_update::flags when a loose ref is being
- * pruned.
+ * pruned. This flag must only be used when REF_NODEREF is set.
*/
#define REF_ISPRUNING 0x04
@@ -43,6 +43,19 @@
*/
/*
+ * 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
+
+/*
* Return true iff refname is minimally safe. "Safe" here means that
* deleting a loose reference by this name will not do any damage, for
* example by causing a file that is not a reference to be deleted.
@@ -109,8 +122,8 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1);
* extras and skip must be sorted.
*/
int verify_refname_available(const char *newname,
- struct string_list *extras,
- struct string_list *skip,
+ const struct string_list *extras,
+ const struct string_list *skip,
struct strbuf *err);
/*
@@ -130,27 +143,59 @@ int should_autocreate_reflog(const char *refname);
* not exist before update.
*/
struct ref_update {
+
/*
* If (flags & REF_HAVE_NEW), set the reference to this value:
*/
unsigned char new_sha1[20];
+
/*
* If (flags & REF_HAVE_OLD), check that the reference
* previously had this value:
*/
unsigned char old_sha1[20];
+
/*
* One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
- * REF_DELETING, and REF_ISPRUNING:
+ * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and
+ * REF_UPDATE_VIA_HEAD:
*/
unsigned int flags;
+
struct ref_lock *lock;
- int type;
+ unsigned int type;
char *msg;
+
+ /*
+ * If this ref_update was split off of a symref update via
+ * split_symref_update(), then this member points at that
+ * update. This is used for two purposes:
+ * 1. When reporting errors, we report the refname under which
+ * the update was originally requested.
+ * 2. When we read the old value of this reference, we
+ * propagate it back to its parent update for recording in
+ * the latter's reflog.
+ */
+ struct ref_update *parent_update;
+
const char refname[FLEX_ARRAY];
};
/*
+ * 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
+ * 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 char *msg);
+
+/*
* Transaction states.
* OPEN: The transaction is in a valid state and can accept new updates.
* An OPEN transaction can be committed.
@@ -204,12 +249,270 @@ int rename_ref_available(const char *oldname, const char *newname);
#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
/*
- * The common backend for the for_each_*ref* functions
+ * Reference iterators
+ *
+ * A reference iterator encapsulates the state of an in-progress
+ * iteration over references. Create an instance of `struct
+ * ref_iterator` via one of the functions in this module.
+ *
+ * A freshly-created ref_iterator doesn't yet point at a reference. To
+ * advance the iterator, call ref_iterator_advance(). If successful,
+ * this sets the iterator's refname, oid, and flags fields to describe
+ * the next reference and returns ITER_OK. The data pointed at by
+ * refname and oid belong to the iterator; if you want to retain them
+ * after calling ref_iterator_advance() again or calling
+ * ref_iterator_abort(), you must make a copy. When the iteration has
+ * been exhausted, ref_iterator_advance() releases any resources
+ * assocated with the iteration, frees the ref_iterator object, and
+ * returns ITER_DONE. If you want to abort the iteration early, call
+ * ref_iterator_abort(), which also frees the ref_iterator object and
+ * any associated resources. If there was an internal error advancing
+ * to the next entry, ref_iterator_advance() aborts the iteration,
+ * frees the ref_iterator, and returns ITER_ERROR.
+ *
+ * The reference currently being looked at can be peeled by calling
+ * ref_iterator_peel(). This function is often faster than peel_ref(),
+ * so it should be preferred when iterating over references.
+ *
+ * Putting it all together, a typical iteration looks like this:
+ *
+ * int ok;
+ * struct ref_iterator *iter = ...;
+ *
+ * while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
+ * if (want_to_stop_iteration()) {
+ * ok = ref_iterator_abort(iter);
+ * break;
+ * }
+ *
+ * // Access information about the current reference:
+ * if (!(iter->flags & REF_ISSYMREF))
+ * printf("%s is %s\n", iter->refname, oid_to_hex(&iter->oid));
+ *
+ * // If you need to peel the reference:
+ * ref_iterator_peel(iter, &oid);
+ * }
+ *
+ * if (ok != ITER_DONE)
+ * handle_error();
+ */
+struct ref_iterator {
+ struct ref_iterator_vtable *vtable;
+ const char *refname;
+ const struct object_id *oid;
+ unsigned int flags;
+};
+
+/*
+ * Advance the iterator to the first or next item and return ITER_OK.
+ * If the iteration is exhausted, free the resources associated with
+ * the ref_iterator and return ITER_DONE. On errors, free the iterator
+ * resources and return ITER_ERROR. It is a bug to use ref_iterator or
+ * call this function again after it has returned ITER_DONE or
+ * ITER_ERROR.
+ */
+int ref_iterator_advance(struct ref_iterator *ref_iterator);
+
+/*
+ * If possible, peel the reference currently being viewed by the
+ * iterator. Return 0 on success.
+ */
+int ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled);
+
+/*
+ * End the iteration before it has been exhausted, freeing the
+ * reference iterator and any associated resources and returning
+ * ITER_DONE. If the abort itself failed, return ITER_ERROR.
+ */
+int ref_iterator_abort(struct ref_iterator *ref_iterator);
+
+/*
+ * An iterator over nothing (its first ref_iterator_advance() call
+ * returns ITER_DONE).
+ */
+struct ref_iterator *empty_ref_iterator_begin(void);
+
+/*
+ * Return true iff ref_iterator is an empty_ref_iterator.
+ */
+int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
+
+/*
+ * A callback function used to instruct merge_ref_iterator how to
+ * interleave the entries from iter0 and iter1. The function should
+ * return one of the constants defined in enum iterator_selection. It
+ * must not advance either of the iterators itself.
+ *
+ * The function must be prepared to handle the case that iter0 and/or
+ * iter1 is NULL, which indicates that the corresponding sub-iterator
+ * has been exhausted. Its return value must be consistent with the
+ * current states of the iterators; e.g., it must not return
+ * ITER_SKIP_1 if iter1 has already been exhausted.
*/
-int do_for_each_ref(const char *submodule, const char *base,
- each_ref_fn fn, int trim, int flags, void *cb_data);
+typedef enum iterator_selection ref_iterator_select_fn(
+ struct ref_iterator *iter0, struct ref_iterator *iter1,
+ void *cb_data);
+/*
+ * Iterate over the entries from iter0 and iter1, with the values
+ * interleaved as directed by the select function. The iterator takes
+ * ownership of iter0 and iter1 and frees them when the iteration is
+ * over.
+ */
+struct ref_iterator *merge_ref_iterator_begin(
+ struct ref_iterator *iter0, struct ref_iterator *iter1,
+ ref_iterator_select_fn *select, void *cb_data);
+
+/*
+ * An iterator consisting of the union of the entries from front and
+ * back. If there are entries common to the two sub-iterators, use the
+ * one from front. Each iterator must iterate over its entries in
+ * strcmp() order by refname for this to work.
+ *
+ * The new iterator takes ownership of its arguments and frees them
+ * when the iteration is over. As a convenience to callers, if front
+ * or back is an empty_ref_iterator, then abort that one immediately
+ * and return the other iterator directly, without wrapping it.
+ */
+struct ref_iterator *overlay_ref_iterator_begin(
+ struct ref_iterator *front, struct ref_iterator *back);
+
+/*
+ * Wrap iter0, only letting through the references whose names start
+ * with prefix. If trim is set, set iter->refname to the name of the
+ * reference with that many characters trimmed off the front;
+ * otherwise set it to the full refname. The new iterator takes over
+ * ownership of iter0 and frees it when iteration is over. It makes
+ * its own copy of prefix.
+ *
+ * As an convenience to callers, if prefix is the empty string and
+ * trim is zero, this function returns iter0 directly, without
+ * wrapping it.
+ */
+struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
+ const char *prefix,
+ int trim);
+
+/*
+ * Iterate over the packed and loose references in the specified
+ * submodule that are within find_containing_dir(prefix). If prefix is
+ * NULL or the empty string, iterate over all references in the
+ * submodule.
+ */
+struct ref_iterator *files_ref_iterator_begin(const char *submodule,
+ const char *prefix,
+ unsigned int flags);
+
+/*
+ * Iterate over the references in the main ref_store that have a
+ * reflog. The paths within a directory are iterated over in arbitrary
+ * order.
+ */
+struct ref_iterator *files_reflog_iterator_begin(void);
+
+/* Internal implementation of reference iteration: */
+
+/*
+ * Base class constructor for ref_iterators. Initialize the
+ * ref_iterator part of iter, setting its vtable pointer as specified.
+ * This is meant to be called only by the initializers of derived
+ * classes.
+ */
+void base_ref_iterator_init(struct ref_iterator *iter,
+ struct ref_iterator_vtable *vtable);
+
+/*
+ * Base class destructor for ref_iterators. Destroy the ref_iterator
+ * part of iter and shallow-free the object. This is meant to be
+ * called only by the destructors of derived classes.
+ */
+void base_ref_iterator_free(struct ref_iterator *iter);
+
+/* Virtual function declarations for ref_iterators: */
+
+typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
+
+typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,
+ struct object_id *peeled);
+
+/*
+ * Implementations of this function should free any resources specific
+ * to the derived class, then call base_ref_iterator_free() to clean
+ * up and free the ref_iterator object.
+ */
+typedef int ref_iterator_abort_fn(struct ref_iterator *ref_iterator);
+
+struct ref_iterator_vtable {
+ ref_iterator_advance_fn *advance;
+ ref_iterator_peel_fn *peel;
+ ref_iterator_abort_fn *abort;
+};
+
+/*
+ * current_ref_iter is a performance hack: when iterating over
+ * references using the for_each_ref*() functions, current_ref_iter is
+ * set to the reference iterator before calling the callback function.
+ * If the callback function calls peel_ref(), then peel_ref() first
+ * checks whether the reference to be peeled is the one referred to by
+ * the iterator (it usually is) and if so, asks the iterator for the
+ * peeled version of the reference if it is available. This avoids a
+ * refname lookup in a common case. current_ref_iter is set to NULL
+ * when the iteration is over.
+ */
+extern struct ref_iterator *current_ref_iter;
+
+/*
+ * The common backend for the for_each_*ref* functions. Call fn for
+ * each reference in iter. If the iterator itself ever returns
+ * ITER_ERROR, return -1. If fn ever returns a non-zero value, stop
+ * the iteration and return that value. Otherwise, return 0. In any
+ * case, free the iterator when done. This function is basically an
+ * adapter between the callback style of reference iteration and the
+ * iterator style.
+ */
+int do_for_each_ref_iterator(struct ref_iterator *iter,
+ each_ref_fn fn, void *cb_data);
+
+/*
+ * Read the specified reference from the filesystem or packed refs
+ * file, non-recursively. Set type to describe the reference, and:
+ *
+ * - If refname is the name of a normal reference, fill in sha1
+ * (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
+ * validating that referent is a valid reference name.
+ *
+ * WARNING: refname might be used as part of a filename, so it is
+ * important from a security standpoint that it be safe in the sense
+ * of refname_is_safe(). Moreover, for symrefs this function sets
+ * referent to whatever the repository says, which might not be a
+ * properly-formatted or even safe reference name. NEITHER INPUT NOR
+ * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION.
+ *
+ * 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
+ * EINVAL, and return -1. If there is another error reading the ref,
+ * set errno appropriately and return -1.
+ *
+ * Backend-specific flags might be set in type as well, regardless of
+ * outcome.
+ *
+ * It is OK for refname to point into referent. If so:
+ *
+ * - if the function succeeds with REF_ISSYMREF, referent will be
+ * overwritten and the memory formerly pointed to by it might be
+ * changed or even freed.
+ *
+ * - in all other cases, referent will be untouched, and therefore
+ * refname will still be valid and unchanged.
+ */
int read_raw_ref(const char *refname, unsigned char *sha1,
- struct strbuf *symref, unsigned int *flags);
+ struct strbuf *referent, unsigned int *type);
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/revision.c b/revision.c
index edba5b79bc..15873bf24d 100644
--- a/revision.c
+++ b/revision.c
@@ -1973,16 +1973,16 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--grep-debug")) {
revs->grep_filter.debug = 1;
} else if (!strcmp(arg, "--basic-regexp")) {
- grep_set_pattern_type_option(GREP_PATTERN_TYPE_BRE, &revs->grep_filter);
+ revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_BRE;
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
- grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, &revs->grep_filter);
+ revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_ERE;
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
revs->grep_filter.regflags |= REG_ICASE;
DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
} else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
- grep_set_pattern_type_option(GREP_PATTERN_TYPE_FIXED, &revs->grep_filter);
+ revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_FIXED;
} else if (!strcmp(arg, "--perl-regexp")) {
- grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter);
+ revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_PCRE;
} else if (!strcmp(arg, "--all-match")) {
revs->grep_filter.all_match = 1;
} else if (!strcmp(arg, "--invert-grep")) {
diff --git a/send-pack.c b/send-pack.c
index 299d303848..3a842ac2d1 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -260,6 +260,7 @@ static int generate_push_cert(struct strbuf *req_buf,
const char *push_cert_nonce)
{
const struct ref *ref;
+ struct string_list_item *item;
char *signing_key = xstrdup(get_signing_key());
const char *cp, *np;
struct strbuf cert = STRBUF_INIT;
@@ -276,6 +277,9 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
+ if (args->push_options)
+ for_each_string_list_item(item, args->push_options)
+ strbuf_addf(&cert, "push-option %s\n", item->string);
strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
@@ -370,6 +374,8 @@ int send_pack(struct send_pack_args *args,
int agent_supported = 0;
int use_atomic = 0;
int atomic_supported = 0;
+ int use_push_options = 0;
+ int push_options_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -392,6 +398,8 @@ int send_pack(struct send_pack_args *args,
args->use_thin_pack = 0;
if (server_supports("atomic"))
atomic_supported = 1;
+ if (server_supports("push-options"))
+ push_options_supported = 1;
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
@@ -418,6 +426,11 @@ int send_pack(struct send_pack_args *args,
use_atomic = atomic_supported && args->atomic;
+ if (args->push_options && !push_options_supported)
+ die(_("the receiving end does not support push options"));
+
+ use_push_options = push_options_supported && args->push_options;
+
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
if (use_sideband)
@@ -426,6 +439,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " quiet");
if (use_atomic)
strbuf_addstr(&cap_buf, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap_buf, " push-options");
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
@@ -512,6 +527,18 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
+ if (use_push_options) {
+ struct string_list_item *item;
+ struct strbuf sb = STRBUF_INIT;
+
+ for_each_string_list_item(item, args->push_options)
+ packet_buf_write(&sb, "%s", item->string);
+
+ write_or_die(out, sb.buf, sb.len);
+ packet_flush(out);
+ strbuf_release(&sb);
+ }
+
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
demux.proc = sideband_demux;
diff --git a/send-pack.h b/send-pack.h
index 57f222abcc..67fc40f4ec 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,8 @@
#ifndef SEND_PACK_H
#define SEND_PACK_H
+#include "string-list.h"
+
/* Possible values for push_cert field in send_pack_args. */
#define SEND_PACK_PUSH_CERT_NEVER 0
#define SEND_PACK_PUSH_CERT_IF_ASKED 1
@@ -21,6 +23,7 @@ struct send_pack_args {
push_cert:2,
stateless_rpc:1,
atomic:1;
+ const struct string_list *push_options;
};
struct option;
diff --git a/sha1_file.c b/sha1_file.c
index 4eb3318ae0..3066b5f71c 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2257,7 +2257,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
if (do_check_packed_object_crc && p->index_version > 1) {
struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
- unsigned long len = revidx[1].offset - obj_offset;
+ off_t len = revidx[1].offset - obj_offset;
if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) {
const unsigned char *sha1 =
nth_packed_object_sha1(p, revidx->nr);
diff --git a/strbuf.c b/strbuf.c
index 1ba600bd78..f3bd5719c6 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -197,6 +197,13 @@ void strbuf_add(struct strbuf *sb, const void *data, size_t len)
strbuf_setlen(sb, sb->len + len);
}
+void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
+{
+ strbuf_grow(sb, sb2->len);
+ memcpy(sb->buf + sb->len, sb2->buf, sb2->len);
+ strbuf_setlen(sb, sb->len + sb2->len);
+}
+
void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
{
strbuf_grow(sb, len);
diff --git a/strbuf.h b/strbuf.h
index 83c5c98530..ba8d5f1d46 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -263,11 +263,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s)
/**
* Copy the contents of another buffer at the end of the current one.
*/
-static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
-{
- strbuf_grow(sb, sb2->len);
- strbuf_add(sb, sb2->buf, sb2->len);
-}
+extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
/**
* Copy part of the buffer from a given position till a given length to the
diff --git a/submodule-config.c b/submodule-config.c
index 077db4054f..d22a851d27 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -371,9 +371,9 @@ static int parse_config(const char *var, const char *value, void *data)
}
static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1,
- unsigned char *gitmodules_sha1)
+ unsigned char *gitmodules_sha1,
+ struct strbuf *rev)
{
- struct strbuf rev = STRBUF_INIT;
int ret = 0;
if (is_null_sha1(commit_sha1)) {
@@ -381,11 +381,10 @@ static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1,
return 1;
}
- strbuf_addf(&rev, "%s:.gitmodules", sha1_to_hex(commit_sha1));
- if (get_sha1(rev.buf, gitmodules_sha1) >= 0)
+ strbuf_addf(rev, "%s:.gitmodules", sha1_to_hex(commit_sha1));
+ if (get_sha1(rev->buf, gitmodules_sha1) >= 0)
ret = 1;
- strbuf_release(&rev);
return ret;
}
@@ -399,7 +398,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,
{
struct strbuf rev = STRBUF_INIT;
unsigned long config_size;
- char *config;
+ char *config = NULL;
unsigned char sha1[20];
enum object_type type;
const struct submodule *submodule = NULL;
@@ -420,8 +419,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,
return entry->config;
}
- if (!gitmodule_sha1_from_commit(commit_sha1, sha1))
- return NULL;
+ if (!gitmodule_sha1_from_commit(commit_sha1, sha1, &rev))
+ goto out;
switch (lookup_type) {
case lookup_name:
@@ -432,24 +431,20 @@ static const struct submodule *config_from(struct submodule_cache *cache,
break;
}
if (submodule)
- return submodule;
+ goto out;
config = read_sha1_file(sha1, &type, &config_size);
- if (!config)
- return NULL;
-
- if (type != OBJ_BLOB) {
- free(config);
- return NULL;
- }
+ if (!config || type != OBJ_BLOB)
+ goto out;
/* fill the submodule config into the cache */
parameter.cache = cache;
parameter.commit_sha1 = commit_sha1;
parameter.gitmodules_sha1 = sha1;
parameter.overwrite = 0;
- git_config_from_mem(parse_config, "submodule-blob", rev.buf,
+ git_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,
config, config_size, &parameter);
+ strbuf_release(&rev);
free(config);
switch (lookup_type) {
@@ -460,6 +455,11 @@ static const struct submodule *config_from(struct submodule_cache *cache,
default:
return NULL;
}
+
+out:
+ strbuf_release(&rev);
+ free(config);
+ return submodule;
}
static const struct submodule *config_from_path(struct submodule_cache *cache,
diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c
index 61049b87a0..2f144d539a 100644
--- a/t/helper/test-submodule-config.c
+++ b/t/helper/test-submodule-config.c
@@ -23,7 +23,7 @@ int cmd_main(int argc, const char **argv)
arg++;
my_argc--;
- while (starts_with(arg[0], "--")) {
+ while (arg[0] && starts_with(arg[0], "--")) {
if (!strcmp(arg[0], "--url"))
output_url = 1;
if (!strcmp(arg[0], "--name"))
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index fb8823224e..688313ed5c 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -65,81 +65,22 @@ svn_cmd () {
svn "$orig_svncmd" --config-dir "$svnconf" "$@"
}
-prepare_httpd () {
- for d in \
- "$SVN_HTTPD_PATH" \
- /usr/sbin/apache2 \
- /usr/sbin/httpd \
- ; do
- if test -f "$d"
- then
- SVN_HTTPD_PATH="$d"
- break
- fi
- done
- if test -z "$SVN_HTTPD_PATH"
- then
- echo >&2 '*** error: Apache not found'
- return 1
- fi
- for d in \
- "$SVN_HTTPD_MODULE_PATH" \
- /usr/lib/apache2/modules \
- /usr/libexec/apache2 \
- ; do
- if test -d "$d"
- then
- SVN_HTTPD_MODULE_PATH="$d"
- break
- fi
- done
- if test -z "$SVN_HTTPD_MODULE_PATH"
- then
- echo >&2 '*** error: Apache module dir not found'
- return 1
- fi
- if test ! -f "$SVN_HTTPD_MODULE_PATH/mod_dav_svn.so"
- then
- echo >&2 '*** error: Apache module "mod_dav_svn" not found'
- return 1
- fi
-
- repo_base_path="${1-svn}"
- mkdir "$GIT_DIR"/logs
-
- cat > "$GIT_DIR/httpd.conf" <<EOF
-ServerName "git svn test"
-ServerRoot "$GIT_DIR"
-DocumentRoot "$GIT_DIR"
-PidFile "$GIT_DIR/httpd.pid"
-LockFile logs/accept.lock
-Listen 127.0.0.1:$SVN_HTTPD_PORT
-LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
-LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
-<Location /$repo_base_path>
- DAV svn
- SVNPath "$rawsvnrepo"
-</Location>
-EOF
-}
-
-start_httpd () {
- if test -z "$SVN_HTTPD_PORT"
- then
- echo >&2 'SVN_HTTPD_PORT is not defined!'
- return
- fi
-
- prepare_httpd "$1" || return 1
-
- "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
- svnrepo="http://127.0.0.1:$SVN_HTTPD_PORT/$repo_base_path"
-}
-
-stop_httpd () {
- test -z "$SVN_HTTPD_PORT" && return
- test ! -f "$GIT_DIR/httpd.conf" && return
- "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
+maybe_start_httpd () {
+ loc=${1-svn}
+
+ test_tristate GIT_SVN_TEST_HTTPD
+ case $GIT_SVN_TEST_HTTPD in
+ true)
+ . "$TEST_DIRECTORY"/lib-httpd.sh
+ LIB_HTTPD_SVN="$loc"
+ start_httpd
+ ;;
+ *)
+ stop_httpd () {
+ : noop
+ }
+ ;;
+ esac
}
convert_to_rev_db () {
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index ac2cbee250..435a37465a 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -24,7 +24,7 @@
# LIB_HTTPD_MODULE_PATH web server modules path
# LIB_HTTPD_PORT listening port
# LIB_HTTPD_DAV enable DAV
-# LIB_HTTPD_SVN enable SVN
+# LIB_HTTPD_SVN enable SVN at given location (e.g. "svn")
# LIB_HTTPD_SSL enable SSL
#
# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
@@ -162,8 +162,10 @@ prepare_httpd() {
if test -n "$LIB_HTTPD_SVN"
then
HTTPD_PARA="$HTTPD_PARA -DSVN"
- rawsvnrepo="$HTTPD_ROOT_PATH/svnrepo"
- svnrepo="http://127.0.0.1:$LIB_HTTPD_PORT/svn"
+ LIB_HTTPD_SVNPATH="$rawsvnrepo"
+ svnrepo="http://127.0.0.1:$LIB_HTTPD_PORT/"
+ svnrepo="$svnrepo$LIB_HTTPD_SVN"
+ export LIB_HTTPD_SVN LIB_HTTPD_SVNPATH
fi
fi
}
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 018a83a5a1..c3e631394f 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -208,8 +208,8 @@ RewriteRule ^/half-auth-complete/ - [E=AUTHREQUIRED:yes]
<IfDefine SVN>
LoadModule dav_svn_module modules/mod_dav_svn.so
- <Location /svn>
+ <Location /${LIB_HTTPD_SVN}>
DAV svn
- SVNPath svnrepo
+ SVNPath "${LIB_HTTPD_SVNPATH}"
</Location>
</IfDefine>
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 60811a3a7c..1aa5093f36 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -834,7 +834,7 @@ test_expect_success 'git write-tree should be able to write an empty tree' '
'
test_expect_success 'validate object ID of a known tree' '
- test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+ test "$tree" = $EMPTY_TREE
'
# Various types of objects
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index 4c8cf58512..c0c910867d 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -46,7 +46,10 @@ check_show rfc2822 "$TIME" 'Wed, 15 Jun 2016 16:13:20 +0200'
check_show short "$TIME" '2016-06-15'
check_show default "$TIME" 'Wed Jun 15 16:13:20 2016 +0200'
check_show raw "$TIME" '1466000000 +0200'
+check_show unix "$TIME" '1466000000'
check_show iso-local "$TIME" '2016-06-15 14:13:20 +0000'
+check_show raw-local "$TIME" '1466000000 +0000'
+check_show unix-local "$TIME" '1466000000'
# arbitrary time absurdly far in the future
FUTURE="5758122296 -0400"
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 7bac2bcf26..e799e59544 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -268,4 +268,15 @@ test_expect_success 'disable filter with empty override' '
test_must_be_empty err
'
+test_expect_success 'diff does not reuse worktree files that need cleaning' '
+ test_config filter.counter.clean "echo . >>count; sed s/^/clean:/" &&
+ echo "file filter=counter" >.gitattributes &&
+ test_commit one file &&
+ test_commit two file &&
+
+ >count &&
+ git diff-tree -p HEAD &&
+ test_line_count = 0 count
+'
+
test_done
diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh
index c164b4662a..d0bee08b2e 100755
--- a/t/t0025-crlf-auto.sh
+++ b/t/t0025-crlf-auto.sh
@@ -114,7 +114,7 @@ test_expect_success 'autocrlf=true does not normalize CRLF files' '
test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff"
'
-test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' '
+test_expect_success 'text=auto, autocrlf=true does not normalize CRLF files' '
rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL &&
git config core.autocrlf true &&
@@ -126,7 +126,7 @@ test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' '
LFonlydiff=$(git diff LFonly) &&
CRLFonlydiff=$(git diff CRLFonly) &&
LFwithNULdiff=$(git diff LFwithNUL) &&
- test -z "$LFonlydiff" -a -n "$CRLFonlydiff" -a -z "$LFwithNULdiff"
+ test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff"
'
test_expect_success 'text=auto, autocrlf=true does not normalize binary files' '
diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index 93725895a4..2860d2d08b 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -175,8 +175,8 @@ attr_ascii () {
text,lf) echo "text eol=lf" ;;
text,crlf) echo "text eol=crlf" ;;
auto,) echo "text=auto" ;;
- auto,lf) echo "text eol=lf" ;;
- auto,crlf) echo "text eol=crlf" ;;
+ auto,lf) echo "text=auto eol=lf" ;;
+ auto,crlf) echo "text=auto eol=crlf" ;;
lf,) echo "text eol=lf" ;;
crlf,) echo "text eol=crlf" ;;
,) echo "" ;;
@@ -397,10 +397,9 @@ commit_chk_wrnNNO "" "" false "" "" "" ""
commit_chk_wrnNNO "" "" true LF_CRLF "" "" "" ""
commit_chk_wrnNNO "" "" input "" "" "" "" ""
-commit_chk_wrnNNO "auto" "" false "$WILC" "$WICL" "$WAMIX" "" ""
-commit_chk_wrnNNO "auto" "" true LF_CRLF "" LF_CRLF "" ""
-commit_chk_wrnNNO "auto" "" input "" CRLF_LF CRLF_LF "" ""
-
+commit_chk_wrnNNO "auto" "" false "$WILC" "" "" "" ""
+commit_chk_wrnNNO "auto" "" true LF_CRLF "" "" "" ""
+commit_chk_wrnNNO "auto" "" input "" "" "" "" ""
for crlf in true false input
do
commit_chk_wrnNNO -text "" $crlf "" "" "" "" ""
@@ -408,8 +407,8 @@ do
commit_chk_wrnNNO -text crlf $crlf "" "" "" "" ""
commit_chk_wrnNNO "" lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF
commit_chk_wrnNNO "" crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF ""
- commit_chk_wrnNNO auto lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF
- commit_chk_wrnNNO auto crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF ""
+ commit_chk_wrnNNO auto lf $crlf "" "" "" "" ""
+ commit_chk_wrnNNO auto crlf $crlf LF_CRLF "" "" "" ""
commit_chk_wrnNNO text lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF
commit_chk_wrnNNO text crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF ""
done
@@ -454,9 +453,9 @@ do
check_in_repo_NNO -text "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
check_in_repo_NNO -text lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
check_in_repo_NNO -text crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
- check_in_repo_NNO auto "" $crlf LF LF LF LF_mix_CR CRLF_nul
- check_in_repo_NNO auto lf $crlf LF LF LF LF_mix_CR LF_nul
- check_in_repo_NNO auto crlf $crlf LF LF LF LF_mix_CR LF_nul
+ check_in_repo_NNO auto "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+ check_in_repo_NNO auto lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+ check_in_repo_NNO auto crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
check_in_repo_NNO text "" $crlf LF LF LF LF_mix_CR LF_nul
check_in_repo_NNO text lf $crlf LF LF LF LF_mix_CR LF_nul
check_in_repo_NNO text crlf $crlf LF LF LF LF_mix_CR LF_nul
@@ -509,7 +508,7 @@ do
checkout_files text "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
# currently the same as text, eol=XXX
checkout_files auto "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
- checkout_files auto "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+ checkout_files auto "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
done
# core.autocrlf false, different core.eol
@@ -517,7 +516,7 @@ do
# core.autocrlf true
checkout_files "" "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
# text: core.autocrlf = true overrides core.eol
- checkout_files auto "$id" "" true "$ceol" CRLF CRLF CRLF LF_mix_CR LF_nul
+ checkout_files auto "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
checkout_files text "$id" "" true "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
# text: core.autocrlf = input overrides core.eol
checkout_files text "$id" "" input "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
@@ -531,8 +530,8 @@ do
checkout_files text "$id" "" false "" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL
checkout_files text "$id" "" false native $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL
# auto: core.autocrlf=false and core.eol unset(or native) uses native eol
- checkout_files auto "$id" "" false "" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul
- checkout_files auto "$id" "" false native $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul
+ checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
done
# Should be the last test case: remove some files from the worktree
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index e5fa235d3a..c167f606ca 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -15,11 +15,11 @@ test_description='sparse checkout tests
. "$TEST_DIRECTORY"/lib-read-tree.sh
test_expect_success 'setup' '
- cat >expected <<-\EOF &&
+ cat >expected <<-EOF &&
100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
- 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added
- 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/addedtoo
- 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added
+ 100644 $EMPTY_BLOB 0 sub/added
+ 100644 $EMPTY_BLOB 0 sub/addedtoo
+ 100644 $EMPTY_BLOB 0 subsub/added
EOF
cat >expected.swt <<-\EOF &&
H init.t
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
index f9f3d1391f..096dbffecc 100755
--- a/t/t1050-large.sh
+++ b/t/t1050-large.sh
@@ -177,10 +177,9 @@ test_expect_success 'zip achiving, deflate' '
git archive --format=zip HEAD >/dev/null
'
-test_expect_success 'fsck' '
- test_must_fail git fsck 2>err &&
- n=$(grep "error: attempting to allocate .* over limit" err | wc -l) &&
- test "$n" -gt 1
+test_expect_success 'fsck large blobs' '
+ git fsck 2>err &&
+ test_must_be_empty err
'
test_done
diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh
index b7e9b4fc5b..ae66ba5bab 100755
--- a/t/t1100-commit-tree-options.sh
+++ b/t/t1100-commit-tree-options.sh
@@ -15,7 +15,7 @@ Also make sure that command line parser understands the normal
. ./test-lib.sh
cat >expected <<EOF
-tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+tree $EMPTY_TREE
author Author Name <author@email> 1117148400 +0000
committer Committer Name <committer@email> 1117150200 +0000
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 75fa6548c4..d4fb977060 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -23,7 +23,7 @@ test_expect_success setup '
m=refs/heads/master
n_dir=refs/heads/gu
n=$n_dir/fixes
-outside=foo
+outside=refs/foo
test_expect_success \
"create $m" \
@@ -479,7 +479,7 @@ test_expect_success 'stdin fails with duplicate refs' '
create $a $m
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
+ grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err
'
test_expect_success 'stdin create ref works' '
@@ -880,7 +880,7 @@ test_expect_success 'stdin -z fails option with unknown name' '
test_expect_success 'stdin -z fails with duplicate refs' '
printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
+ grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err
'
test_expect_success 'stdin -z create ref works' '
@@ -1102,6 +1102,41 @@ test_expect_success 'stdin -z delete refs works with packed and loose refs' '
test_must_fail git rev-parse --verify -q $c
'
+test_expect_success 'fails with duplicate HEAD update' '
+ git branch target1 $A &&
+ git checkout target1 &&
+ cat >stdin <<-EOF &&
+ update refs/heads/target1 $C
+ option no-deref
+ update HEAD $B
+ EOF
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: multiple updates for '\''HEAD'\'' (including one via its referent .refs/heads/target1.) are not allowed" err &&
+ echo "refs/heads/target1" >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual &&
+ echo "$A" >expect &&
+ git rev-parse refs/heads/target1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'fails with duplicate ref update via symref' '
+ git branch target2 $A &&
+ git symbolic-ref refs/heads/symref2 refs/heads/target2 &&
+ cat >stdin <<-EOF &&
+ update refs/heads/target2 $C
+ update refs/heads/symref2 $B
+ EOF
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: multiple updates for '\''refs/heads/target2'\'' (including one via symref .refs/heads/symref2.) are not allowed" err &&
+ echo "refs/heads/target2" >expect &&
+ git symbolic-ref refs/heads/symref2 >actual &&
+ test_cmp expect actual &&
+ echo "$A" >expect &&
+ git rev-parse refs/heads/target2 >actual &&
+ test_cmp expect actual
+'
+
run_with_limited_open_files () {
(ulimit -n 32 && "$@")
}
diff --git a/t/t1404-update-ref-df-conflicts.sh b/t/t1404-update-ref-df-conflicts.sh
deleted file mode 100755
index 66bafb5cf4..0000000000
--- a/t/t1404-update-ref-df-conflicts.sh
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/sh
-
-test_description='Test git update-ref with D/F conflicts'
-. ./test-lib.sh
-
-test_update_rejected () {
- prefix="$1" &&
- before="$2" &&
- pack="$3" &&
- create="$4" &&
- error="$5" &&
- printf "create $prefix/%s $C\n" $before |
- git update-ref --stdin &&
- git for-each-ref $prefix >unchanged &&
- if $pack
- then
- git pack-refs --all
- fi &&
- printf "create $prefix/%s $C\n" $create >input &&
- test_must_fail git update-ref --stdin <input 2>output.err &&
- grep -F "$error" output.err &&
- git for-each-ref $prefix >actual &&
- test_cmp unchanged actual
-}
-
-Q="'"
-
-test_expect_success 'setup' '
-
- git commit --allow-empty -m Initial &&
- C=$(git rev-parse HEAD)
-
-'
-
-test_expect_success 'existing loose ref is a simple prefix of new' '
-
- prefix=refs/1l &&
- test_update_rejected $prefix "a c e" false "b c/x d" \
- "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
-
-'
-
-test_expect_success 'existing packed ref is a simple prefix of new' '
-
- prefix=refs/1p &&
- test_update_rejected $prefix "a c e" true "b c/x d" \
- "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
-
-'
-
-test_expect_success 'existing loose ref is a deeper prefix of new' '
-
- prefix=refs/2l &&
- test_update_rejected $prefix "a c e" false "b c/x/y d" \
- "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
-
-'
-
-test_expect_success 'existing packed ref is a deeper prefix of new' '
-
- prefix=refs/2p &&
- test_update_rejected $prefix "a c e" true "b c/x/y d" \
- "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
-
-'
-
-test_expect_success 'new ref is a simple prefix of existing loose' '
-
- prefix=refs/3l &&
- test_update_rejected $prefix "a c/x e" false "b c d" \
- "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
-
-'
-
-test_expect_success 'new ref is a simple prefix of existing packed' '
-
- prefix=refs/3p &&
- test_update_rejected $prefix "a c/x e" true "b c d" \
- "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
-
-'
-
-test_expect_success 'new ref is a deeper prefix of existing loose' '
-
- prefix=refs/4l &&
- test_update_rejected $prefix "a c/x/y e" false "b c d" \
- "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
-
-'
-
-test_expect_success 'new ref is a deeper prefix of existing packed' '
-
- prefix=refs/4p &&
- test_update_rejected $prefix "a c/x/y e" true "b c d" \
- "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
-
-'
-
-test_expect_success 'one new ref is a simple prefix of another' '
-
- prefix=refs/5 &&
- test_update_rejected $prefix "a e" false "b c c/x d" \
- "cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time"
-
-'
-
-test_done
diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh
new file mode 100755
index 0000000000..c34ece48f5
--- /dev/null
+++ b/t/t1404-update-ref-errors.sh
@@ -0,0 +1,407 @@
+#!/bin/sh
+
+test_description='Test git update-ref error handling'
+. ./test-lib.sh
+
+# Create some references, perhaps run pack-refs --all, then try to
+# create some more references. Ensure that the second creation fails
+# with the correct error message.
+# Usage: test_update_rejected <before> <pack> <create> <error>
+# <before> is a ws-separated list of refs to create before the test
+# <pack> (true or false) tells whether to pack the refs before the test
+# <create> is a list of variables to attempt creating
+# <error> is a string to look for in the stderr of update-ref.
+# All references are created in the namespace specified by the current
+# value of $prefix.
+test_update_rejected () {
+ before="$1" &&
+ pack="$2" &&
+ create="$3" &&
+ error="$4" &&
+ printf "create $prefix/%s $C\n" $before |
+ git update-ref --stdin &&
+ git for-each-ref $prefix >unchanged &&
+ if $pack
+ then
+ git pack-refs --all
+ fi &&
+ printf "create $prefix/%s $C\n" $create >input &&
+ test_must_fail git update-ref --stdin <input 2>output.err &&
+ grep -F "$error" output.err &&
+ git for-each-ref $prefix >actual &&
+ test_cmp unchanged actual
+}
+
+Q="'"
+
+test_expect_success 'setup' '
+
+ git commit --allow-empty -m Initial &&
+ C=$(git rev-parse HEAD) &&
+ git commit --allow-empty -m Second &&
+ D=$(git rev-parse HEAD) &&
+ git commit --allow-empty -m Third &&
+ E=$(git rev-parse HEAD)
+'
+
+test_expect_success 'existing loose ref is a simple prefix of new' '
+
+ prefix=refs/1l &&
+ test_update_rejected "a c e" false "b c/x d" \
+ "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
+
+'
+
+test_expect_success 'existing packed ref is a simple prefix of new' '
+
+ prefix=refs/1p &&
+ test_update_rejected "a c e" true "b c/x d" \
+ "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
+
+'
+
+test_expect_success 'existing loose ref is a deeper prefix of new' '
+
+ prefix=refs/2l &&
+ test_update_rejected "a c e" false "b c/x/y d" \
+ "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
+
+'
+
+test_expect_success 'existing packed ref is a deeper prefix of new' '
+
+ prefix=refs/2p &&
+ test_update_rejected "a c e" true "b c/x/y d" \
+ "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
+
+'
+
+test_expect_success 'new ref is a simple prefix of existing loose' '
+
+ prefix=refs/3l &&
+ test_update_rejected "a c/x e" false "b c d" \
+ "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
+
+'
+
+test_expect_success 'new ref is a simple prefix of existing packed' '
+
+ prefix=refs/3p &&
+ test_update_rejected "a c/x e" true "b c d" \
+ "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
+
+'
+
+test_expect_success 'new ref is a deeper prefix of existing loose' '
+
+ prefix=refs/4l &&
+ test_update_rejected "a c/x/y e" false "b c d" \
+ "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
+
+'
+
+test_expect_success 'new ref is a deeper prefix of existing packed' '
+
+ prefix=refs/4p &&
+ test_update_rejected "a c/x/y e" true "b c d" \
+ "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
+
+'
+
+test_expect_success 'one new ref is a simple prefix of another' '
+
+ prefix=refs/5 &&
+ test_update_rejected "a e" false "b c c/x d" \
+ "cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time"
+
+'
+
+test_expect_success 'empty directory should not fool rev-parse' '
+ prefix=refs/e-rev-parse &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ echo "$C" >expected &&
+ git rev-parse $prefix/foo >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'empty directory should not fool for-each-ref' '
+ prefix=refs/e-for-each-ref &&
+ git update-ref $prefix/foo $C &&
+ git for-each-ref $prefix >expected &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ git for-each-ref $prefix >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'empty directory should not fool create' '
+ prefix=refs/e-create &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "create %s $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool verify' '
+ prefix=refs/e-verify &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "verify %s $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 1-arg update' '
+ prefix=refs/e-update-1 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "update %s $D\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 2-arg update' '
+ prefix=refs/e-update-2 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "update %s $D $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 0-arg delete' '
+ prefix=refs/e-delete-0 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "delete %s\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 1-arg delete' '
+ prefix=refs/e-delete-1 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "delete %s $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+# Test various errors when reading the old values of references...
+
+test_expect_success 'missing old value blocks update' '
+ prefix=refs/missing-update &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q
+ EOF
+ printf "%s\n" "update $prefix/foo $E $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'incorrect old value blocks update' '
+ prefix=refs/incorrect-update &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D
+ EOF
+ printf "%s\n" "update $prefix/foo $E $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'existing old value blocks create' '
+ prefix=refs/existing-create &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: reference already exists
+ EOF
+ printf "%s\n" "create $prefix/foo $E" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'incorrect old value blocks delete' '
+ prefix=refs/incorrect-delete &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D
+ EOF
+ printf "%s\n" "delete $prefix/foo $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'missing old value blocks indirect update' '
+ prefix=refs/missing-indirect-update &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q
+ EOF
+ printf "%s\n" "update $prefix/symref $E $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'incorrect old value blocks indirect update' '
+ prefix=refs/incorrect-indirect-update &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+ EOF
+ printf "%s\n" "update $prefix/symref $E $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'existing old value blocks indirect create' '
+ prefix=refs/existing-indirect-create &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists
+ EOF
+ printf "%s\n" "create $prefix/symref $E" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'incorrect old value blocks indirect delete' '
+ prefix=refs/incorrect-indirect-delete &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+ EOF
+ printf "%s\n" "delete $prefix/symref $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'missing old value blocks indirect no-deref update' '
+ prefix=refs/missing-noderef-update &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: reference is missing but expected $D
+ EOF
+ printf "%s\n" "option no-deref" "update $prefix/symref $E $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'incorrect old value blocks indirect no-deref update' '
+ prefix=refs/incorrect-noderef-update &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+ EOF
+ printf "%s\n" "option no-deref" "update $prefix/symref $E $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'existing old value blocks indirect no-deref create' '
+ prefix=refs/existing-noderef-create &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists
+ EOF
+ printf "%s\n" "option no-deref" "create $prefix/symref $E" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'incorrect old value blocks indirect no-deref delete' '
+ prefix=refs/incorrect-noderef-delete &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ git update-ref $prefix/foo $C &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+ EOF
+ printf "%s\n" "option no-deref" "delete $prefix/symref $D" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'non-empty directory blocks create' '
+ prefix=refs/ne-create &&
+ mkdir -p .git/$prefix/foo/bar &&
+ : >.git/$prefix/foo/bar/baz.lock &&
+ test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q
+ EOF
+ printf "%s\n" "update $prefix/foo $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q
+ EOF
+ printf "%s\n" "update $prefix/foo $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'broken reference blocks create' '
+ prefix=refs/broken-create &&
+ mkdir -p .git/$prefix &&
+ echo "gobbledigook" >.git/$prefix/foo &&
+ test_when_finished "rm -f .git/$prefix/foo" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+ EOF
+ printf "%s\n" "update $prefix/foo $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+ EOF
+ printf "%s\n" "update $prefix/foo $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'non-empty directory blocks indirect create' '
+ prefix=refs/ne-indirect-create &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ mkdir -p .git/$prefix/foo/bar &&
+ : >.git/$prefix/foo/bar/baz.lock &&
+ test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q
+ EOF
+ printf "%s\n" "update $prefix/symref $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q
+ EOF
+ printf "%s\n" "update $prefix/symref $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'broken reference blocks indirect create' '
+ prefix=refs/broken-indirect-create &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ echo "gobbledigook" >.git/$prefix/foo &&
+ test_when_finished "rm -f .git/$prefix/foo" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+ EOF
+ printf "%s\n" "update $prefix/symref $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+ EOF
+ printf "%s\n" "update $prefix/symref $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_done
diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index 25ddab4e98..8937e25e49 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -285,7 +285,7 @@ test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '
echo precious >expect &&
test_must_fail git update-ref -d my-private-file >output 2>error &&
test_must_be_empty output &&
- test_i18ngrep -e "cannot lock .*: unable to resolve reference" error &&
+ test_i18ngrep -e "refusing to update ref with bad name" error &&
test_cmp expect .git/my-private-file
'
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 7ee8ea004f..8f52da2771 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -523,4 +523,26 @@ test_expect_success 'fsck --connectivity-only' '
)
'
+remove_loose_object () {
+ sha1="$(git rev-parse "$1")" &&
+ remainder=${sha1#??} &&
+ firsttwo=${sha1%$remainder} &&
+ rm .git/objects/$firsttwo/$remainder
+}
+
+test_expect_success 'fsck --name-objects' '
+ rm -rf name-objects &&
+ git init name-objects &&
+ (
+ cd name-objects &&
+ test_commit julius caesar.t &&
+ test_commit augustus &&
+ test_commit caesar &&
+ remove_loose_object $(git rev-parse julius:caesar.t) &&
+ test_must_fail git fsck --name-objects >out &&
+ tree=$(git rev-parse --verify julius:) &&
+ grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out
+ )
+'
+
test_done
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index 8aef49f236..292a0720fc 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -33,14 +33,14 @@ test_expect_success 'add one file' '
git update-index --add one &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<EOF &&
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
+100644 $EMPTY_BLOB 0 one
EOF
test_cmp ls-files.expect ls-files.actual &&
test-dump-split-index .git/index | sed "/^own/d" >actual &&
cat >expect <<EOF &&
base $base
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
+100644 $EMPTY_BLOB 0 one
replacements:
deletions:
EOF
@@ -51,7 +51,7 @@ test_expect_success 'disable split index' '
git update-index --no-split-index &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<EOF &&
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
+100644 $EMPTY_BLOB 0 one
EOF
test_cmp ls-files.expect ls-files.actual &&
@@ -67,7 +67,7 @@ test_expect_success 'enable split index again, "one" now belongs to base index"'
git update-index --split-index &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<EOF &&
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
+100644 $EMPTY_BLOB 0 one
EOF
test_cmp ls-files.expect ls-files.actual &&
@@ -105,7 +105,7 @@ test_expect_success 'add another file, which stays index' '
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<EOF &&
100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two
+100644 $EMPTY_BLOB 0 two
EOF
test_cmp ls-files.expect ls-files.actual &&
@@ -113,7 +113,7 @@ EOF
q_to_tab >expect <<EOF &&
$BASE
100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two
+100644 $EMPTY_BLOB 0 two
replacements: 0
deletions:
EOF
@@ -159,14 +159,14 @@ test_expect_success 'add original file back' '
git update-index --add one &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<EOF &&
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
+100644 $EMPTY_BLOB 0 one
EOF
test_cmp ls-files.expect ls-files.actual &&
test-dump-split-index .git/index | sed "/^own/d" >actual &&
cat >expect <<EOF &&
$BASE
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
+100644 $EMPTY_BLOB 0 one
replacements:
deletions: 0
EOF
@@ -178,8 +178,8 @@ test_expect_success 'add new file' '
git update-index --add two &&
git ls-files --stage >actual &&
cat >expect <<EOF &&
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two
+100644 $EMPTY_BLOB 0 one
+100644 $EMPTY_BLOB 0 two
EOF
test_cmp expect actual
'
@@ -188,8 +188,8 @@ test_expect_success 'unify index, two files remain' '
git update-index --no-split-index &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<EOF &&
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two
+100644 $EMPTY_BLOB 0 one
+100644 $EMPTY_BLOB 0 two
EOF
test_cmp ls-files.expect ls-files.actual &&
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
new file mode 100755
index 0000000000..8298aaf97f
--- /dev/null
+++ b/t/t2028-worktree-move.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+test_description='test git worktree move, remove, lock and unlock'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit init &&
+ git worktree add source &&
+ git worktree list --porcelain | grep "^worktree" >actual &&
+ cat <<-EOF >expected &&
+ worktree $(pwd)
+ worktree $(pwd)/source
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'lock main worktree' '
+ test_must_fail git worktree lock .
+'
+
+test_expect_success 'lock linked worktree' '
+ git worktree lock --reason hahaha source &&
+ echo hahaha >expected &&
+ test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock linked worktree from another worktree' '
+ rm .git/worktrees/source/locked &&
+ git worktree add elsewhere &&
+ git -C elsewhere worktree lock --reason hahaha ../source &&
+ echo hahaha >expected &&
+ test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock worktree twice' '
+ test_must_fail git worktree lock source &&
+ echo hahaha >expected &&
+ test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock worktree twice (from the locked worktree)' '
+ test_must_fail git -C source worktree lock . &&
+ echo hahaha >expected &&
+ test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock main worktree' '
+ test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+ git worktree unlock source &&
+ test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+ test_must_fail git worktree unlock source &&
+ test_path_is_missing .git/worktrees/source/locked
+'
+
+test_done
diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh
index 2a4a749b4f..8f22c43e24 100755
--- a/t/t2203-add-intent.sh
+++ b/t/t2203-add-intent.sh
@@ -82,5 +82,36 @@ test_expect_success 'cache-tree invalidates i-t-a paths' '
test_cmp expect actual
'
+test_expect_success 'cache-tree does not ignore dir that has i-t-a entries' '
+ git init ita-in-dir &&
+ (
+ cd ita-in-dir &&
+ mkdir 2 &&
+ for f in 1 2/1 2/2 3
+ do
+ echo "$f" >"$f"
+ done &&
+ git add 1 2/2 3 &&
+ git add -N 2/1 &&
+ git commit -m committed &&
+ git ls-tree -r HEAD >actual &&
+ grep 2/2 actual
+ )
+'
+
+test_expect_success 'cache-tree does skip dir that becomes empty' '
+ rm -fr ita-in-dir &&
+ git init ita-in-dir &&
+ (
+ cd ita-in-dir &&
+ mkdir -p 1/2/3 &&
+ echo 4 >1/2/3/4 &&
+ git add -N 1/2/3/4 &&
+ git write-tree >actual &&
+ echo $EMPTY_TREE >expected &&
+ test_cmp expected actual
+ )
+'
+
test_done
diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh
index 4d4b02e760..e804377f1c 100755
--- a/t/t3102-ls-tree-wildcards.sh
+++ b/t/t3102-ls-tree-wildcards.sh
@@ -12,16 +12,16 @@ test_expect_success 'setup' '
'
test_expect_success 'ls-tree a[a] matches literally' '
- cat >expect <<-\EOF &&
- 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three
+ cat >expect <<-EOF &&
+ 100644 blob $EMPTY_BLOB a[a]/three
EOF
git ls-tree -r HEAD "a[a]" >actual &&
test_cmp expect actual
'
test_expect_success 'ls-tree outside prefix' '
- cat >expect <<-\EOF &&
- 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ../a[a]/three
+ cat >expect <<-EOF &&
+ 100644 blob $EMPTY_BLOB ../a[a]/three
EOF
( cd aa && git ls-tree -r HEAD "../a[a]"; ) >actual &&
test_cmp expect actual
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index ac9c764799..8a833f354e 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -79,6 +79,15 @@ test_expect_success 'git branch -m dumps usage' '
test_i18ngrep "branch name required" err
'
+test_expect_success 'git branch -m m broken_symref should work' '
+ test_when_finished "git branch -D broken_symref" &&
+ git branch -l m &&
+ git symbolic-ref refs/heads/broken_symref refs/heads/i_am_broken &&
+ git branch -m m broken_symref &&
+ git reflog exists refs/heads/broken_symref &&
+ test_must_fail git reflog exists refs/heads/i_am_broken
+'
+
test_expect_success 'git branch -m m m/m should work' '
git branch -l m &&
git branch -m m m/m &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 3532c482fc..197914bbd8 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1281,4 +1281,12 @@ test_expect_success 'editor saves as CR/LF' '
)
'
+SQ="'"
+test_expect_success 'rebase -i --gpg-sign=<key-id>' '
+ set_fake_editor &&
+ FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
+ >out 2>err &&
+ grep "$SQ-S\"S I Gner\"$SQ" err
+'
+
test_done
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index 8f53e54ce4..48346f1cc0 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -271,4 +271,37 @@ test_expect_success 'autosquash with custom inst format' '
test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l)
'
+set_backup_editor () {
+ write_script backup-editor.sh <<-\EOF
+ cp "$1" .git/backup-"$(basename "$1")"
+ EOF
+ test_set_editor "$PWD/backup-editor.sh"
+}
+
+test_expect_failure 'autosquash with multiple empty patches' '
+ test_tick &&
+ git commit --allow-empty -m "empty" &&
+ test_tick &&
+ git commit --allow-empty -m "empty2" &&
+ test_tick &&
+ >fixup &&
+ git add fixup &&
+ git commit --fixup HEAD^^ &&
+ (
+ set_backup_editor &&
+ GIT_USE_REBASE_HELPER=false \
+ git rebase -i --force-rebase --autosquash HEAD~4 &&
+ grep empty2 .git/backup-git-rebase-todo
+ )
+'
+
+test_expect_success 'extra spaces after fixup!' '
+ base=$(git rev-parse HEAD) &&
+ test_commit to-fixup &&
+ git commit --allow-empty -m "fixup! to-fixup" &&
+ git rebase -i --autosquash --keep-empty HEAD~2 &&
+ parent=$(git rev-parse HEAD^) &&
+ test $base = $parent
+'
+
test_done
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
index 43c488b545..35b35a81c8 100755
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
@@ -78,8 +78,6 @@ test_expect_success 'diff-tree pathspec' '
test_cmp expected current
'
-EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-
test_expect_success 'diff-tree with wildcard shows dir also matches' '
git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result &&
echo file0 >expected &&
diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh
index 3c9932edf3..113304dc59 100755
--- a/t/t4033-diff-patience.sh
+++ b/t/t4033-diff-patience.sh
@@ -5,6 +5,14 @@ test_description='patience diff algorithm'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff-alternative.sh
+test_expect_success '--ignore-space-at-eol with a single appended character' '
+ printf "a\nb\nc\n" >pre &&
+ printf "a\nbX\nc\n" >post &&
+ test_must_fail git diff --no-index \
+ --patience --ignore-space-at-eol pre post >diff &&
+ grep "^+.*X" diff
+'
+
test_diff_frobnitz "patience"
test_diff_unique "patience"
diff --git a/t/t4054-diff-bogus-tree.sh b/t/t4054-diff-bogus-tree.sh
index 1d6efab3c5..18f42c5fff 100755
--- a/t/t4054-diff-bogus-tree.sh
+++ b/t/t4054-diff-bogus-tree.sh
@@ -3,8 +3,6 @@
test_description='test diff with a bogus tree containing the null sha1'
. ./test-lib.sh
-empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-
test_expect_success 'create bogus tree' '
bogus_tree=$(
printf "100644 fooQQQQQQQQQQQQQQQQQQQQQ" |
@@ -22,13 +20,13 @@ test_expect_success 'create tree with matching file' '
test_expect_success 'raw diff shows null sha1 (addition)' '
echo ":000000 100644 $_z40 $_z40 A foo" >expect &&
- git diff-tree $empty_tree $bogus_tree >actual &&
+ git diff-tree $EMPTY_TREE $bogus_tree >actual &&
test_cmp expect actual
'
test_expect_success 'raw diff shows null sha1 (removal)' '
echo ":100644 000000 $_z40 $_z40 D foo" >expect &&
- git diff-tree $bogus_tree $empty_tree >actual &&
+ git diff-tree $bogus_tree $EMPTY_TREE >actual &&
test_cmp expect actual
'
@@ -57,11 +55,11 @@ test_expect_success 'raw diff shows null sha1 (index)' '
'
test_expect_success 'patch fails due to bogus sha1 (addition)' '
- test_must_fail git diff-tree -p $empty_tree $bogus_tree
+ test_must_fail git diff-tree -p $EMPTY_TREE $bogus_tree
'
test_expect_success 'patch fails due to bogus sha1 (removal)' '
- test_must_fail git diff-tree -p $bogus_tree $empty_tree
+ test_must_fail git diff-tree -p $bogus_tree $EMPTY_TREE
'
test_expect_success 'patch fails due to bogus sha1 (modification)' '
diff --git a/t/t4130-apply-criss-cross-rename.sh b/t/t4130-apply-criss-cross-rename.sh
index d173acde0f..f8a313bcb9 100755
--- a/t/t4130-apply-criss-cross-rename.sh
+++ b/t/t4130-apply-criss-cross-rename.sh
@@ -13,9 +13,13 @@ create_file() {
}
test_expect_success 'setup' '
- create_file file1 "File1 contents" &&
- create_file file2 "File2 contents" &&
- create_file file3 "File3 contents" &&
+ # Ensure that file sizes are different, because on Windows
+ # lstat() does not discover inode numbers, and we need
+ # other properties to discover swapped files
+ # (mtime is not always different, either).
+ create_file file1 "some content" &&
+ create_file file2 "some other content" &&
+ create_file file3 "again something else" &&
git add file1 file2 file3 &&
git commit -m 1
'
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 803e1e6b8f..e2db47c36e 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -255,6 +255,20 @@ test_expect_success 'log -F -E --grep=<ere> uses ere' '
test_cmp expect actual
'
+test_expect_success 'log with grep.patternType configuration' '
+ >expect &&
+ git -c grep.patterntype=fixed \
+ log -1 --pretty=tformat:%s --grep=s.c.nd >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log with grep.patternType configuration and command line' '
+ echo second >expect &&
+ git -c grep.patterntype=fixed \
+ log -1 --pretty=tformat:%s --basic-regexp --grep=s.c.nd >actual &&
+ test_cmp expect actual
+'
+
cat > expect <<EOF
* Second
* sixth
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index d9f62425b0..f5435fd250 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -145,199 +145,199 @@ test_expect_success 'setup more commits' '
test_expect_success 'left alignment formatting' '
git log --pretty="tformat:%<(40)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-message two Z
-message one Z
-add bar Z
-$(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ message two Z
+ message one Z
+ add bar Z
+ $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(40)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-message two Z
-message one Z
-add bar Z
-$(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ message two Z
+ message one Z
+ add bar Z
+ $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting at the nth column' '
git log --pretty="tformat:%h %<|(40)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-$head1 message two Z
-$head2 message one Z
-$head3 add bar Z
-$head4 $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ $head1 message two Z
+ $head2 message one Z
+ $head3 add bar Z
+ $head4 $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting at the nth column' '
COLUMNS=50 git log --pretty="tformat:%h %<|(-10)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-$head1 message two Z
-$head2 message one Z
-$head3 add bar Z
-$head4 $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ $head1 message two Z
+ $head2 message one Z
+ $head3 add bar Z
+ $head4 $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting at the nth column. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%h %<|(40)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-$head1 message two Z
-$head2 message one Z
-$head3 add bar Z
-$head4 $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ $head1 message two Z
+ $head2 message one Z
+ $head3 add bar Z
+ $head4 $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with no padding' '
git log --pretty="tformat:%<(1)%s" >actual &&
- cat <<EOF >expected &&
-message two
-message one
-add bar
-$(commit_msg)
-EOF
+ cat <<-EOF >expected &&
+ message two
+ message one
+ add bar
+ $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with no padding. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(1)%s" >actual &&
- cat <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-message two
-message one
-add bar
-$(commit_msg)
-EOF
+ cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ message two
+ message one
+ add bar
+ $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with trunc' '
git log --pretty="tformat:%<(10,trunc)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-message ..
-message ..
-add bar Z
-initial...
-EOF
+ qz_to_tab_space <<-\EOF >expected &&
+ message ..
+ message ..
+ add bar Z
+ initial...
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with trunc. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,trunc)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-message ..
-message ..
-add bar Z
-initial...
-EOF
+ qz_to_tab_space <<-\EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ message ..
+ message ..
+ add bar Z
+ initial...
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with ltrunc' '
git log --pretty="tformat:%<(10,ltrunc)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-..sage two
-..sage one
-add bar Z
-..${sample_utf8_part}lich
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ ..sage two
+ ..sage one
+ add bar Z
+ ..${sample_utf8_part}lich
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with ltrunc. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,ltrunc)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-..sage two
-..sage one
-add bar Z
-..${sample_utf8_part}lich
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ ..sage two
+ ..sage one
+ add bar Z
+ ..${sample_utf8_part}lich
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with mtrunc' '
git log --pretty="tformat:%<(10,mtrunc)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-mess.. two
-mess.. one
-add bar Z
-init..lich
-EOF
+ qz_to_tab_space <<-\EOF >expected &&
+ mess.. two
+ mess.. one
+ add bar Z
+ init..lich
+ EOF
test_cmp expected actual
'
test_expect_success 'left alignment formatting with mtrunc. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,mtrunc)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-mess.. two
-mess.. one
-add bar Z
-init..lich
-EOF
+ qz_to_tab_space <<-\EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ mess.. two
+ mess.. one
+ add bar Z
+ init..lich
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting' '
git log --pretty="tformat:%>(40)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-Z message two
-Z message one
-Z add bar
-Z $(commit_msg)
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ Z message two
+ Z message one
+ Z add bar
+ Z $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%>(40)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-Z message two
-Z message one
-Z add bar
-Z $(commit_msg)
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ Z message two
+ Z message one
+ Z add bar
+ Z $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting at the nth column' '
git log --pretty="tformat:%h %>|(40)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-$head1 message two
-$head2 message one
-$head3 add bar
-$head4 $(commit_msg)
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ $head1 message two
+ $head2 message one
+ $head3 add bar
+ $head4 $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting at the nth column' '
COLUMNS=50 git log --pretty="tformat:%h %>|(-10)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-$head1 message two
-$head2 message one
-$head3 add bar
-$head4 $(commit_msg)
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ $head1 message two
+ $head2 message one
+ $head3 add bar
+ $head4 $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting at the nth column. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%h %>|(40)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-$head1 message two
-$head2 message one
-$head3 add bar
-$head4 $(commit_msg)
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ $head1 message two
+ $head2 message one
+ $head3 add bar
+ $head4 $(commit_msg)
+ EOF
test_cmp expected actual
'
@@ -345,110 +345,110 @@ EOF
# as in previous test.
test_expect_success 'right alignment formatting at the nth column with --graph. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --graph --pretty="tformat:%h %>|(40)%s" >actual &&
- iconv -f utf-8 -t $test_encoding >expected <<EOF&&
-* $head1 message two
-* $head2 message one
-* $head3 add bar
-* $head4 $(commit_msg)
-EOF
+ iconv -f utf-8 -t $test_encoding >expected <<-EOF &&
+ * $head1 message two
+ * $head2 message one
+ * $head3 add bar
+ * $head4 $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting with no padding' '
git log --pretty="tformat:%>(1)%s" >actual &&
- cat <<EOF >expected &&
-message two
-message one
-add bar
-$(commit_msg)
-EOF
+ cat <<-EOF >expected &&
+ message two
+ message one
+ add bar
+ $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting with no padding and with --graph' '
git log --graph --pretty="tformat:%>(1)%s" >actual &&
- cat <<EOF >expected &&
-* message two
-* message one
-* add bar
-* $(commit_msg)
-EOF
+ cat <<-EOF >expected &&
+ * message two
+ * message one
+ * add bar
+ * $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'right alignment formatting with no padding. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%>(1)%s" >actual &&
- cat <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-message two
-message one
-add bar
-$(commit_msg)
-EOF
+ cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ message two
+ message one
+ add bar
+ $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'center alignment formatting' '
git log --pretty="tformat:%><(40)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-Z message two Z
-Z message one Z
-Z add bar Z
-Z $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ Z message two Z
+ Z message one Z
+ Z add bar Z
+ Z $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'center alignment formatting. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%><(40)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-Z message two Z
-Z message one Z
-Z add bar Z
-Z $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ Z message two Z
+ Z message one Z
+ Z add bar Z
+ Z $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'center alignment formatting at the nth column' '
git log --pretty="tformat:%h %><|(40)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-$head1 message two Z
-$head2 message one Z
-$head3 add bar Z
-$head4 $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ $head1 message two Z
+ $head2 message one Z
+ $head3 add bar Z
+ $head4 $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'center alignment formatting at the nth column' '
COLUMNS=70 git log --pretty="tformat:%h %><|(-30)%s" >actual &&
- qz_to_tab_space <<EOF >expected &&
-$head1 message two Z
-$head2 message one Z
-$head3 add bar Z
-$head4 $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF >expected &&
+ $head1 message two Z
+ $head2 message one Z
+ $head3 add bar Z
+ $head4 $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'center alignment formatting at the nth column. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%h %><|(40)%s" >actual &&
- qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-$head1 message two Z
-$head2 message one Z
-$head3 add bar Z
-$head4 $(commit_msg) Z
-EOF
+ qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ $head1 message two Z
+ $head2 message one Z
+ $head3 add bar Z
+ $head4 $(commit_msg) Z
+ EOF
test_cmp expected actual
'
test_expect_success 'center alignment formatting with no padding' '
git log --pretty="tformat:%><(1)%s" >actual &&
- cat <<EOF >expected &&
-message two
-message one
-add bar
-$(commit_msg)
-EOF
+ cat <<-EOF >expected &&
+ message two
+ message one
+ add bar
+ $(commit_msg)
+ EOF
test_cmp expected actual
'
@@ -457,34 +457,34 @@ EOF
old_head1=$(git rev-parse --verify HEAD~0)
test_expect_success 'center alignment formatting with no padding. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%><(1)%s" >actual &&
- cat <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-message two
-message one
-add bar
-$(commit_msg)
-EOF
+ cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ message two
+ message one
+ add bar
+ $(commit_msg)
+ EOF
test_cmp expected actual
'
test_expect_success 'left/right alignment formatting with stealing' '
git commit --amend -m short --author "long long long <long@me.com>" &&
git log --pretty="tformat:%<(10,trunc)%s%>>(10,ltrunc)% an" >actual &&
- cat <<EOF >expected &&
-short long long long
-message .. A U Thor
-add bar A U Thor
-initial... A U Thor
-EOF
+ cat <<-\EOF >expected &&
+ short long long long
+ message .. A U Thor
+ add bar A U Thor
+ initial... A U Thor
+ EOF
test_cmp expected actual
'
test_expect_success 'left/right alignment formatting with stealing. i18n.logOutputEncoding' '
git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,trunc)%s%>>(10,ltrunc)% an" >actual &&
- cat <<EOF | iconv -f utf-8 -t $test_encoding >expected &&
-short long long long
-message .. A U Thor
-add bar A U Thor
-initial... A U Thor
-EOF
+ cat <<-\EOF | iconv -f utf-8 -t $test_encoding >expected &&
+ short long long long
+ message .. A U Thor
+ add bar A U Thor
+ initial... A U Thor
+ EOF
test_cmp expected actual
'
@@ -504,8 +504,10 @@ test_expect_success 'ISO and ISO-strict date formats display the same values' '
'
# get new digests (with no abbreviations)
-head1=$(git rev-parse --verify HEAD~0) &&
-head2=$(git rev-parse --verify HEAD~1) &&
+test_expect_success 'set up log decoration tests' '
+ head1=$(git rev-parse --verify HEAD~0) &&
+ head2=$(git rev-parse --verify HEAD~1)
+'
test_expect_success 'log decoration properly follows tag chain' '
git tag -a tag1 -m tag1 &&
@@ -513,22 +515,22 @@ test_expect_success 'log decoration properly follows tag chain' '
git tag -d tag1 &&
git commit --amend -m shorter &&
git log --no-walk --tags --pretty="%H %d" --decorate=full >actual &&
- cat <<EOF >expected &&
-$head1 (tag: refs/tags/tag2)
-$head2 (tag: refs/tags/message-one)
-$old_head1 (tag: refs/tags/message-two)
-EOF
+ cat <<-EOF >expected &&
+ $head1 (tag: refs/tags/tag2)
+ $head2 (tag: refs/tags/message-one)
+ $old_head1 (tag: refs/tags/message-two)
+ EOF
sort actual >actual1 &&
test_cmp expected actual1
'
test_expect_success 'clean log decoration' '
git log --no-walk --tags --pretty="%H %D" --decorate=full >actual &&
- cat >expected <<EOF &&
-$head1 tag: refs/tags/tag2
-$head2 tag: refs/tags/message-one
-$old_head1 tag: refs/tags/message-two
-EOF
+ cat >expected <<-EOF &&
+ $head1 tag: refs/tags/tag2
+ $head2 tag: refs/tags/message-one
+ $old_head1 tag: refs/tags/message-two
+ EOF
sort actual >actual1 &&
test_cmp expected actual1
'
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
index 44f3d5fb28..9b19cff729 100755
--- a/t/t5504-fetch-receive-strict.sh
+++ b/t/t5504-fetch-receive-strict.sh
@@ -115,8 +115,8 @@ test_expect_success 'push with transfer.fsckobjects' '
test_cmp exp act
'
-cat >bogus-commit <<\EOF
-tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+cat >bogus-commit <<EOF
+tree $EMPTY_TREE
author Bugs Bunny 1234567890 +0000
committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 6bd4853079..668c54be41 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -688,7 +688,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
-test_expect_success 'fetch aligned output' '
+test_expect_success C_LOCALE_OUTPUT 'fetch aligned output' '
git clone . full-output &&
test_commit looooooooooooong-tag &&
(
@@ -703,7 +703,7 @@ test_expect_success 'fetch aligned output' '
test_cmp expect actual
'
-test_expect_success 'fetch compact output' '
+test_expect_success C_LOCALE_OUTPUT 'fetch compact output' '
git clone . compact &&
test_commit extraaa &&
(
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index ca6becfe37..4840c71f02 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -368,5 +368,14 @@ test_expect_success GPG 'push with post-receive to inspect certificate' '
test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status"
'
+test_expect_success 'push status output scrubs password' '
+ cd "$ROOT_PATH/test_repo_clone" &&
+ git push --porcelain \
+ "$HTTPD_URL_USER_PASS/smart/test_repo.git" \
+ +HEAD:scrub >status &&
+ # should have been scrubbed down to vanilla URL
+ grep "^To $HTTPD_URL/smart/test_repo.git" status
+'
+
stop_httpd
test_done
diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh
new file mode 100755
index 0000000000..ea813b9383
--- /dev/null
+++ b/t/t5545-push-options.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+test_description='pushing to a repository using push options'
+
+. ./test-lib.sh
+
+mk_repo_pair () {
+ rm -rf workbench upstream &&
+ test_create_repo upstream &&
+ test_create_repo workbench &&
+ (
+ cd upstream &&
+ git config receive.denyCurrentBranch warn &&
+ mkdir -p .git/hooks &&
+ cat >.git/hooks/pre-receive <<-'EOF' &&
+ #!/bin/sh
+ if test -n "$GIT_PUSH_OPTION_COUNT"; then
+ i=0
+ >hooks/pre-receive.push_options
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ echo $value >>hooks/pre-receive.push_options
+ i=$((i + 1))
+ done
+ fi
+ EOF
+ chmod u+x .git/hooks/pre-receive
+
+ cat >.git/hooks/post-receive <<-'EOF' &&
+ #!/bin/sh
+ if test -n "$GIT_PUSH_OPTION_COUNT"; then
+ i=0
+ >hooks/post-receive.push_options
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ echo $value >>hooks/post-receive.push_options
+ i=$((i + 1))
+ done
+ fi
+ EOF
+ chmod u+x .git/hooks/post-receive
+ ) &&
+ (
+ cd workbench &&
+ git remote add up ../upstream
+ )
+}
+
+# Compare the ref ($1) in upstream with a ref value from workbench ($2)
+# i.e. test_refs second HEAD@{2}
+test_refs () {
+ test $# = 2 &&
+ git -C upstream rev-parse --verify "$1" >expect &&
+ git -C workbench rev-parse --verify "$2" >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'one push option works for a single branch' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git push --push-option=asdf up master
+ ) &&
+ test_refs master master &&
+ echo "asdf" >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 denied by remote' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions false &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ test_must_fail git push --push-option=asdf up master
+ ) &&
+ test_refs master HEAD@{1}
+'
+
+test_expect_success 'two push options work' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git push --push-option=asdf --push-option="more structured text" up master
+ ) &&
+ test_refs master master &&
+ printf "asdf\nmore structured text\n" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_done
diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh
index 85c10b0940..5e8d5fa50c 100755
--- a/t/t6038-merge-text-auto.sh
+++ b/t/t6038-merge-text-auto.sh
@@ -16,6 +16,13 @@ test_description='CRLF merge conflict across text=auto change
test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
+compare_files () {
+ tr '\015\000' QN <"$1" >"$1".expect &&
+ tr '\015\000' QN <"$2" >"$2".actual &&
+ test_cmp "$1".expect "$2".actual &&
+ rm "$1".expect "$2".actual
+}
+
test_expect_success setup '
git config core.autocrlf false &&
@@ -30,7 +37,7 @@ test_expect_success setup '
git branch side &&
echo "* text=auto" >.gitattributes &&
- touch file &&
+ echo first line >file &&
git add .gitattributes file &&
test_tick &&
git commit -m "normalize file" &&
@@ -81,38 +88,49 @@ test_expect_success 'Merge after setting text=auto' '
rm -f .gitattributes &&
git reset --hard a &&
git merge b &&
- test_cmp expected file
+ compare_files expected file
'
-test_expect_success 'Merge addition of text=auto' '
+test_expect_success 'Merge addition of text=auto eol=LF' '
+ git config core.eol lf &&
cat <<-\EOF >expected &&
first line
same line
EOF
- if test_have_prereq NATIVE_CRLF; then
- append_cr <expected >expected.temp &&
- mv expected.temp expected
- fi &&
git config merge.renormalize true &&
git rm -fr . &&
rm -f .gitattributes &&
git reset --hard b &&
git merge a &&
- test_cmp expected file
+ compare_files expected file
+'
+
+test_expect_success 'Merge addition of text=auto eol=CRLF' '
+ git config core.eol crlf &&
+ cat <<-\EOF >expected &&
+ first line
+ same line
+ EOF
+
+ append_cr <expected >expected.temp &&
+ mv expected.temp expected &&
+ git config merge.renormalize true &&
+ git rm -fr . &&
+ rm -f .gitattributes &&
+ git reset --hard b &&
+ echo >&2 "After git reset --hard b" &&
+ git ls-files -s --eol >&2 &&
+ git merge a &&
+ compare_files expected file
'
test_expect_success 'Detect CRLF/LF conflict after setting text=auto' '
+ git config core.eol native &&
echo "<<<<<<<" >expected &&
- if test_have_prereq NATIVE_CRLF; then
- echo first line | append_cr >>expected &&
- echo same line | append_cr >>expected &&
- echo ======= | append_cr >>expected
- else
- echo first line >>expected &&
- echo same line >>expected &&
- echo ======= >>expected
- fi &&
+ echo first line >>expected &&
+ echo same line >>expected &&
+ echo ======= >>expected &&
echo first line | append_cr >>expected &&
echo same line | append_cr >>expected &&
echo ">>>>>>>" >>expected &&
@@ -121,29 +139,23 @@ test_expect_success 'Detect CRLF/LF conflict after setting text=auto' '
git reset --hard a &&
test_must_fail git merge b &&
fuzz_conflict file >file.fuzzy &&
- test_cmp expected file.fuzzy
+ compare_files expected file.fuzzy
'
test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' '
echo "<<<<<<<" >expected &&
echo first line | append_cr >>expected &&
echo same line | append_cr >>expected &&
- if test_have_prereq NATIVE_CRLF; then
- echo ======= | append_cr >>expected &&
- echo first line | append_cr >>expected &&
- echo same line | append_cr >>expected
- else
- echo ======= >>expected &&
- echo first line >>expected &&
- echo same line >>expected
- fi &&
+ echo ======= >>expected &&
+ echo first line >>expected &&
+ echo same line >>expected &&
echo ">>>>>>>" >>expected &&
git config merge.renormalize false &&
rm -f .gitattributes &&
git reset --hard b &&
test_must_fail git merge a &&
fuzz_conflict file >file.fuzzy &&
- test_cmp expected file.fuzzy
+ compare_files expected file.fuzzy
'
test_expect_failure 'checkout -m after setting text=auto' '
@@ -158,7 +170,7 @@ test_expect_failure 'checkout -m after setting text=auto' '
git reset --hard initial &&
git checkout a -- . &&
git checkout -m b &&
- test_cmp expected file
+ compare_files expected file
'
test_expect_failure 'checkout -m addition of text=auto' '
@@ -173,7 +185,7 @@ test_expect_failure 'checkout -m addition of text=auto' '
git reset --hard initial &&
git checkout b -- . &&
git checkout -m a &&
- test_cmp expected file
+ compare_files expected file
'
test_expect_failure 'cherry-pick patch from after text=auto was added' '
@@ -187,7 +199,7 @@ test_expect_failure 'cherry-pick patch from after text=auto was added' '
git reset --hard b &&
test_must_fail git cherry-pick a >err 2>&1 &&
grep "[Nn]othing added" err &&
- test_cmp expected file
+ compare_files expected file
'
test_expect_success 'Test delete/normalize conflict' '
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
index 88d60c1ce2..84f41451ec 100755
--- a/t/t7011-skip-worktree-reading.sh
+++ b/t/t7011-skip-worktree-reading.sh
@@ -23,17 +23,15 @@ S sub/1
H sub/2
EOF
-NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-
setup_absent() {
test -f 1 && rm 1
git update-index --remove 1 &&
- git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 &&
git update-index --skip-worktree 1
}
test_absent() {
- echo "100644 $NULL_SHA1 0 1" > expected &&
+ echo "100644 $EMPTY_BLOB 0 1" > expected &&
git ls-files --stage 1 > result &&
test_cmp expected result &&
test ! -f 1
@@ -42,12 +40,12 @@ test_absent() {
setup_dirty() {
git update-index --force-remove 1 &&
echo dirty > 1 &&
- git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 &&
git update-index --skip-worktree 1
}
test_dirty() {
- echo "100644 $NULL_SHA1 0 1" > expected &&
+ echo "100644 $EMPTY_BLOB 0 1" > expected &&
git ls-files --stage 1 > result &&
test_cmp expected result &&
echo dirty > expected
@@ -120,7 +118,7 @@ test_expect_success 'grep with skip-worktree file' '
test "$(git grep --no-ext-grep test)" = "1:test"
'
-echo ":000000 100644 $_z40 $NULL_SHA1 A 1" > expected
+echo ":000000 100644 $_z40 $EMPTY_BLOB A 1" > expected
test_expect_success 'diff-index does not examine skip-worktree absent entries' '
setup_absent &&
git diff-index HEAD -- 1 > result &&
diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh
index 9ceaa4049f..9d1abe50ef 100755
--- a/t/t7012-skip-worktree-writing.sh
+++ b/t/t7012-skip-worktree-writing.sh
@@ -53,17 +53,15 @@ test_expect_success 'read-tree removes worktree, dirty case' '
git update-index --no-skip-worktree added
'
-NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-
setup_absent() {
test -f 1 && rm 1
git update-index --remove 1 &&
- git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 &&
git update-index --skip-worktree 1
}
test_absent() {
- echo "100644 $NULL_SHA1 0 1" > expected &&
+ echo "100644 $EMPTY_BLOB 0 1" > expected &&
git ls-files --stage 1 > result &&
test_cmp expected result &&
test ! -f 1
@@ -72,12 +70,12 @@ test_absent() {
setup_dirty() {
git update-index --force-remove 1 &&
echo dirty > 1 &&
- git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 &&
git update-index --skip-worktree 1
}
test_dirty() {
- echo "100644 $NULL_SHA1 0 1" > expected &&
+ echo "100644 $EMPTY_BLOB 0 1" > expected &&
git ls-files --stage 1 > result &&
test_cmp expected result &&
echo dirty > expected
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
index 44bf1d84af..4d17363a92 100755
--- a/t/t7060-wtstatus.sh
+++ b/t/t7060-wtstatus.sh
@@ -34,6 +34,7 @@ test_expect_success 'M/D conflict does not segfault' '
On branch side
You have unmerged paths.
(fix conflicts and run "git commit")
+ (use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add/rm <file>..." as appropriate to mark resolution)
@@ -138,6 +139,7 @@ test_expect_success 'status when conflicts with add and rm advice (deleted by th
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
+ (use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add/rm <file>..." as appropriate to mark resolution)
@@ -171,6 +173,7 @@ test_expect_success 'status when conflicts with add and rm advice (both deleted)
On branch conflict_second
You have unmerged paths.
(fix conflicts and run "git commit")
+ (use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add/rm <file>..." as appropriate to mark resolution)
@@ -195,6 +198,7 @@ test_expect_success 'status when conflicts with only rm advice (both deleted)' '
On branch conflict_second
You have unmerged paths.
(fix conflicts and run "git commit")
+ (use "git merge --abort" to abort the merge)
Changes to be committed:
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index 38b3890532..c23a21cb74 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -53,7 +53,7 @@ A two
EOF
cat >../dump.expect <<EOF &&
-info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+info/exclude $EMPTY_BLOB
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
@@ -137,7 +137,7 @@ EOF
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
-info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+info/exclude $EMPTY_BLOB
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
@@ -184,7 +184,7 @@ EOF
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
-info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+info/exclude $EMPTY_BLOB
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh
index fc97c3314e..400e2b1439 100755
--- a/t/t7411-submodule-config.sh
+++ b/t/t7411-submodule-config.sh
@@ -82,6 +82,17 @@ test_expect_success 'error in one submodule config lets continue' '
)
'
+test_expect_success 'error message contains blob reference' '
+ (cd super &&
+ sha1=$(git rev-parse HEAD) &&
+ test-submodule-config \
+ HEAD b \
+ HEAD submodule \
+ 2>actual_err &&
+ grep "submodule-blob $sha1:.gitmodules" actual_err >/dev/null
+ )
+'
+
cat >super/expect_url <<EOF
Submodule url: 'git@somewhere.else.net:a.git' for path 'b'
Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule'
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index b3bdd162aa..fb00e6d9b0 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -803,7 +803,7 @@ EOF
'
cat >expect <<EOF
-:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M dir1/modified
+:100644 100644 $EMPTY_BLOB 0000000000000000000000000000000000000000 M dir1/modified
EOF
test_expect_success 'status refreshes the index' '
touch dir2/added &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 49d19a3b36..5c3db656df 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -29,6 +29,7 @@ test_expect_success 'status when conflicts unresolved' '
On branch conflicts
You have unmerged paths.
(fix conflicts and run "git commit")
+ (use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 42a2929835..2974900578 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -412,6 +412,20 @@ run_dir_diff_test 'difftool --dir-diff from subdirectory' '
)
'
+run_dir_diff_test 'difftool --dir-diff from subdirectory with GIT_DIR set' '
+ (
+ GIT_DIR=$(pwd)/.git &&
+ export GIT_DIR &&
+ GIT_WORK_TREE=$(pwd) &&
+ export GIT_WORK_TREE &&
+ cd sub &&
+ git difftool --dir-diff $symlinks --extcmd ls \
+ branch -- sub >output &&
+ grep sub output &&
+ ! grep file output
+ )
+'
+
run_dir_diff_test 'difftool --dir-diff when worktree file is missing' '
test_when_finished git reset --hard &&
rm file2 &&
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
index a9b266f0d3..e48370dfa0 100755
--- a/t/t8003-blame-corner-cases.sh
+++ b/t/t8003-blame-corner-cases.sh
@@ -41,12 +41,12 @@ test_expect_success setup '
test_tick &&
GIT_AUTHOR_NAME=Fourth git commit -m Fourth &&
- {
- echo ABC
- echo DEF
- echo XXXX
- echo GHIJK
- } >cow &&
+ cat >cow <<-\EOF &&
+ ABC
+ DEF
+ XXXX
+ GHIJK
+ EOF
git add cow &&
test_tick &&
GIT_AUTHOR_NAME=Fifth git commit -m Fifth
@@ -115,11 +115,11 @@ test_expect_success 'append with -C -C -C' '
test_expect_success 'blame wholesale copy' '
git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current &&
- {
- echo mouse-Initial
- echo mouse-Second
- echo mouse-Third
- } >expected &&
+ cat >expected <<-\EOF &&
+ mouse-Initial
+ mouse-Second
+ mouse-Third
+ EOF
test_cmp expected current
'
@@ -127,16 +127,61 @@ test_expect_success 'blame wholesale copy' '
test_expect_success 'blame wholesale copy and more' '
git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current &&
- {
- echo mouse-Initial
- echo mouse-Second
- echo cow-Fifth
- echo mouse-Third
- } >expected &&
+ cat >expected <<-\EOF &&
+ mouse-Initial
+ mouse-Second
+ cow-Fifth
+ mouse-Third
+ EOF
test_cmp expected current
'
+test_expect_success 'blame wholesale copy and more in the index' '
+
+ cat >horse <<-\EOF &&
+ ABC
+ DEF
+ XXXX
+ YYYY
+ GHIJK
+ EOF
+ git add horse &&
+ test_when_finished "git rm -f horse" &&
+ git blame -f -C -C1 -- horse | sed -e "$pick_fc" >current &&
+ cat >expected <<-\EOF &&
+ mouse-Initial
+ mouse-Second
+ cow-Fifth
+ horse-Not
+ mouse-Third
+ EOF
+ test_cmp expected current
+
+'
+
+test_expect_success 'blame during cherry-pick with file rename conflict' '
+
+ test_when_finished "git reset --hard && git checkout master" &&
+ git checkout HEAD~3 &&
+ echo MOUSE >> mouse &&
+ git mv mouse rodent &&
+ git add rodent &&
+ GIT_AUTHOR_NAME=Rodent git commit -m "rodent" &&
+ git checkout --detach master &&
+ (git cherry-pick HEAD@{1} || test $? -eq 1) &&
+ git show HEAD@{1}:rodent > rodent &&
+ git add rodent &&
+ git blame -f -C -C1 rodent | sed -e "$pick_fc" >current &&
+ cat current &&
+ cat >expected <<-\EOF &&
+ mouse-Initial
+ mouse-Second
+ rodent-Not
+ EOF
+ test_cmp expected current
+'
+
test_expect_success 'blame path that used to be a directory' '
mkdir path &&
echo A A A A A >path/file &&
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index 28082b134f..92a3aa8063 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -8,8 +8,6 @@ GIT_SVN_LC_ALL=${LC_ALL:-$LANG}
. ./lib-git-svn.sh
-say 'define NO_SVN_TESTS to skip git svn tests'
-
case "$GIT_SVN_LC_ALL" in
*.UTF-8)
test_set_prereq UTF8
@@ -19,6 +17,27 @@ case "$GIT_SVN_LC_ALL" in
;;
esac
+deepdir=nothing-above
+ceiling=$PWD
+
+test_expect_success 'git svn --version works anywhere' '
+ mkdir -p "$deepdir" && (
+ GIT_CEILING_DIRECTORIES="$ceiling" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd "$deepdir" &&
+ git svn --version
+ )
+'
+
+test_expect_success 'git svn help works anywhere' '
+ mkdir -p "$deepdir" && (
+ GIT_CEILING_DIRECTORIES="$ceiling" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd "$deepdir" &&
+ git svn help
+ )
+'
+
test_expect_success \
'initialize git svn' '
mkdir import &&
diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh
index a87d3d3fc1..64bb495834 100755
--- a/t/t9115-git-svn-dcommit-funky-renames.sh
+++ b/t/t9115-git-svn-dcommit-funky-renames.sh
@@ -8,9 +8,10 @@ test_description='git svn dcommit can commit renames of files with ugly names'
. ./lib-git-svn.sh
test_expect_success 'load repository with strange names' '
- svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9115/funky-names.dump &&
- start_httpd gtk+
- '
+ svnadmin load -q "$rawsvnrepo" <"$TEST_DIRECTORY"/t9115/funky-names.dump
+'
+
+maybe_start_httpd gtk+
test_expect_success 'init and fetch repository' '
git svn init "$svnrepo" &&
diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh
index ecb1fed147..41a026637f 100755
--- a/t/t9118-git-svn-funky-branch-names.sh
+++ b/t/t9118-git-svn-funky-branch-names.sh
@@ -32,7 +32,7 @@ test_expect_success 'setup svnrepo' '
"$svnrepo/pr ject/branches/trailing_dotlock.lock" &&
svn_cmd cp -m "reflog" "$svnrepo/pr ject/trunk" \
"$svnrepo/pr ject/branches/not-a@{0}reflog@" &&
- start_httpd
+ maybe_start_httpd
'
# SVN 1.7 will truncate "not-a%40{0]" to just "not-a".
diff --git a/t/t9120-git-svn-clone-with-percent-escapes.sh b/t/t9120-git-svn-clone-with-percent-escapes.sh
index 59465b147e..b28a1741e3 100755
--- a/t/t9120-git-svn-clone-with-percent-escapes.sh
+++ b/t/t9120-git-svn-clone-with-percent-escapes.sh
@@ -15,7 +15,7 @@ test_expect_success 'setup svnrepo' '
svn_cmd cp -m "tag" "$svnrepo/pr ject/trunk" \
"$svnrepo/pr ject/tags/v1" &&
rm -rf project &&
- start_httpd
+ maybe_start_httpd
'
test_expect_success 'test clone with percent escapes' '
diff --git a/t/t9142-git-svn-shallow-clone.sh b/t/t9142-git-svn-shallow-clone.sh
index e21ee5f663..9ee23be640 100755
--- a/t/t9142-git-svn-shallow-clone.sh
+++ b/t/t9142-git-svn-shallow-clone.sh
@@ -18,7 +18,7 @@ test_expect_success 'setup test repository' '
svn_cmd add foo &&
svn_cmd commit -m "add foo"
) &&
- start_httpd
+ maybe_start_httpd
'
test_expect_success 'clone trunk with "-r HEAD"' '
diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh
index 13f78f2682..a875b45102 100755
--- a/t/t9158-git-svn-mergeinfo.sh
+++ b/t/t9158-git-svn-mergeinfo.sh
@@ -7,8 +7,6 @@ test_description='git svn mergeinfo propagation'
. ./lib-git-svn.sh
-say 'define NO_SVN_TESTS to skip git svn tests'
-
test_expect_success 'initialize source svn repo' '
svn_cmd mkdir -m x "$svnrepo"/trunk &&
svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh
index b4a4434604..0ede3cfedb 100755
--- a/t/t9160-git-svn-preserve-empty-dirs.sh
+++ b/t/t9160-git-svn-preserve-empty-dirs.sh
@@ -11,7 +11,6 @@ local Git repository with placeholder files.'
. ./lib-git-svn.sh
-say 'define NO_SVN_TESTS to skip git svn tests'
GIT_REPO=git-svn-repo
test_expect_success 'initialize source svn repo containing empty dirs' '
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 11201e9cf8..d731d66e36 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -162,6 +162,9 @@ _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
# Zero SHA-1
_z40=0000000000000000000000000000000000000000
+EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+
# Line feed
LF='
'
@@ -170,7 +173,7 @@ LF='
# when case-folding filenames
u200c=$(printf '\342\200\214')
-export _x05 _x40 _z40 LF u200c
+export _x05 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB
# Each test should start with something like this, after copyright notices:
#
@@ -798,7 +801,7 @@ then
# override all git executables in TEST_DIRECTORY/..
GIT_VALGRIND=$TEST_DIRECTORY/valgrind
mkdir -p "$GIT_VALGRIND"/bin
- for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
+ for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/t/helper/test-*
do
make_valgrind_symlink $file
done
diff --git a/templates/hooks--pre-receive.sample b/templates/hooks--pre-receive.sample
new file mode 100644
index 0000000000..a1fd29ec14
--- /dev/null
+++ b/templates/hooks--pre-receive.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to make use of push options.
+# The example simply echoes all push options that start with 'echoback='
+# and rejects all pushes when the "reject" push option is used.
+#
+# To enable this hook, rename this file to "pre-receive".
+
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
+ i=0
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+ do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ case "$value" in
+ echoback=*)
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
+ ;;
+ reject)
+ exit 1
+ esac
+ i=$((i + 1))
+ done
+fi
diff --git a/transport.c b/transport.c
index 59b911e5ff..4ba48b0596 100644
--- a/transport.c
+++ b/transport.c
@@ -359,8 +359,11 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain)
{
- if (!count)
- fprintf(porcelain ? stdout : stderr, "To %s\n", dest);
+ if (!count) {
+ char *url = transport_anonymize_url(dest);
+ fprintf(porcelain ? stdout : stderr, "To %s\n", url);
+ free(url);
+ }
switch(ref->status) {
case REF_STATUS_NONE:
@@ -510,6 +513,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
+ args.push_options = transport->push_options;
args.url = transport->url;
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
diff --git a/transport.h b/transport.h
index c68140892c..6fe3485325 100644
--- a/transport.h
+++ b/transport.h
@@ -48,6 +48,12 @@ struct transport {
*/
unsigned cloning : 1;
+ /*
+ * These strings will be passed to the {pre, post}-receive hook,
+ * on the remote side, if both sides support the push options capability.
+ */
+ 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
@@ -134,6 +140,7 @@ struct transport {
#define TRANSPORT_PUSH_CERT_ALWAYS 2048
#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
#define TRANSPORT_PUSH_ATOMIC 8192
+#define TRANSPORT_PUSH_OPTIONS 16384
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
diff --git a/worktree.c b/worktree.c
index e2a94e0476..5acfe4cd64 100644
--- a/worktree.c
+++ b/worktree.c
@@ -13,6 +13,7 @@ void free_worktrees(struct worktree **worktrees)
free(worktrees[i]->path);
free(worktrees[i]->id);
free(worktrees[i]->head_ref);
+ free(worktrees[i]->lock_reason);
free(worktrees[i]);
}
free (worktrees);
@@ -80,7 +81,7 @@ static struct worktree *get_main_worktree(void)
int is_bare = 0;
int is_detached = 0;
- strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
+ strbuf_add_absolute_path(&worktree_path, get_git_common_dir());
is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
if (is_bare)
strbuf_strip_suffix(&worktree_path, "/.");
@@ -98,6 +99,8 @@ static struct worktree *get_main_worktree(void)
worktree->is_detached = is_detached;
worktree->is_current = 0;
add_head_info(&head_ref, worktree);
+ worktree->lock_reason = NULL;
+ worktree->lock_reason_valid = 0;
done:
strbuf_release(&path);
@@ -125,7 +128,7 @@ static struct worktree *get_linked_worktree(const char *id)
strbuf_rtrim(&worktree_path);
if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
strbuf_reset(&worktree_path);
- strbuf_addstr(&worktree_path, absolute_path("."));
+ strbuf_add_absolute_path(&worktree_path, ".");
strbuf_strip_suffix(&worktree_path, "/.");
}
@@ -143,6 +146,8 @@ static struct worktree *get_linked_worktree(const char *id)
worktree->is_detached = is_detached;
worktree->is_current = 0;
add_head_info(&head_ref, worktree);
+ worktree->lock_reason = NULL;
+ worktree->lock_reason_valid = 0;
done:
strbuf_release(&path);
@@ -214,6 +219,78 @@ const char *get_worktree_git_dir(const struct worktree *wt)
return git_common_path("worktrees/%s", wt->id);
}
+static struct worktree *find_worktree_by_suffix(struct worktree **list,
+ const char *suffix)
+{
+ struct worktree *found = NULL;
+ int nr_found = 0, suffixlen;
+
+ suffixlen = strlen(suffix);
+ if (!suffixlen)
+ return NULL;
+
+ for (; *list && nr_found < 2; list++) {
+ const char *path = (*list)->path;
+ int pathlen = strlen(path);
+ int start = pathlen - suffixlen;
+
+ /* suffix must start at directory boundary */
+ if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
+ !fspathcmp(suffix, path + start)) {
+ found = *list;
+ nr_found++;
+ }
+ }
+ return nr_found == 1 ? found : NULL;
+}
+
+struct worktree *find_worktree(struct worktree **list,
+ const char *prefix,
+ const char *arg)
+{
+ struct worktree *wt;
+ char *path;
+
+ if ((wt = find_worktree_by_suffix(list, arg)))
+ return wt;
+
+ arg = prefix_filename(prefix, strlen(prefix), arg);
+ path = xstrdup(real_path(arg));
+ for (; *list; list++)
+ if (!fspathcmp(path, real_path((*list)->path)))
+ break;
+ free(path);
+ return *list;
+}
+
+int is_main_worktree(const struct worktree *wt)
+{
+ return !wt->id;
+}
+
+const char *is_worktree_locked(struct worktree *wt)
+{
+ assert(!is_main_worktree(wt));
+
+ if (!wt->lock_reason_valid) {
+ struct strbuf path = STRBUF_INIT;
+
+ strbuf_addstr(&path, worktree_git_path(wt, "locked"));
+ if (file_exists(path.buf)) {
+ struct strbuf lock_reason = STRBUF_INIT;
+ if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
+ die_errno(_("failed to read '%s'"), path.buf);
+ strbuf_trim(&lock_reason);
+ wt->lock_reason = strbuf_detach(&lock_reason, NULL);
+ } else
+ wt->lock_reason = NULL;
+ wt->lock_reason_valid = 1;
+ strbuf_release(&path);
+ }
+
+ return wt->lock_reason;
+}
+
int is_worktree_being_rebased(const struct worktree *wt,
const char *target)
{
diff --git a/worktree.h b/worktree.h
index 13949093cc..90e1311fa7 100644
--- a/worktree.h
+++ b/worktree.h
@@ -5,10 +5,12 @@ struct worktree {
char *path;
char *id;
char *head_ref;
+ char *lock_reason; /* internal use */
unsigned char head_sha1[20];
int is_detached;
int is_bare;
int is_current;
+ int lock_reason_valid;
};
/* Functions for acting on the information about worktrees. */
@@ -30,6 +32,25 @@ extern struct worktree **get_worktrees(void);
extern const char *get_worktree_git_dir(const struct worktree *wt);
/*
+ * Search a worktree that can be unambiguously identified by
+ * "arg". "prefix" must not be NULL.
+ */
+extern struct worktree *find_worktree(struct worktree **list,
+ const char *prefix,
+ const char *arg);
+
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
+/*
+ * Return the reason string if the given worktree is locked or NULL
+ * otherwise.
+ */
+extern const char *is_worktree_locked(struct worktree *wt);
+
+/*
* Free up the memory for worktree(s)
*/
extern void free_worktrees(struct worktree **);
diff --git a/wt-status.c b/wt-status.c
index de62ab2149..3175ec6ddb 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -948,9 +948,12 @@ static void show_merge_in_progress(struct wt_status *s,
{
if (has_unmerged(s)) {
status_printf_ln(s, color, _("You have unmerged paths."));
- if (s->hints)
+ if (s->hints) {
status_printf_ln(s, color,
- _(" (fix conflicts and run \"git commit\")"));
+ _(" (fix conflicts and run \"git commit\")"));
+ status_printf_ln(s, color,
+ _(" (use \"git merge --abort\" to abort the merge)"));
+ }
} else {
s-> commitable = 1;
status_printf_ln(s, color,
@@ -1063,7 +1066,7 @@ static void abbrev_sha1_in_line(struct strbuf *line)
strbuf_addf(split[1], "%s ", abbrev);
strbuf_reset(line);
for (i = 0; split[i]; i++)
- strbuf_addf(line, "%s", split[i]->buf);
+ strbuf_addbuf(line, split[i]);
}
}
strbuf_list_free(split);
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index 04e1a1ab2a..a613efc703 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -1,6 +1,6 @@
/*
* LibXDiff by Davide Libenzi ( File Differential Library )
- * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin
+ * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 62cb23dfd3..027192a1c7 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -200,8 +200,10 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
return 0;
}
} else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
- while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++])
- ; /* keep going */
+ while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
+ i1++;
+ i2++;
+ }
}
/*