summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.3.8.txt22
-rw-r--r--Documentation/RelNotes/2.4.1.txt40
-rw-r--r--Documentation/RelNotes/2.5.0.txt247
-rw-r--r--Documentation/blame-options.txt2
-rw-r--r--Documentation/config.txt38
-rw-r--r--Documentation/diff-generate-patch.txt2
-rw-r--r--Documentation/diff-options.txt8
-rw-r--r--Documentation/git-add.txt3
-rw-r--r--Documentation/git-cat-file.txt5
-rw-r--r--Documentation/git-checkout.txt78
-rw-r--r--Documentation/git-commit.txt11
-rw-r--r--Documentation/git-credential-store.txt35
-rw-r--r--Documentation/git-fast-export.txt6
-rw-r--r--Documentation/git-fast-import.txt30
-rw-r--r--Documentation/git-fetch-pack.txt2
-rw-r--r--Documentation/git-hash-object.txt10
-rw-r--r--Documentation/git-http-backend.txt4
-rw-r--r--Documentation/git-log.txt4
-rw-r--r--Documentation/git-merge.txt4
-rw-r--r--Documentation/git-p4.txt17
-rw-r--r--Documentation/git-pack-objects.txt2
-rw-r--r--Documentation/git-prune.txt3
-rw-r--r--Documentation/git-push.txt4
-rw-r--r--Documentation/git-rebase.txt2
-rw-r--r--Documentation/git-rev-list.txt96
-rw-r--r--Documentation/git-rev-parse.txt14
-rw-r--r--Documentation/git-send-pack.txt2
-rw-r--r--Documentation/git-show.txt2
-rw-r--r--Documentation/git-stripspace.txt6
-rw-r--r--Documentation/git-svn.txt10
-rw-r--r--Documentation/git-tag.txt2
-rw-r--r--Documentation/git.txt15
-rw-r--r--Documentation/gitcore-tutorial.txt2
-rw-r--r--Documentation/gitdiffcore.txt6
-rw-r--r--Documentation/gitk.txt4
-rw-r--r--Documentation/gitremote-helpers.txt14
-rw-r--r--Documentation/gitrepository-layout.txt74
-rw-r--r--Documentation/line-range-format.txt11
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--Makefile7
l---------RelNotes2
-rw-r--r--archive-zip.c4
-rw-r--r--archive.c22
-rw-r--r--attr.c16
-rw-r--r--bisect.c40
-rw-r--r--builtin/add.c4
-rw-r--r--builtin/apply.c14
-rw-r--r--builtin/blame.c1
-rw-r--r--builtin/branch.c10
-rw-r--r--builtin/bundle.c8
-rw-r--r--builtin/cat-file.c51
-rw-r--r--builtin/checkout-index.c10
-rw-r--r--builtin/checkout.c280
-rw-r--r--builtin/clone.c9
-rw-r--r--builtin/commit.c10
-rw-r--r--builtin/config.c14
-rw-r--r--builtin/count-objects.c4
-rw-r--r--builtin/fetch.c5
-rw-r--r--builtin/fmt-merge-msg.c16
-rw-r--r--builtin/fsck.c4
-rw-r--r--builtin/gc.c34
-rw-r--r--builtin/hash-object.c4
-rw-r--r--builtin/index-pack.c290
-rw-r--r--builtin/init-db.c25
-rw-r--r--builtin/merge.c248
-rw-r--r--builtin/pack-objects.c4
-rw-r--r--builtin/patch-id.c34
-rw-r--r--builtin/prune.c99
-rw-r--r--builtin/receive-pack.c2
-rw-r--r--builtin/remote.c2
-rw-r--r--builtin/repack.c8
-rw-r--r--builtin/rev-parse.c11
-rw-r--r--builtin/show-branch.c6
-rw-r--r--builtin/update-index.c13
-rw-r--r--bulk-checkin.c12
-rw-r--r--cache.h77
-rw-r--r--combine-diff.c56
-rw-r--r--command-list.txt10
-rw-r--r--commit.c56
-rw-r--r--commit.h4
-rw-r--r--compat/mingw.h8
-rw-r--r--config.c16
-rw-r--r--config.mak.uname1
-rw-r--r--connect.c54
-rw-r--r--contrib/completion/git-completion.bash46
-rwxr-xr-xcontrib/subtree/git-subtree.sh15
-rw-r--r--contrib/subtree/git-subtree.txt2
-rw-r--r--credential-store.c90
-rw-r--r--daemon.c13
-rw-r--r--date.c14
-rw-r--r--diff-lib.c22
-rw-r--r--diff-no-index.c66
-rw-r--r--diff.h5
-rw-r--r--dir.c21
-rw-r--r--environment.c34
-rw-r--r--exec_cmd.c1
-rw-r--r--fast-import.c7
-rwxr-xr-xgit-am.sh22
-rw-r--r--git-compat-util.h60
-rwxr-xr-xgit-cvsimport.perl2
-rwxr-xr-xgit-filter-branch.sh10
-rwxr-xr-xgit-p4.py67
-rwxr-xr-xgit-pull.sh5
-rw-r--r--git-rebase--interactive.sh22
-rw-r--r--git-rebase--merge.sh6
-rwxr-xr-xgit-rebase.sh6
-rw-r--r--git-sh-setup.sh2
-rwxr-xr-xgit-stash.sh8
-rw-r--r--git.c2
-rw-r--r--hex.c16
-rw-r--r--http.c10
-rw-r--r--line-log.c3
-rw-r--r--lockfile.c79
-rw-r--r--lockfile.h16
-rw-r--r--log-tree.c33
-rw-r--r--notes-merge.c6
-rw-r--r--object.c3
-rw-r--r--pack-bitmap.c2
-rw-r--r--path.c277
-rw-r--r--progress.c22
-rw-r--r--reachable.c6
-rw-r--r--refs.c514
-rw-r--r--refs.h2
-rw-r--r--revision.c23
-rw-r--r--run-command.c4
-rw-r--r--run-command.h2
-rw-r--r--send-pack.c2
-rw-r--r--setup.c128
-rw-r--r--sha1_file.c180
-rw-r--r--shallow.c8
-rw-r--r--strbuf.c49
-rw-r--r--strbuf.h3
-rw-r--r--submodule.c9
-rw-r--r--t/lib-git-p4.sh2
-rw-r--r--t/lib-httpd.sh12
-rwxr-xr-xt/t0008-ignores.sh10
-rwxr-xr-xt/t0027-auto-crlf.sh243
-rwxr-xr-xt/t0060-path-utils.sh36
-rwxr-xr-xt/t0302-credential-store.sh114
-rwxr-xr-xt/t1006-cat-file.sh45
-rwxr-xr-xt/t1007-hash-object.sh19
-rwxr-xr-xt/t1400-update-ref.sh42
-rwxr-xr-xt/t1404-update-ref-df-conflicts.sh107
-rwxr-xr-xt/t1430-bad-ref-name.sh8
-rwxr-xr-xt/t1501-worktree.sh77
-rwxr-xr-xt/t1509/prepare-chroot.sh38
-rwxr-xr-xt/t1510-repo-setup.sh1
-rwxr-xr-xt/t2025-checkout-to.sh129
-rwxr-xr-xt/t2026-prune-linked-checkouts.sh96
-rwxr-xr-xt/t2203-add-intent.sh23
-rwxr-xr-xt/t3033-merge-toplevel.sh136
-rwxr-xr-xt/t3210-pack-refs.sh17
-rwxr-xr-xt/t3402-rebase-merge.sh2
-rwxr-xr-xt/t3404-rebase-interactive.sh47
-rwxr-xr-xt/t3701-add-interactive.sh27
-rwxr-xr-xt/t3702-add-edit.sh7
-rwxr-xr-xt/t3903-stash.sh21
-rwxr-xr-xt/t3904-stash-patch.sh43
-rwxr-xr-xt/t4011-diff-symlink.sh10
-rw-r--r--t/t4013/diff.log_--decorate=full_--all2
-rwxr-xr-xt/t4053-diff-no-index.sh34
-rwxr-xr-xt/t4211-line-log.sh8
-rwxr-xr-xt/t5520-pull.sh31
-rwxr-xr-xt/t5539-fetch-http-shallow.sh6
-rwxr-xr-xt/t5541-http-push-smart.sh5
-rwxr-xr-xt/t5542-push-http-shallow.sh6
-rwxr-xr-xt/t5550-http-fetch-dumb.sh6
-rwxr-xr-xt/t5551-http-fetch-smart.sh6
-rwxr-xr-xt/t5561-http-backend.sh6
-rwxr-xr-xt/t5601-clone.sh35
-rwxr-xr-xt/t6020-merge-df.sh2
-rwxr-xr-xt/t6021-merge-criss-cross.sh6
-rwxr-xr-xt/t7003-filter-branch.sh10
-rwxr-xr-xt/t7004-tag.sh4
-rwxr-xr-xt/t7061-wtstatus-ignore.sh9
-rwxr-xr-xt/t7410-submodule-checkout-to.sh50
-rwxr-xr-xt/t7502-commit.sh2
-rwxr-xr-xt/t8003-blame-corner-cases.sh18
-rwxr-xr-xt/t9402-git-cvsserver-refs.sh2
-rwxr-xr-xt/t9801-git-p4-branch.sh106
-rwxr-xr-xt/t9814-git-p4-rename.sh11
-rwxr-xr-xt/t9816-git-p4-locked.sh10
-rwxr-xr-xt/t9818-git-p4-block.sh64
-rwxr-xr-xt/t9819-git-p4-case-folding.sh54
-rwxr-xr-xt/t9902-completion.sh34
-rw-r--r--t/test-lib-functions.sh13
-rw-r--r--t/test-lib.sh2
-rwxr-xr-xtemplates/hooks--applypatch-msg.sample4
-rwxr-xr-xtemplates/hooks--pre-applypatch.sample4
-rw-r--r--trace.c1
-rw-r--r--transport.c8
-rw-r--r--tree-diff.c10
-rw-r--r--upload-pack.c2
-rw-r--r--utf8.c11
-rw-r--r--utf8.h3
-rw-r--r--wrapper.c31
196 files changed, 4893 insertions, 1424 deletions
diff --git a/Documentation/RelNotes/2.3.8.txt b/Documentation/RelNotes/2.3.8.txt
new file mode 100644
index 0000000..0b67268
--- /dev/null
+++ b/Documentation/RelNotes/2.3.8.txt
@@ -0,0 +1,22 @@
+Git v2.3.8 Release Notes
+========================
+
+Fixes since v2.3.7
+------------------
+
+ * The usual "git diff" when seeing a file turning into a directory
+ showed a patchset to remove the file and create all files in the
+ directory, but "git diff --no-index" simply refused to work. Also,
+ when asked to compare a file and a directory, imitate POSIX "diff"
+ and compare the file with the file with the same name in the
+ directory, instead of refusing to run.
+
+ * The default $HOME/.gitconfig file created upon "git config --global"
+ that edits it had incorrectly spelled user.name and user.email
+ entries in it.
+
+ * "git commit --date=now" or anything that relies on approxidate lost
+ the daylight-saving-time offset.
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
diff --git a/Documentation/RelNotes/2.4.1.txt b/Documentation/RelNotes/2.4.1.txt
new file mode 100644
index 0000000..a65a6c5
--- /dev/null
+++ b/Documentation/RelNotes/2.4.1.txt
@@ -0,0 +1,40 @@
+Git v2.4.1 Release Notes
+========================
+
+Fixes since v2.4
+----------------
+
+ * The usual "git diff" when seeing a file turning into a directory
+ showed a patchset to remove the file and create all files in the
+ directory, but "git diff --no-index" simply refused to work. Also,
+ when asked to compare a file and a directory, imitate POSIX "diff"
+ and compare the file with the file with the same name in the
+ directory, instead of refusing to run.
+
+ * The default $HOME/.gitconfig file created upon "git config --global"
+ that edits it had incorrectly spelled user.name and user.email
+ entries in it.
+
+ * "git commit --date=now" or anything that relies on approxidate lost
+ the daylight-saving-time offset.
+
+ * "git cat-file bl $blob" failed to barf even though there is no
+ object type that is "bl".
+
+ * Teach the codepaths that read .gitignore and .gitattributes files
+ that these files encoded in UTF-8 may have UTF-8 BOM marker at the
+ beginning; this makes it in line with what we do for configuration
+ files already.
+
+ * Access to objects in repositories that borrow from another one on a
+ slow NFS server unnecessarily got more expensive due to recent code
+ becoming more cautious in a naive way not to lose objects to pruning.
+
+ * We avoid setting core.worktree when the repository location is the
+ ".git" directory directly at the top level of the working tree, but
+ the code misdetected the case in which the working tree is at the
+ root level of the filesystem (which arguably is a silly thing to
+ do, but still valid).
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
diff --git a/Documentation/RelNotes/2.5.0.txt b/Documentation/RelNotes/2.5.0.txt
new file mode 100644
index 0000000..fb7aa97
--- /dev/null
+++ b/Documentation/RelNotes/2.5.0.txt
@@ -0,0 +1,247 @@
+Git 2.5 Release Notes
+=====================
+
+Updates since v2.4
+------------------
+
+Ports
+
+
+UI, Workflows & Features
+
+ * "git p4" now detects the filetype (e.g. binary) correctly even when
+ the files are opened exclusively.
+
+ * git p4 attempts to better handle branches in Perforce.
+
+ * "git p4" learned "--changes-block-size <n>" to read the changes in
+ chunks from Perforce, instead of making one call to "p4 changes"
+ that may trigger "too many rows scanned" error from Perforce.
+
+ * "git show-branch --topics HEAD" (with no other arguments) did not
+ do anything interesting. Instead, contrast the given revision
+ against all the local branches by default.
+
+ * A replacement for contrib/workdir/git-new-workdir that does not
+ rely on symbolic links and make sharing of objects and refs safer
+ by making the borrowee and borrowers aware of each other.
+
+ * Tweak the sample "store" backend of the credential helper to honor
+ XDG configuration file locations when specified.
+
+ * A heuristic to help the "git <cmd> <revs> <pathspec>" command line
+ convention to catch mistyped paths is to make sure all the non-rev
+ parameters in the later part of the command line are names of the
+ files in the working tree, but that means "git grep $str -- \*.c"
+ must always be disambiguated with "--", because nobody sane will
+ create a file whose name literally is asterisk-dot-see. Loosen the
+ heuristic to declare that with a wildcard string the user likely
+ meant to give us a pathspec.
+
+ * "git merge FETCH_HEAD" learned that the previous "git fetch" could
+ be to create an Octopus merge, i.e. recording multiple branches
+ that are not marked as "not-for-merge"; this allows us to lose an
+ old style invocation "git merge <msg> HEAD $commits..." in the
+ implementation of "git pull" script; the old style syntax can now
+ be deprecated.
+
+ * Help us to find broken test script that splits the body part of the
+ test by mistaken use of wrong kind of quotes.
+ (merge d93d5d5 jc/test-prereq-validate later to maint).
+
+ * Developer support to automatically detect broken &&-chain in the
+ test scripts is now turned on by default.
+ (merge 92b269f jk/test-chain-lint later to maint).
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * "unsigned char [20]" used thoughout the code to represent object
+ names are being converted into a semi-opaque "struct object_id".
+ This effort is expected to interfere with other topics in flight,
+ but hopefully will give us one extra level of abstraction in the
+ end, when completed.
+
+ * Catch a programmer mistake to feed a pointer not an array to
+ ARRAY_SIZE() macro, by using a couple of GCC extensions.
+ (merge 89c855e ep/do-not-feed-a-pointer-to-array-size later to maint).
+
+ * Some error messages in "git config" were emitted without calling
+ the usual error() facility.
+
+ * When "add--interactive" splits a hunk into two overlapping hunks
+ and then let the user choose only one, it sometimes feeds an
+ incorrect patch text to "git apply". Add tests to demonstrate
+ this.
+
+ I have a slight suspicion that this may be $gmane/87202 coming back
+ and biting us (I seem to have said "let's run with this and see
+ what happens" back then).
+
+ * More line-ending tests.
+
+ * An earlier rewrite to use strbuf_getwholeline() instead of fgets(3)
+ to read packed-refs file revealed that the former is unacceptably
+ inefficient.
+
+ * The refs API uses ref_lock struct which had its own "int fd", even
+ though the same file descriptor was in the lock struct it contains.
+ Clean-up the code to lose this redundant field.
+
+ * Add the "--allow-unknown-type" option to "cat-file" to allow
+ inspecting loose objects of an experimental or a broken type.
+
+ * Many long-running operations show progress eye-candy, even when
+ they are later backgrounded. Hide the eye-candy when the process
+ is sent to the background instead.
+ (merge 9a9a41d lm/squelch-bg-progress later to maint).
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.4
+----------------
+
+Unless otherwise noted, all the fixes since v2.4 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * Memory usage of "git index-pack" has been trimmed by tens of
+ per-cent.
+ (merge c6458e6 nd/slim-index-pack-memory-usage later to maint).
+
+ * "git rev-list --objects $old --not --all" to see if everything that
+ is reachable from $old is already connected to the existing refs
+ was very inefficient.
+ (merge b6e8a3b jk/still-interesting later to maint).
+
+ * "hash-object --literally" introduced in v2.2 was not prepared to
+ take a really long object type name.
+ (merge 1427a7f jc/hash-object later to maint).
+
+ * "git rebase --quiet" was not quite quiet when there is nothing to
+ do.
+ (merge 22946a9 jk/rebase-quiet-noop later to maint).
+
+ * The completion for "log --decorate=" parameter value was incorrect.
+ (merge af16bda sg/complete-decorate-full-not-long later to maint).
+
+ * "filter-branch" corrupted commit log message that ends with an
+ incomplete line on platforms with some "sed" implementations that
+ munge such a line. Work it around by avoiding to use "sed".
+ (merge df06201 jk/filter-branch-use-of-sed-on-incomplete-line later to maint).
+
+ * "git daemon" fails to build from the source under NO_IPV6
+ configuration (regression in 2.4).
+ (merge d358f77 jc/daemon-no-ipv6-for-2.4.1 later to maint).
+
+ * Some time ago, "git blame" (incorrectly) lost the convert_to_git()
+ call when synthesizing a fake "tip" commit that represents the
+ state in the working tree, which broke folks who record the history
+ with LF line ending to make their project portabile across
+ platforms while terminating lines in their working tree files with
+ CRLF for their platform.
+ (merge 4bf256d tb/blame-resurrect-convert-to-git later to maint).
+
+ * We avoid setting core.worktree when the repository location is the
+ ".git" directory directly at the top level of the working tree, but
+ the code misdetected the case in which the working tree is at the
+ root level of the filesystem (which arguably is a silly thing to
+ do, but still valid).
+ (merge 84ccad8 jk/init-core-worktree-at-root later to maint).
+
+ * "git commit --date=now" or anything that relies on approxidate lost
+ the daylight-saving-time offset.
+ (merge f6e6362 jc/epochtime-wo-tz later to maint).
+
+ * Access to objects in repositories that borrow from another one on a
+ slow NFS server unnecessarily got more expensive due to recent code
+ becoming more cautious in a naive way not to lose objects to pruning.
+ (merge ee1c6c3 jk/prune-mtime later to maint).
+
+ * The codepaths that read .gitignore and .gitattributes files have been
+ taught that these files encoded in UTF-8 may have UTF-8 BOM marker at
+ the beginning; this makes it in line with what we do for configuration
+ files already.
+ (merge 27547e5 cn/bom-in-gitignore later to maint).
+
+ * a few helper scripts in the test suite did not report errors
+ correcty.
+ (merge de248e9 ep/fix-test-lib-functions-report later to maint).
+
+ * The default $HOME/.gitconfig file created upon "git config --global"
+ that edits it had incorrectly spelled user.name and user.email
+ entries in it.
+ (merge 7e11052 oh/fix-config-default-user-name-section later to maint).
+
+ * "git cat-file bl $blob" failed to barf even though there is no
+ object type that is "bl".
+ (merge b7994af jk/type-from-string-gently later to maint).
+
+ * The usual "git diff" when seeing a file turning into a directory
+ showed a patchset to remove the file and create all files in the
+ directory, but "git diff --no-index" simply refused to work. Also,
+ when asked to compare a file and a directory, imitate POSIX "diff"
+ and compare the file with the file with the same name in the
+ directory, instead of refusing to run.
+ (merge 0615173 jc/diff-no-index-d-f later to maint).
+
+ * "git rebase -i" moved the "current" command from "todo" to "done" a
+ bit too prematurely, losing a step when a "pick" did not even start.
+ (merge 8cbc57c ph/rebase-i-redo later to maint).
+
+ * The connection initiation code for "ssh" transport tried to absorb
+ differences between the stock "ssh" and Putty-supplied "plink" and
+ its derivatives, but the logic to tell that we are using "plink"
+ variants were too loose and falsely triggered when "plink" appeared
+ anywhere in the path (e.g. "/home/me/bin/uplink/ssh").
+ (merge baaf233 bc/connect-plink later to maint).
+
+ * "git stash pop/apply" forgot to make sure that not just the working
+ tree is clean but also the index is clean. The latter is important
+ as a stash application can conflict and the index will be used for
+ conflict resolution.
+ (merge ed178ef jk/stash-require-clean-index later to maint).
+
+ * We have prepended $GIT_EXEC_PATH and the path "git" is installed in
+ (typically "/usr/bin") to $PATH when invoking subprograms and hooks
+ for almost eternity, but the original use case the latter tried to
+ support was semi-bogus (i.e. install git to /opt/foo/git and run it
+ without having /opt/foo on $PATH), and more importantly it has
+ become less and less relevant as Git grew more mainstream (i.e. the
+ users would _want_ to have it on their $PATH). Stop prepending the
+ path in which "git" is installed to users' $PATH, as that would
+ interfere the command search order people depend on (e.g. they may
+ not like versions of programs that are unrelated to Git in /usr/bin
+ and want to override them by having different ones in /usr/local/bin
+ and have the latter directory earlier in their $PATH).
+ (merge a0b4507 jk/git-no-more-argv0-path-munging later to maint).
+
+ * core.excludesfile (defaulting to $XDG_HOME/git/ignore) is supposed
+ to be overridden by repository-specific .git/info/exclude file, but
+ the order was swapped from the beginning. This belatedly fixes it.
+ (merge 099d2d8 jc/gitignore-precedence later to maint).
+
+ * After "git add -N", the path appeared in output of "git diff HEAD"
+ and "git diff --cached HEAD", leading "git status" to classify it
+ as "Changes to be committed". Such a path, however, is not yet to
+ be scheduled to be committed. "git diff" showed the change to the
+ path as modification, not as a "new file", in the header of its
+ output.
+
+ Treat such paths as "yet to be added to the index but Git already
+ know about them"; "git diff HEAD" and "git diff --cached HEAD"
+ should not talk about them, and "git diff" should show them as new
+ files yet to be added to the index.
+ (merge d95d728 nd/diff-i-t-a later to maint).
+
+ * Code cleanups and documentation updates.
+ (merge 0269f96 mm/usage-log-l-can-take-regex later to maint).
+ (merge 64f2589 nd/t1509-chroot-test later to maint).
+ (merge f86a374 sb/test-bitmap-free-at-end later to maint).
+ (merge 05bfc7d sb/line-log-plug-pairdiff-leak later to maint).
+ (merge 846e5df pt/xdg-config-path later to maint).
+ (merge 1154aa4 jc/plug-fmt-merge-msg-leak later to maint).
+ (merge 319b678 jk/sha1-file-reduce-useless-warnings later to maint).
+ (merge 9a35c14 fg/document-commit-message-stripping later to maint).
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index b299b59..a09969b 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -10,7 +10,7 @@
Include additional statistics at the end of blame output.
-L <start>,<end>::
--L :<regex>::
+-L :<funcname>::
Annotate only the given line range. May be specified multiple times.
Overlapping ranges are allowed.
+
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 2e5ceaf..0f668bb 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -453,6 +453,8 @@ false), while all other repositories are assumed to be bare (bare
core.worktree::
Set the path to the root of the working tree.
+ If GIT_COMMON_DIR environment variable is set, core.worktree
+ is ignored and not used for determining the root of working tree.
This can be overridden by the GIT_WORK_TREE environment
variable and the '--work-tree' command-line option.
The value can be an absolute path or relative to the path to
@@ -622,6 +624,12 @@ core.commentChar::
If set to "auto", `git-commit` would select a character that is not
the beginning character of any line in existing commit messages.
+core.packedRefsTimeout::
+ The length of time, in milliseconds, to retry when trying to
+ lock the `packed-refs` file. Value 0 means not to retry at
+ all; -1 means to try indefinitely. Default is 1000 (i.e.,
+ retry for 1 second).
+
sequence.editor::
Text editor used by `git rebase -i` for editing the rebase instruction file.
The value is meant to be interpreted by the shell when it is used.
@@ -1274,6 +1282,13 @@ gc.pruneExpire::
"now" may be used to disable this grace period and always prune
unreachable objects immediately.
+gc.pruneWorktreesExpire::
+ When 'git gc' is run, it will call
+ 'prune --worktrees --expire 3.months.ago'.
+ Override the grace period with this config variable. The value
+ "now" may be used to disable the grace period and prune
+ $GIT_DIR/worktrees immediately.
+
gc.reflogExpire::
gc.<pattern>.reflogExpire::
'git reflog expire' removes reflog entries older than
@@ -1560,6 +1575,19 @@ http.saveCookies::
If set, store cookies received during requests to the file specified by
http.cookieFile. Has no effect if http.cookieFile is unset.
+http.sslCipherList::
+ A list of SSL ciphers to use when negotiating an SSL connection.
+ The available ciphers depend on whether libcurl was built against
+ NSS or OpenSSL and the particular configuration of the crypto
+ library in use. Internally this sets the 'CURLOPT_SSL_CIPHER_LIST'
+ option; see the libcurl documentation for more details on the format
+ of this list.
++
+Can be overridden by the 'GIT_SSL_CIPHER_LIST' environment variable.
+To force git to use libcurl's default cipher list and ignore any
+explicit http.sslCipherList option, set 'GIT_SSL_CIPHER_LIST' to the
+empty string.
+
http.sslVerify::
Whether to verify the SSL certificate when fetching or pushing
over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@ -2270,18 +2298,18 @@ remote.<name>.skipFetchAll::
remote.<name>.receivepack::
The default program to execute on the remote side when pushing. See
- option \--receive-pack of linkgit:git-push[1].
+ option --receive-pack of linkgit:git-push[1].
remote.<name>.uploadpack::
The default program to execute on the remote side when fetching. See
- option \--upload-pack of linkgit:git-fetch-pack[1].
+ option --upload-pack of linkgit:git-fetch-pack[1].
remote.<name>.tagOpt::
- Setting this value to \--no-tags disables automatic tag following when
- fetching from remote <name>. Setting it to \--tags will fetch every
+ Setting this value to --no-tags disables automatic tag following when
+ fetching from remote <name>. Setting it to --tags will fetch every
tag from remote <name>, even if they are not reachable from remote
branch heads. Passing these flags directly to linkgit:git-fetch[1] can
- override this setting. See options \--tags and \--no-tags of
+ override this setting. See options --tags and --no-tags of
linkgit:git-fetch[1].
remote.<name>.vcs::
diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt
index 843a20b..bcf54da 100644
--- a/Documentation/diff-generate-patch.txt
+++ b/Documentation/diff-generate-patch.txt
@@ -77,7 +77,7 @@ combined diff format
Any diff-generating command can take the `-c` or `--cc` option to
produce a 'combined diff' when showing a merge. This is the default
format when showing merges with linkgit:git-diff[1] or
-linkgit:git-show[1]. Note also that you can give the `-m' option to any
+linkgit:git-show[1]. Note also that you can give the `-m` option to any
of these commands to force generation of diffs with individual parents
of a merge.
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index ccd4998..b7c3afe 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -23,7 +23,9 @@ ifndef::git-format-patch[]
-u::
--patch::
Generate patch (see section on generating patches).
- {git-diff? This is the default.}
+ifdef::git-diff[]
+ This is the default.
+endif::git-diff[]
endif::git-format-patch[]
-s::
@@ -42,7 +44,9 @@ endif::git-format-patch[]
ifndef::git-format-patch[]
--raw::
Generate the raw format.
- {git-diff-core? This is the default.}
+ifdef::git-diff-core[]
+ This is the default.
+endif::git-diff-core[]
endif::git-format-patch[]
ifndef::git-format-patch[]
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index f2eb907..fe5282f 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -93,7 +93,8 @@ This effectively runs `add --interactive`, but bypasses the
initial command menu and directly jumps to the `patch` subcommand.
See ``Interactive mode'' for details.
--e, \--edit::
+-e::
+--edit::
Open the diff vs. the index in an editor and let the user
edit it. After the editor was closed, adjust the hunk headers
and apply the patch to the index.
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
index f6a16f4..499ae7b 100644
--- a/Documentation/git-cat-file.txt
+++ b/Documentation/git-cat-file.txt
@@ -9,7 +9,7 @@ git-cat-file - Provide content or type and size information for repository objec
SYNOPSIS
--------
[verse]
-'git cat-file' (-t | -s | -e | -p | <type> | --textconv ) <object>
+'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object>
'git cat-file' (--batch | --batch-check) < <list-of-objects>
DESCRIPTION
@@ -69,6 +69,9 @@ OPTIONS
not be combined with any other options or arguments. See the
section `BATCH OUTPUT` below for details.
+--allow-unknown-type::
+ Allow -s or -t to query broken/corrupt objects of unknown type.
+
OUTPUT
------
If '-t' is specified, one of the <type>.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index d504108..d263a56 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,6 +225,19 @@ This means that you can use `git checkout -p` to selectively discard
edits from your current working tree. See the ``Interactive Mode''
section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
+--to=<path>::
+ Check out a branch in a separate working directory at
+ `<path>`. A new working directory is linked to the current
+ repository, sharing everything except working directory
+ specific files such as HEAD, index... See "MULTIPLE WORKING
+ TREES" section for more information.
+
+--ignore-other-worktrees::
+ `git checkout` refuses when the wanted ref is already checked
+ out by another worktree. This option makes it check the ref
+ out anyway. In other words, the ref can be held by more than one
+ worktree.
+
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
when prepended with "refs/heads/", is a valid ref), then that
@@ -388,6 +401,71 @@ $ git reflog -2 HEAD # or
$ git log -g -2 HEAD
------------
+MULTIPLE WORKING TREES
+----------------------
+
+A git repository can support multiple working trees, allowing you to check
+out more than one branch at a time. With `git checkout --to` a new working
+tree is associated with the repository. This new working tree is called a
+"linked working tree" as opposed to the "main working tree" prepared by "git
+init" or "git clone". A repository has one main working tree (if it's not a
+bare repository) and zero or more linked working trees.
+
+Each linked working tree has a private sub-directory in the repository's
+$GIT_DIR/worktrees directory. The private sub-directory's name is usually
+the base name of the linked working tree's path, possibly appended with a
+number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
+command `git checkout --to /path/other/test-next next` creates the linked
+working tree in `/path/other/test-next` and also creates a
+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
+if `test-next` is already taken).
+
+Within a linked working tree, $GIT_DIR is set to point to this private
+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
+the top directory of the linked working tree.
+
+Path resolution via `git rev-parse --git-path` uses either
+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
+linked working tree `git rev-parse --git-path HEAD` returns
+`/path/main/.git/worktrees/test-next/HEAD` (not
+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
+rev-parse --git-path refs/heads/master` uses
+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
+since refs are shared across all working trees.
+
+See linkgit:gitrepository-layout[5] for more information. The rule of
+thumb is do not make any assumption about whether a path belongs to
+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
+
+When you are done with a linked working tree you can simply delete it.
+The working tree's entry in the repository's $GIT_DIR/worktrees
+directory will eventually be removed automatically (see
+`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
+`git prune --worktrees` in the main or any linked working tree to
+clean up any stale entries in $GIT_DIR/worktrees.
+
+If you move a linked working directory to another file system, or
+within a file system that does not support hard links, you need to run
+at least one git command inside the linked working directory
+(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
+so that it does not get automatically removed.
+
+To prevent a $GIT_DIR/worktrees entry from 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
+'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
+`/path/main/.git/worktrees/test-next/locked` will prevent the
+`test-next` entry from being pruned. See
+linkgit:gitrepository-layout[5] for details.
+
+Multiple checkout support for submodules is incomplete. It is NOT
+recommended to make multiple checkouts of a superproject.
+
EXAMPLES
--------
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 617e29b..617dea0 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -180,8 +180,8 @@ OPTIONS
+
--
strip::
- Strip leading and trailing empty lines, trailing whitespace, and
- #commentary and collapse consecutive empty lines.
+ Strip leading and trailing empty lines, trailing whitespace,
+ commentary and collapse consecutive empty lines.
whitespace::
Same as `strip` except #commentary is not removed.
verbatim::
@@ -282,8 +282,11 @@ configuration variable documented in linkgit:git-config[1].
--verbose::
Show unified diff between the HEAD commit and what
would be committed at the bottom of the commit message
- template. Note that this diff output doesn't have its
- lines prefixed with '#'.
+ template to help the user describe the commit by reminding
+ what changes the commit has.
+ Note that this diff output doesn't have its
+ lines prefixed with '#'. This diff will not be a part
+ of the commit message.
+
If specified twice, show in addition the unified diff between
what would be committed and the worktree files, i.e. the unstaged
diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt
index bc97071..e3c8f27 100644
--- a/Documentation/git-credential-store.txt
+++ b/Documentation/git-credential-store.txt
@@ -31,10 +31,41 @@ OPTIONS
--file=<path>::
- Use `<path>` to store credentials. The file will have its
+ Use `<path>` to lookup and store credentials. The file will have its
filesystem permissions set to prevent other users on the system
from reading it, but will not be encrypted or otherwise
- protected. Defaults to `~/.git-credentials`.
+ protected. If not specified, credentials will be searched for from
+ `~/.git-credentials` and `$XDG_CONFIG_HOME/git/credentials`, and
+ credentials will be written to `~/.git-credentials` if it exists, or
+ `$XDG_CONFIG_HOME/git/credentials` if it exists and the former does
+ not. See also <<FILES>>.
+
+[[FILES]]
+FILES
+-----
+
+If not set explicitly with '--file', there are two files where
+git-credential-store will search for credentials in order of precedence:
+
+~/.git-credentials::
+ User-specific credentials file.
+
+$XDG_CONFIG_HOME/git/credentials::
+ Second user-specific credentials file. If '$XDG_CONFIG_HOME' is not set
+ or empty, `$HOME/.config/git/credentials` will be used. Any credentials
+ stored in this file will not be used if `~/.git-credentials` has a
+ matching credential as well. It is a good idea not to create this file
+ if you sometimes use older versions of Git that do not support it.
+
+For credential lookups, the files are read in the order given above, with the
+first matching credential found taking precedence over credentials found in
+files further down the list.
+
+Credential storage will by default write to the first existing file in the
+list. If none of these files exist, `~/.git-credentials` will be created and
+written to.
+
+When erasing credentials, matching credentials will be erased from all files.
EXAMPLES
--------
diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt
index 929e496..ed57c68 100644
--- a/Documentation/git-fast-export.txt
+++ b/Documentation/git-fast-export.txt
@@ -67,17 +67,17 @@ produced incorrect results if you gave these options.
have been completed, or to save the marks table across
incremental runs. As <file> is only opened and truncated
at completion, the same path can also be safely given to
- \--import-marks.
+ --import-marks.
The file will not be written if no new object has been
marked/exported.
--import-marks=<file>::
Before processing any input, load the marks specified in
<file>. The input file must exist, must be readable, and
- must use the same format as produced by \--export-marks.
+ must use the same format as produced by --export-marks.
+
Any commits that have already been marked will not be exported again.
-If the backend uses a similar \--import-marks file, this allows for
+If the backend uses a similar --import-marks file, this allows for
incremental bidirectional exporting of the repository by keeping the
marks the same across runs.
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index 690fed3..fd32895 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -42,13 +42,13 @@ OPTIONS
--quiet::
Disable all non-fatal output, making fast-import silent when it
is successful. This option disables the output shown by
- \--stats.
+ --stats.
--stats::
Display some basic statistics about the objects fast-import has
created, the packfiles they were stored into, and the
memory used by fast-import during this run. Showing this output
- is currently the default, but can be disabled with \--quiet.
+ is currently the default, but can be disabled with --quiet.
Options for Frontends
~~~~~~~~~~~~~~~~~~~~~
@@ -81,12 +81,12 @@ Locations of Marks Files
have been completed, or to save the marks table across
incremental runs. As <file> is only opened and truncated
at checkpoint (or completion) the same path can also be
- safely given to \--import-marks.
+ safely given to --import-marks.
--import-marks=<file>::
Before processing any input, load the marks specified in
<file>. The input file must exist, must be readable, and
- must use the same format as produced by \--export-marks.
+ must use the same format as produced by --export-marks.
Multiple options may be supplied to import more than one
set of marks. If a mark is defined to different values,
the last file wins.
@@ -179,8 +179,8 @@ fast-forward update, fast-import will skip updating that ref and instead
prints a warning message. fast-import will always attempt to update all
branch refs, and does not stop on the first failure.
-Branch updates can be forced with \--force, but it's recommended that
-this only be used on an otherwise quiet repository. Using \--force
+Branch updates can be forced with --force, but it's recommended that
+this only be used on an otherwise quiet repository. Using --force
is not necessary for an initial import into an empty repository.
@@ -231,11 +231,11 @@ Date Formats
~~~~~~~~~~~~
The following date formats are supported. A frontend should select
the format it will use for this import by passing the format name
-in the \--date-format=<fmt> command-line option.
+in the --date-format=<fmt> command-line option.
`raw`::
This is the Git native format and is `<time> SP <offutc>`.
- It is also fast-import's default format, if \--date-format was
+ It is also fast-import's default format, if --date-format was
not specified.
+
The time of the event is specified by `<time>` as the number of
@@ -437,7 +437,7 @@ the email address from the other fields in the line. Note that
of bytes, except `LT`, `GT` and `LF`. `<name>` is typically UTF-8 encoded.
The time of the change is specified by `<when>` using the date format
-that was selected by the \--date-format=<fmt> command-line option.
+that was selected by the --date-format=<fmt> command-line option.
See ``Date Formats'' above for the set of supported formats, and
their syntax.
@@ -600,7 +600,7 @@ be removed from the branch.
See `filemodify` above for a detailed description of `<path>`.
`filecopy`
-^^^^^^^^^^^^
+^^^^^^^^^^
Recursively copies an existing file or subdirectory to a different
location within the branch. The existing file or directory must
exist. If the destination exists it will be completely replaced
@@ -888,7 +888,7 @@ save out all current branch refs, tags and marks.
....
Note that fast-import automatically switches packfiles when the current
-packfile reaches \--max-pack-size, or 4 GiB, whichever limit is
+packfile reaches --max-pack-size, or 4 GiB, whichever limit is
smaller. During an automatic packfile switch fast-import does not update
the branch refs, tags or marks.
@@ -1226,7 +1226,7 @@ users of fast-import, and are offered here as suggestions.
Use One Mark Per Commit
~~~~~~~~~~~~~~~~~~~~~~~
When doing a repository conversion, use a unique mark per commit
-(`mark :<n>`) and supply the \--export-marks option on the command
+(`mark :<n>`) and supply the --export-marks option on the command
line. fast-import will dump a file which lists every mark and the Git
object SHA-1 that corresponds to it. If the frontend can tie
the marks back to the source repository, it is easy to verify the
@@ -1291,7 +1291,7 @@ even for considerably large projects (100,000+ commits).
However repacking the repository is necessary to improve data
locality and access performance. It can also take hours on extremely
-large projects (especially if -f and a large \--window parameter is
+large projects (especially if -f and a large --window parameter is
used). Since repacking is safe to run alongside readers and writers,
run the repack in the background and let it finish when it finishes.
There is no reason to wait to explore your new Git project!
@@ -1305,7 +1305,7 @@ Repacking Historical Data
~~~~~~~~~~~~~~~~~~~~~~~~~
If you are repacking very old imported data (e.g. older than the
last year), consider expending some extra CPU time and supplying
-\--window=50 (or higher) when you run 'git repack'.
+--window=50 (or higher) when you run 'git repack'.
This will take longer, but will also produce a smaller packfile.
You only need to expend the effort once, and everyone using your
project will benefit from the smaller repository.
@@ -1407,7 +1407,7 @@ branch, their in-memory storage size can grow to a considerable size
fast-import automatically moves active branches to inactive status based on
a simple least-recently-used algorithm. The LRU chain is updated on
each `commit` command. The maximum number of active branches can be
-increased or decreased on the command line with \--active-branches=.
+increased or decreased on the command line with --active-branches=.
per active tree
~~~~~~~~~~~~~~~
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index 93b5067..8680f45 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -80,7 +80,7 @@ be in a separate packet, and the list must end with a flush packet.
the things up in .bash_profile).
--exec=<git-upload-pack>::
- Same as \--upload-pack=<git-upload-pack>.
+ Same as --upload-pack=<git-upload-pack>.
--depth=<n>::
Limit fetching to ancestor-chains not longer than n.
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
index 02c1f12..0c75f3b 100644
--- a/Documentation/git-hash-object.txt
+++ b/Documentation/git-hash-object.txt
@@ -9,7 +9,7 @@ git-hash-object - Compute object ID and optionally creates a blob from a file
SYNOPSIS
--------
[verse]
-'git hash-object' [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...
+'git hash-object' [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin [--literally]] [--] <file>...
'git hash-object' [-t <type>] [-w] --stdin-paths [--no-filters] < <list-of-paths>
DESCRIPTION
@@ -51,7 +51,13 @@ OPTIONS
Hash the contents as is, ignoring any input filter that would
have been chosen by the attributes mechanism, including the end-of-line
conversion. If the file is read from standard input then this
- is always implied, unless the --path option is given.
+ is always implied, unless the `--path` option is given.
+
+--literally::
+ Allow `--stdin` to hash any garbage into a loose object which might not
+ otherwise pass standard object parsing or git-fsck checks. Useful for
+ stress-testing Git itself or reproducing characteristics of corrupt or
+ bogus objects encountered in the wild.
GIT
---
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index d422ba4..3ca18c4 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -65,8 +65,8 @@ automatically by the web server.
EXAMPLES
--------
-All of the following examples map 'http://$hostname/git/foo/bar.git'
-to '/var/www/git/foo/bar.git'.
+All of the following examples map `http://$hostname/git/foo/bar.git`
+to `/var/www/git/foo/bar.git`.
Apache 2.x::
Ensure mod_cgi, mod_alias, and mod_env are enabled, set
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 18bc716..5692945 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -62,9 +62,9 @@ produced by `--stat`, etc.
output by allowing them to allocate space in advance.
-L <start>,<end>:<file>::
--L :<regex>:<file>::
+-L :<funcname>:<file>::
Trace the evolution of the line range given by "<start>,<end>"
- (or the funcname regex <regex>) within the <file>. You may
+ (or the function name regex <funcname>) within the <file>. You may
not give any pathspec limiters. This is currently limited to
a walk starting from a single revision, i.e., you may only
give zero or one positive revision arguments.
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 1f94908..273a100 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -104,6 +104,10 @@ commit or stash your changes before running 'git merge'.
If no commit is given from the command line, merge the remote-tracking
branches that the current branch is configured to use as its upstream.
See also the configuration section of this manual page.
++
+When `FETCH_HEAD` (and no other commit) is specified, the branches
+recorded in the `.git/FETCH_HEAD` file by the previous invocation
+of `git fetch` for merging are merged to the current branch.
PRE-MERGE CHECKS
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
index a1664b9..82aa5d6 100644
--- a/Documentation/git-p4.txt
+++ b/Documentation/git-p4.txt
@@ -225,9 +225,20 @@ Git repository:
they can find the p4 branches in refs/heads.
--max-changes <n>::
- Limit the number of imported changes to 'n'. Useful to
- limit the amount of history when using the '@all' p4 revision
- specifier.
+ Import at most 'n' changes, rather than the entire range of
+ changes included in the given revision specifier. A typical
+ usage would be use '@all' as the revision specifier, but then
+ to use '--max-changes 1000' to import only the last 1000
+ revisions rather than the entire revision history.
+
+--changes-block-size <n>::
+ The internal block size to use when converting a revision
+ specifier such as '@all' into a list of specific change
+ numbers. Instead of using a single call to 'p4 changes' to
+ find the full list of changes for the conversion, there are a
+ sequence of calls to 'p4 changes -m', each of which requests
+ one block of changes of the given size. The default block size
+ is 500, which should usually be suitable.
--keep-path::
The mapping of file names from the p4 depot path to Git, by
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index c2f76fb..bbea529 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -192,7 +192,7 @@ self-contained. Use `git index-pack --fix-thin`
--shallow::
Optimize a pack that will be provided to a client with a shallow
- repository. This option, combined with \--thin, can result in a
+ repository. This option, combined with --thin, can result in a
smaller pack at the cost of speed.
--delta-base-offset::
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 7a493c8..1cf3bed 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -48,6 +48,9 @@ OPTIONS
--expire <time>::
Only expire loose objects older than <time>.
+--worktrees::
+ Prune dead working tree information in $GIT_DIR/worktrees.
+
<head>...::
In addition to objects
reachable from any of our references, keep objects
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 863c30c..135d810 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -265,8 +265,8 @@ origin +master` to force a push to the `master` branch). See the
--[no-]verify::
Toggle the pre-push hook (see linkgit:githooks[5]). The
- default is \--verify, giving the hook a chance to prevent the
- push. With \--no-verify, the hook is bypassed completely.
+ default is --verify, giving the hook a chance to prevent the
+ push. With --no-verify, the hook is bypassed completely.
include::urls-remotes.txt[]
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 47984e8..1d01baa 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -80,7 +80,7 @@ remain the checked-out branch.
If the upstream branch already contains a change you have made (e.g.,
because you mailed a patch which was applied upstream), then that commit
will be skipped. For example, running `git rebase master` on the
-following history (in which A' and A introduce the same set of changes,
+following history (in which `A'` and `A` introduce the same set of changes,
but have different committer information):
------------
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 5b11922..b10ea60 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -9,54 +9,54 @@ git-rev-list - Lists commit objects in reverse chronological order
SYNOPSIS
--------
[verse]
-'git rev-list' [ \--max-count=<number> ]
- [ \--skip=<number> ]
- [ \--max-age=<timestamp> ]
- [ \--min-age=<timestamp> ]
- [ \--sparse ]
- [ \--merges ]
- [ \--no-merges ]
- [ \--min-parents=<number> ]
- [ \--no-min-parents ]
- [ \--max-parents=<number> ]
- [ \--no-max-parents ]
- [ \--first-parent ]
- [ \--remove-empty ]
- [ \--full-history ]
- [ \--not ]
- [ \--all ]
- [ \--branches[=<pattern>] ]
- [ \--tags[=<pattern>] ]
- [ \--remotes[=<pattern>] ]
- [ \--glob=<glob-pattern> ]
- [ \--ignore-missing ]
- [ \--stdin ]
- [ \--quiet ]
- [ \--topo-order ]
- [ \--parents ]
- [ \--timestamp ]
- [ \--left-right ]
- [ \--left-only ]
- [ \--right-only ]
- [ \--cherry-mark ]
- [ \--cherry-pick ]
- [ \--encoding=<encoding> ]
- [ \--(author|committer|grep)=<pattern> ]
- [ \--regexp-ignore-case | -i ]
- [ \--extended-regexp | -E ]
- [ \--fixed-strings | -F ]
- [ \--date=(local|relative|default|iso|iso-strict|rfc|short) ]
- [ [ \--objects | \--objects-edge | \--objects-edge-aggressive ]
- [ \--unpacked ] ]
- [ \--pretty | \--header ]
- [ \--bisect ]
- [ \--bisect-vars ]
- [ \--bisect-all ]
- [ \--merge ]
- [ \--reverse ]
- [ \--walk-reflogs ]
- [ \--no-walk ] [ \--do-walk ]
- [ \--use-bitmap-index ]
+'git rev-list' [ --max-count=<number> ]
+ [ --skip=<number> ]
+ [ --max-age=<timestamp> ]
+ [ --min-age=<timestamp> ]
+ [ --sparse ]
+ [ --merges ]
+ [ --no-merges ]
+ [ --min-parents=<number> ]
+ [ --no-min-parents ]
+ [ --max-parents=<number> ]
+ [ --no-max-parents ]
+ [ --first-parent ]
+ [ --remove-empty ]
+ [ --full-history ]
+ [ --not ]
+ [ --all ]
+ [ --branches[=<pattern>] ]
+ [ --tags[=<pattern>] ]
+ [ --remotes[=<pattern>] ]
+ [ --glob=<glob-pattern> ]
+ [ --ignore-missing ]
+ [ --stdin ]
+ [ --quiet ]
+ [ --topo-order ]
+ [ --parents ]
+ [ --timestamp ]
+ [ --left-right ]
+ [ --left-only ]
+ [ --right-only ]
+ [ --cherry-mark ]
+ [ --cherry-pick ]
+ [ --encoding=<encoding> ]
+ [ --(author|committer|grep)=<pattern> ]
+ [ --regexp-ignore-case | -i ]
+ [ --extended-regexp | -E ]
+ [ --fixed-strings | -F ]
+ [ --date=(local|relative|default|iso|iso-strict|rfc|short) ]
+ [ [ --objects | --objects-edge | --objects-edge-aggressive ]
+ [ --unpacked ] ]
+ [ --pretty | --header ]
+ [ --bisect ]
+ [ --bisect-vars ]
+ [ --bisect-all ]
+ [ --merge ]
+ [ --reverse ]
+ [ --walk-reflogs ]
+ [ --no-walk ] [ --do-walk ]
+ [ --use-bitmap-index ]
<commit>... [ \-- <paths>... ]
DESCRIPTION
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index d6de42f..c483100 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -102,7 +102,7 @@ eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")"
+
If you want to make sure that the output actually names an object in
your object database and/or can be used as a specific type of object
-you require, you can add "\^{type}" peeling operator to the parameter.
+you require, you can add the `^{type}` peeling operator to the parameter.
For example, `git rev-parse "$VAR^{commit}"` will make sure `$VAR`
names an existing object that is a commit-ish (i.e. a commit, or an
annotated tag that points at a commit). To make sure that `$VAR`
@@ -147,7 +147,7 @@ can be used.
form as close to the original input as possible.
--symbolic-full-name::
- This is similar to \--symbolic, but it omits input that
+ This is similar to --symbolic, but it omits input that
are not refs (i.e. branch or tag names; or more
explicitly disambiguating "heads/master" form, when you
want to name the "master" branch when there is an
@@ -216,6 +216,9 @@ If `$GIT_DIR` is not defined and the current directory
is not detected to lie in a Git repository or work tree
print a message to stderr and exit with nonzero status.
+--git-common-dir::
+ Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
+
--is-inside-git-dir::
When the current working directory is below the repository
directory print "true", otherwise "false".
@@ -233,6 +236,13 @@ print a message to stderr and exit with nonzero status.
repository. If <path> is a gitfile then the resolved path
to the real repository is printed.
+--git-path <path>::
+ Resolve "$GIT_DIR/<path>" and takes other path relocation
+ variables such as $GIT_OBJECT_DIRECTORY,
+ $GIT_INDEX_FILE... into account. For example, if
+ $GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
+ --git-path objects/abc" returns /foo/bar/abc.
+
--show-cdup::
When the command is invoked from a subdirectory, show the
path of the top-level directory relative to the current
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 45c7725..b5d09f7 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -29,7 +29,7 @@ OPTIONS
a directory on the default $PATH.
--exec=<git-receive-pack>::
- Same as \--receive-pack=<git-receive-pack>.
+ Same as --receive-pack=<git-receive-pack>.
--all::
Instead of explicitly specifying which refs to update,
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index 4e617e6..82a4125 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -22,7 +22,7 @@ presents the merge commit in a special format as produced by
For tags, it shows the tag message and the referenced objects.
For trees, it shows the names (equivalent to 'git ls-tree'
-with \--name-only).
+with --name-only).
For plain blobs, it shows the plain contents.
diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt
index 6c6e989..60328d5 100644
--- a/Documentation/git-stripspace.txt
+++ b/Documentation/git-stripspace.txt
@@ -49,7 +49,7 @@ EXAMPLES
Given the following noisy input with '$' indicating the end of a line:
---------
+---------
|A brief introduction $
| $
|$
@@ -65,7 +65,7 @@ Given the following noisy input with '$' indicating the end of a line:
Use 'git stripspace' with no arguments to obtain:
---------
+---------
|A brief introduction$
|$
|A new paragraph$
@@ -79,7 +79,7 @@ Use 'git stripspace' with no arguments to obtain:
Use 'git stripspace --strip-comments' to obtain:
---------
+---------
|A brief introduction$
|$
|A new paragraph$
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 39e9a18..11d1e2f 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -70,8 +70,8 @@ COMMANDS
--username=<user>;;
For transports that SVN handles authentication for (http,
https, and plain svn), specify the username. For other
- transports (e.g. svn+ssh://), you must include the username in
- the URL, e.g. svn+ssh://foo@svn.bar.com/project
+ transports (e.g. `svn+ssh://`), you must include the username in
+ the URL, e.g. `svn+ssh://foo@svn.bar.com/project`
--prefix=<prefix>;;
This allows one to specify a prefix which is prepended
to the names of remotes if trunk/branches/tags are
@@ -279,9 +279,9 @@ first have already been pushed into SVN.
Ask the user to confirm that a patch set should actually be sent to SVN.
For each patch, one may answer "yes" (accept this patch), "no" (discard this
patch), "all" (accept all patches), or "quit".
- +
- 'git svn dcommit' returns immediately if answer is "no" or "quit", without
- committing anything to SVN.
++
+'git svn dcommit' returns immediately if answer is "no" or "quit", without
+committing anything to SVN.
'branch'::
Create a branch in the SVN repository.
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index f5b267e..034d10d 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -158,7 +158,7 @@ This option is only applicable when listing tags without annotation lines.
CONFIGURATION
-------------
By default, 'git tag' in sign-with-default mode (-s) will use your
-committer identity (of the form "Your Name <\your@email.address>") to
+committer identity (of the form `Your Name <your@email.address>`) to
find a key. If you want to use a different default key, you can specify
it in the repository configuration as follows:
diff --git a/Documentation/git.txt b/Documentation/git.txt
index c71e818..38cc004 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -43,14 +43,16 @@ unreleased) version of Git, that is available from the 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v2.4.0/git.html[documentation for release 2.4]
+* link:v2.4.1/git.html[documentation for release 2.4.1]
* release notes for
+ link:RelNotes/2.4.1.txt[2.4.1],
link:RelNotes/2.4.0.txt[2.4].
-* link:v2.3.7/git.html[documentation for release 2.3.7]
+* link:v2.3.8/git.html[documentation for release 2.3.8]
* release notes for
+ link:RelNotes/2.3.8.txt[2.3.8],
link:RelNotes/2.3.7.txt[2.3.7],
link:RelNotes/2.3.6.txt[2.3.6],
link:RelNotes/2.3.5.txt[2.3.5],
@@ -833,6 +835,15 @@ Git so take care if using Cogito etc.
an explicit repository directory set via 'GIT_DIR' or on the
command line.
+'GIT_COMMON_DIR'::
+ If this variable is set to a path, non-worktree files that are
+ normally in $GIT_DIR will be taken from this path
+ instead. Worktree-specific files such as HEAD or index are
+ taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and
+ the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]
+ details. This variable has lower precedence than other path
+ variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
+
Git Commits
~~~~~~~~~~~
'GIT_AUTHOR_NAME'::
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index 8475c07..36e9ab3 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -259,7 +259,7 @@ index 557db03..263414f 100644
@@ -1 +1,2 @@
Hello World
+It's a new day for git
-----
+------------
i.e. the diff of the change we caused by adding another line to `hello`.
diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt
index c8b3e51..c579593 100644
--- a/Documentation/gitdiffcore.txt
+++ b/Documentation/gitdiffcore.txt
@@ -28,8 +28,8 @@ The 'git diff-{asterisk}' family works by first comparing two sets of
files:
- 'git diff-index' compares contents of a "tree" object and the
- working directory (when '\--cached' flag is not used) or a
- "tree" object and the index file (when '\--cached' flag is
+ working directory (when '--cached' flag is not used) or a
+ "tree" object and the index file (when '--cached' flag is
used);
- 'git diff-files' compares contents of the index file and the
@@ -142,7 +142,7 @@ merges these filepairs and creates:
When the "-C" option is used, the original contents of modified files,
and deleted files (and also unmodified files, if the
-"\--find-copies-harder" option is used) are considered as candidates
+"--find-copies-harder" option is used) are considered as candidates
of the source files in rename/copy operation. If the input were like
these filepairs, that talk about a modified file fileY and a newly
created file file0:
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
index 7ae50aa..6ade002 100644
--- a/Documentation/gitk.txt
+++ b/Documentation/gitk.txt
@@ -99,10 +99,10 @@ linkgit:git-rev-list[1] for a complete list.
detailed explanation.)
-L<start>,<end>:<file>::
--L:<regex>:<file>::
+-L:<funcname>:<file>::
Trace the evolution of the line range given by "<start>,<end>"
- (or the funcname regex <regex>) within the <file>. You may
+ (or the function name regex <funcname>) within the <file>. You may
not give any pathspec limiters. This is currently limited to
a walk starting from a single revision, i.e., you may only
give zero or one positive revision arguments.
diff --git a/Documentation/gitremote-helpers.txt b/Documentation/gitremote-helpers.txt
index 8edf72c..82e2d15 100644
--- a/Documentation/gitremote-helpers.txt
+++ b/Documentation/gitremote-helpers.txt
@@ -408,14 +408,14 @@ set by Git if the remote helper has the 'option' capability.
of <n> correspond to the number of -v flags passed on the
command line.
-'option progress' \{'true'|'false'\}::
+'option progress' {'true'|'false'}::
Enables (or disables) progress messages displayed by the
transport helper during a command.
'option depth' <depth>::
Deepens the history of a shallow repository.
-'option followtags' \{'true'|'false'\}::
+'option followtags' {'true'|'false'}::
If enabled the helper should automatically fetch annotated
tag objects if the object the tag points at was transferred
during the fetch command. If the tag is not fetched by
@@ -423,7 +423,7 @@ set by Git if the remote helper has the 'option' capability.
ask for the tag specifically. Some helpers may be able to
use this option to avoid a second network connection.
-'option dry-run' \{'true'|'false'\}:
+'option dry-run' {'true'|'false'}:
If true, pretend the operation completed successfully,
but don't actually change any repository data. For most
helpers this only applies to the 'push', if supported.
@@ -434,18 +434,18 @@ set by Git if the remote helper has the 'option' capability.
must not rely on this option being set before
connect request occurs.
-'option check-connectivity' \{'true'|'false'\}::
+'option check-connectivity' {'true'|'false'}::
Request the helper to check connectivity of a clone.
-'option force' \{'true'|'false'\}::
+'option force' {'true'|'false'}::
Request the helper to perform a force update. Defaults to
'false'.
-'option cloning \{'true'|'false'\}::
+'option cloning {'true'|'false'}::
Notify the helper this is a clone request (i.e. the current
repository is guaranteed empty).
-'option update-shallow \{'true'|'false'\}::
+'option update-shallow {'true'|'false'}::
Allow to extend .git/shallow if the new refs require it.
SEE ALSO
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 79653f3..7173b38 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for
use with dumb transports but otherwise is OK as long as
`objects/info/alternates` points at the object stores it
borrows from.
++
+This directory is ignored if $GIT_COMMON_DIR is set and
+"$GIT_COMMON_DIR/objects" will be used instead.
objects/[0-9a-f][0-9a-f]::
A newly created object is stored in its own file.
@@ -92,7 +95,8 @@ refs::
References are stored in subdirectories of this
directory. The 'git prune' command knows to preserve
objects reachable from refs found in this directory and
- its subdirectories.
+ its subdirectories. This directory is ignored if $GIT_COMMON_DIR
+ is set and "$GIT_COMMON_DIR/refs" will be used instead.
refs/heads/`name`::
records tip-of-the-tree commit objects of branch `name`
@@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`::
packed-refs::
records the same information as refs/heads/, refs/tags/,
and friends record in a more efficient way. See
- linkgit:git-pack-refs[1].
+ linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR
+ is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
HEAD::
A symref (see glossary) to the `refs/heads/` namespace
@@ -133,6 +138,11 @@ being a symref to point at the current branch. Such a state
is often called 'detached HEAD.' See linkgit:git-checkout[1]
for details.
+config::
+ Repository specific configuration file. This file is ignored
+ if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
+ used instead.
+
branches::
A slightly deprecated way to store shorthands to be used
to specify a URL to 'git fetch', 'git pull' and 'git push'.
@@ -140,7 +150,10 @@ branches::
'name' can be given to these commands in place of
'repository' argument. See the REMOTES section in
linkgit:git-fetch[1] for details. This mechanism is legacy
- and not likely to be found in modern repositories.
+ and not likely to be found in modern repositories. This
+ directory is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/branches" will be used instead.
+
hooks::
Hooks are customization scripts used by various Git
@@ -149,7 +162,9 @@ hooks::
default. To enable, the `.sample` suffix has to be
removed from the filename by renaming.
Read linkgit:githooks[5] for more details about
- each hook.
+ each hook. This directory is ignored if $GIT_COMMON_DIR is set
+ and "$GIT_COMMON_DIR/hooks" will be used instead.
+
index::
The current index file for the repository. It is
@@ -161,7 +176,8 @@ sharedindex.<SHA-1>::
info::
Additional information about the repository is recorded
- in this directory.
+ in this directory. This directory is ignored if $GIT_COMMON_DIR
+ is set and "$GIT_COMMON_DIR/index" will be used instead.
info/refs::
This file helps dumb transports discover what refs are
@@ -201,12 +217,15 @@ remotes::
when interacting with remote repositories via 'git fetch',
'git pull' and 'git push' commands. See the REMOTES section
in linkgit:git-fetch[1] for details. This mechanism is legacy
- and not likely to be found in modern repositories.
+ and not likely to be found in modern repositories. This
+ directory is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/remotes" will be used instead.
logs::
- Records of changes made to refs are stored in this
- directory. See linkgit:git-update-ref[1]
- for more information.
+ Records of changes made to refs are stored in this directory.
+ See linkgit:git-update-ref[1] for more information. This
+ directory is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/logs" will be used instead.
logs/refs/heads/`name`::
Records all changes made to the branch tip named `name`.
@@ -217,11 +236,46 @@ logs/refs/tags/`name`::
shallow::
This is similar to `info/grafts` but is internally used
and maintained by shallow clone mechanism. See `--depth`
- option to linkgit:git-clone[1] and linkgit:git-fetch[1].
+ option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
+ file is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/shallow" will be used instead.
+
+commondir::
+ If this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will
+ be set to the path specified in this file if it is not
+ explicitly set. If the specified path is relative, it is
+ relative to $GIT_DIR. The repository with commondir is
+ incomplete without the repository pointed by "commondir".
modules::
Contains the git-repositories of the submodules.
+worktrees::
+ Contains worktree specific information of linked
+ checkouts. Each subdirectory contains the worktree-related
+ part of a linked checkout. This directory is ignored if
+ $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/worktrees" will be
+ used instead.
+
+worktrees/<id>/gitdir::
+ A text file containing the absolute path back to the .git file
+ that points to here. This is used to check if the linked
+ repository has been manually removed and there is no need to
+ keep this directory any more. mtime of this file should be
+ updated every time the linked repository is accessed.
+
+worktrees/<id>/locked::
+ If this file exists, the linked repository may be on a
+ portable device and not available. It does not mean that the
+ linked repository is gone and `worktrees/<id>` could be
+ removed. The file's content contains a reason string on why
+ the repository is locked.
+
+worktrees/<id>/link::
+ If this file exists, it is a hard link to the linked .git
+ file. It is used to detect if the linked repository is
+ manually removed.
+
SEE ALSO
--------
linkgit:git-init[1],
diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
index d7f2603..829676f 100644
--- a/Documentation/line-range-format.txt
+++ b/Documentation/line-range-format.txt
@@ -22,8 +22,9 @@ This is only valid for <end> and will specify a number
of lines before or after the line given by <start>.
+
-If ``:<regex>'' is given in place of <start> and <end>, it denotes the range
-from the first funcname line that matches <regex>, up to the next
-funcname line. ``:<regex>'' searches from the end of the previous `-L` range,
-if any, otherwise from the start of file.
-``^:<regex>'' searches from the start of file.
+If ``:<funcname>'' is given in place of <start> and <end>, it is a
+regular expression that denotes the range from the first funcname line
+that matches <funcname>, up to the next funcname line. ``:<funcname>''
+searches from the end of the previous `-L` range, if any, otherwise
+from the start of file. ``^:<funcname>'' searches from the start of
+file.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 48aee9c..bfb715d 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.4.0
+DEF_VER=v2.4.0.GIT
LF='
'
diff --git a/Makefile b/Makefile
index 5f3987f..25a453b 100644
--- a/Makefile
+++ b/Makefile
@@ -359,6 +359,8 @@ all::
# compiler is detected to support it.
#
# Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
+#
+# Define HAVE_GETDELIM if your system has the getdelim() function.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1437,6 +1439,10 @@ ifdef HAVE_BSD_SYSCTL
BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
endif
+ifdef HAVE_GETDELIM
+ BASIC_CFLAGS += -DHAVE_GETDELIM
+endif
+
ifeq ($(TCLTK_PATH),)
NO_TCLTK = NoThanks
endif
@@ -2102,6 +2108,7 @@ GIT-BUILD-OPTIONS: FORCE
@echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
+ @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@
@echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
diff --git a/RelNotes b/RelNotes
index 1addbec..3295d66 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.4.0.txt \ No newline at end of file
+Documentation/RelNotes/2.5.0.txt \ No newline at end of file
diff --git a/archive-zip.c b/archive-zip.c
index ffb3535..ae3d67f 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -448,12 +448,12 @@ static void write_zip_trailer(const unsigned char *sha1)
copy_le16(trailer.entries, zip_dir_entries);
copy_le32(trailer.size, zip_dir_offset);
copy_le32(trailer.offset, zip_offset);
- copy_le16(trailer.comment_length, sha1 ? 40 : 0);
+ copy_le16(trailer.comment_length, sha1 ? GIT_SHA1_HEXSZ : 0);
write_or_die(1, zip_dir, zip_dir_offset);
write_or_die(1, &trailer, ZIP_DIR_TRAILER_SIZE);
if (sha1)
- write_or_die(1, sha1_to_hex(sha1), 40);
+ write_or_die(1, sha1_to_hex(sha1), GIT_SHA1_HEXSZ);
}
static void dos_time(time_t *time, int *dos_date, int *dos_time)
diff --git a/archive.c b/archive.c
index 96057ed..d37c41d 100644
--- a/archive.c
+++ b/archive.c
@@ -101,7 +101,7 @@ static void setup_archive_check(struct git_attr_check *check)
struct directory {
struct directory *up;
- unsigned char sha1[20];
+ struct object_id oid;
int baselen, len;
unsigned mode;
int stage;
@@ -177,7 +177,7 @@ static void queue_directory(const unsigned char *sha1,
d->stage = stage;
c->bottom = d;
d->len = sprintf(d->path, "%.*s%s/", (int)base->len, base->buf, filename);
- hashcpy(d->sha1, sha1);
+ hashcpy(d->oid.hash, sha1);
}
static int write_directory(struct archiver_context *c)
@@ -191,7 +191,7 @@ static int write_directory(struct archiver_context *c)
d->path[d->len - 1] = '\0'; /* no trailing slash */
ret =
write_directory(c) ||
- write_archive_entry(d->sha1, d->path, d->baselen,
+ write_archive_entry(d->oid.hash, d->path, d->baselen,
d->path + d->baselen, d->mode,
d->stage, c) != READ_TREE_RECURSIVE;
free(d);
@@ -354,7 +354,7 @@ static void parse_treeish_arg(const char **argv,
time_t archive_time;
struct tree *tree;
const struct commit *commit;
- unsigned char sha1[20];
+ struct object_id oid;
/* Remotes are only allowed to fetch actual refs */
if (remote && !remote_allow_unreachable) {
@@ -362,15 +362,15 @@ static void parse_treeish_arg(const char **argv,
const char *colon = strchrnul(name, ':');
int refnamelen = colon - name;
- if (!dwim_ref(name, refnamelen, sha1, &ref))
+ if (!dwim_ref(name, refnamelen, oid.hash, &ref))
die("no such ref: %.*s", refnamelen, name);
free(ref);
}
- if (get_sha1(name, sha1))
+ if (get_sha1(name, oid.hash))
die("Not a valid object name");
- commit = lookup_commit_reference_gently(sha1, 1);
+ commit = lookup_commit_reference_gently(oid.hash, 1);
if (commit) {
commit_sha1 = commit->object.sha1;
archive_time = commit->date;
@@ -379,21 +379,21 @@ static void parse_treeish_arg(const char **argv,
archive_time = time(NULL);
}
- tree = parse_tree_indirect(sha1);
+ tree = parse_tree_indirect(oid.hash);
if (tree == NULL)
die("not a tree object");
if (prefix) {
- unsigned char tree_sha1[20];
+ struct object_id tree_oid;
unsigned int mode;
int err;
err = get_tree_entry(tree->object.sha1, prefix,
- tree_sha1, &mode);
+ tree_oid.hash, &mode);
if (err || !S_ISDIR(mode))
die("current working directory is untracked");
- tree = parse_tree_indirect(tree_sha1);
+ tree = parse_tree_indirect(tree_oid.hash);
}
ar_args->tree = tree;
ar_args->commit_sha1 = commit_sha1;
diff --git a/attr.c b/attr.c
index 1f9eebd..8f2ac6c 100644
--- a/attr.c
+++ b/attr.c
@@ -12,6 +12,7 @@
#include "exec_cmd.h"
#include "attr.h"
#include "dir.h"
+#include "utf8.h"
const char git_attr__true[] = "(builtin)true";
const char git_attr__false[] = "\0(builtin)false";
@@ -379,8 +380,12 @@ static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
return NULL;
}
res = xcalloc(1, sizeof(*res));
- while (fgets(buf, sizeof(buf), fp))
- handle_attr_line(res, buf, path, ++lineno, macro_ok);
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *bufp = buf;
+ if (!lineno)
+ skip_utf8_bom(&bufp, strlen(bufp));
+ handle_attr_line(res, bufp, path, ++lineno, macro_ok);
+ }
fclose(fp);
return res;
}
@@ -488,7 +493,6 @@ static int git_attr_system(void)
static void bootstrap_attr_stack(void)
{
struct attr_stack *elem;
- char *xdg_attributes_file;
if (attr_stack)
return;
@@ -507,10 +511,8 @@ static void bootstrap_attr_stack(void)
}
}
- if (!git_attributes_file) {
- home_config_paths(NULL, &xdg_attributes_file, "attributes");
- git_attributes_file = xdg_attributes_file;
- }
+ if (!git_attributes_file)
+ git_attributes_file = xdg_config_home("attributes");
if (git_attributes_file) {
elem = read_attr_from_file(git_attributes_file, 1);
if (elem) {
diff --git a/bisect.c b/bisect.c
index 8c6d843..10f5e57 100644
--- a/bisect.c
+++ b/bisect.c
@@ -15,7 +15,7 @@
static struct sha1_array good_revs;
static struct sha1_array skipped_revs;
-static unsigned char *current_bad_sha1;
+static struct object_id *current_bad_oid;
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
@@ -404,8 +404,8 @@ static int register_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
if (!strcmp(refname, "bad")) {
- current_bad_sha1 = xmalloc(20);
- hashcpy(current_bad_sha1, sha1);
+ current_bad_oid = xmalloc(sizeof(*current_bad_oid));
+ hashcpy(current_bad_oid->hash, sha1);
} else if (starts_with(refname, "good-")) {
sha1_array_append(&good_revs, sha1);
} else if (starts_with(refname, "skip-")) {
@@ -564,7 +564,7 @@ static struct commit_list *skip_away(struct commit_list *list, int count)
for (i = 0; cur; cur = cur->next, i++) {
if (i == index) {
- if (hashcmp(cur->item->object.sha1, current_bad_sha1))
+ if (hashcmp(cur->item->object.sha1, current_bad_oid->hash))
return cur;
if (previous)
return previous;
@@ -607,7 +607,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
/* rev_argv.argv[0] will be ignored by setup_revisions */
argv_array_push(&rev_argv, "bisect_rev_setup");
- argv_array_pushf(&rev_argv, bad_format, sha1_to_hex(current_bad_sha1));
+ argv_array_pushf(&rev_argv, bad_format, oid_to_hex(current_bad_oid));
for (i = 0; i < good_revs.nr; i++)
argv_array_pushf(&rev_argv, good_format,
sha1_to_hex(good_revs.sha1[i]));
@@ -628,7 +628,7 @@ static void bisect_common(struct rev_info *revs)
}
static void exit_if_skipped_commits(struct commit_list *tried,
- const unsigned char *bad)
+ const struct object_id *bad)
{
if (!tried)
return;
@@ -637,12 +637,12 @@ static void exit_if_skipped_commits(struct commit_list *tried,
"The first bad commit could be any of:\n");
print_commit_list(tried, "%s\n", "%s\n");
if (bad)
- printf("%s\n", sha1_to_hex(bad));
+ printf("%s\n", oid_to_hex(bad));
printf("We cannot bisect more!\n");
exit(2);
}
-static int is_expected_rev(const unsigned char *sha1)
+static int is_expected_rev(const struct object_id *oid)
{
const char *filename = git_path("BISECT_EXPECTED_REV");
struct stat st;
@@ -658,7 +658,7 @@ static int is_expected_rev(const unsigned char *sha1)
return 0;
if (strbuf_getline(&str, fp, '\n') != EOF)
- res = !strcmp(str.buf, sha1_to_hex(sha1));
+ res = !strcmp(str.buf, oid_to_hex(oid));
strbuf_release(&str);
fclose(fp);
@@ -719,7 +719,7 @@ static struct commit **get_bad_and_good_commits(int *rev_nr)
struct commit **rev = xmalloc(len * sizeof(*rev));
int i, n = 0;
- rev[n++] = get_commit_reference(current_bad_sha1);
+ rev[n++] = get_commit_reference(current_bad_oid->hash);
for (i = 0; i < good_revs.nr; i++)
rev[n++] = get_commit_reference(good_revs.sha1[i]);
*rev_nr = n;
@@ -729,8 +729,8 @@ static struct commit **get_bad_and_good_commits(int *rev_nr)
static void handle_bad_merge_base(void)
{
- if (is_expected_rev(current_bad_sha1)) {
- char *bad_hex = sha1_to_hex(current_bad_sha1);
+ if (is_expected_rev(current_bad_oid)) {
+ char *bad_hex = oid_to_hex(current_bad_oid);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
fprintf(stderr, "The merge base %s is bad.\n"
@@ -750,7 +750,7 @@ static void handle_bad_merge_base(void)
static void handle_skipped_merge_base(const unsigned char *mb)
{
char *mb_hex = sha1_to_hex(mb);
- char *bad_hex = sha1_to_hex(current_bad_sha1);
+ char *bad_hex = sha1_to_hex(current_bad_oid->hash);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
warning("the merge base between %s and [%s] "
@@ -781,7 +781,7 @@ static void check_merge_bases(int no_checkout)
for (; result; result = result->next) {
const unsigned char *mb = result->item->object.sha1;
- if (!hashcmp(mb, current_bad_sha1)) {
+ if (!hashcmp(mb, current_bad_oid->hash)) {
handle_bad_merge_base();
} else if (0 <= sha1_array_lookup(&good_revs, mb)) {
continue;
@@ -838,7 +838,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
struct stat st;
int fd;
- if (!current_bad_sha1)
+ if (!current_bad_oid)
die("a bad revision is needed");
/* Check if file BISECT_ANCESTORS_OK exists. */
@@ -903,7 +903,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
struct commit_list *tried;
int reaches = 0, all = 0, nr, steps;
const unsigned char *bisect_rev;
- char bisect_rev_hex[41];
+ char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];
if (read_bisect_refs())
die("reading bisect refs failed");
@@ -927,7 +927,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
exit_if_skipped_commits(tried, NULL);
printf("%s was both good and bad\n",
- sha1_to_hex(current_bad_sha1));
+ oid_to_hex(current_bad_oid));
exit(1);
}
@@ -938,10 +938,10 @@ int bisect_next_all(const char *prefix, int no_checkout)
}
bisect_rev = revs.commits->item->object.sha1;
- memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), 41);
+ memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
- if (!hashcmp(bisect_rev, current_bad_sha1)) {
- exit_if_skipped_commits(tried, current_bad_sha1);
+ if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
+ exit_if_skipped_commits(tried, current_bad_oid);
printf("%s is the first bad commit\n", bisect_rev_hex);
show_diff_tree(prefix, revs.commits->item);
/* This means the bisection process succeeded. */
diff --git a/builtin/add.c b/builtin/add.c
index 3390933..df5135b 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -63,6 +63,7 @@ static void update_callback(struct diff_queue_struct *q,
switch (fix_unmerged_status(p, data)) {
default:
die(_("unexpected diff status %c"), p->status);
+ case DIFF_STATUS_ADDED:
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
if (add_file_to_index(&the_index, path, data->flags)) {
@@ -208,7 +209,8 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
if (run_diff_files(&rev, 0))
die(_("Could not write patch"));
- launch_editor(file, NULL, NULL);
+ if (launch_editor(file, NULL, NULL))
+ die(_("editing patch failed"));
if (stat(file, &st))
die_errno(_("Could not stat '%s'"), file);
diff --git a/builtin/apply.c b/builtin/apply.c
index 0769b09..146be97 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -208,7 +208,7 @@ struct patch {
struct patch *next;
/* three-way fallback result */
- unsigned char threeway_stage[3][20];
+ struct object_id threeway_stage[3];
};
static void free_fragment_list(struct fragment *list)
@@ -3426,11 +3426,11 @@ static int try_threeway(struct image *image, struct patch *patch,
if (status) {
patch->conflicted_threeway = 1;
if (patch->is_new)
- hashclr(patch->threeway_stage[0]);
+ oidclr(&patch->threeway_stage[0]);
else
- hashcpy(patch->threeway_stage[0], pre_sha1);
- hashcpy(patch->threeway_stage[1], our_sha1);
- hashcpy(patch->threeway_stage[2], post_sha1);
+ hashcpy(patch->threeway_stage[0].hash, pre_sha1);
+ hashcpy(patch->threeway_stage[1].hash, our_sha1);
+ hashcpy(patch->threeway_stage[2].hash, post_sha1);
fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
} else {
fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
@@ -4186,14 +4186,14 @@ static void add_conflicted_stages_file(struct patch *patch)
remove_file_from_cache(patch->new_name);
for (stage = 1; stage < 4; stage++) {
- if (is_null_sha1(patch->threeway_stage[stage - 1]))
+ if (is_null_oid(&patch->threeway_stage[stage - 1]))
continue;
ce = xcalloc(1, ce_size);
memcpy(ce->name, patch->new_name, namelen);
ce->ce_mode = create_ce_mode(mode);
ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = namelen;
- hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
+ hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash);
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
die(_("unable to add cache entry for %s"), patch->new_name);
}
diff --git a/builtin/blame.c b/builtin/blame.c
index 06484c2..8d70623 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -2348,6 +2348,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
if (strbuf_read(&buf, 0, 0) < 0)
die_errno("failed to read from stdin");
}
+ convert_to_git(path, buf.buf, buf.len, &buf, 0);
origin->file.ptr = buf.buf;
origin->file.size = buf.len;
pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
diff --git a/builtin/branch.c b/builtin/branch.c
index 1d15037..9cbab18 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -242,7 +242,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
sha1, &flags);
if (!target) {
error(remote_branch
- ? _("remote branch '%s' not found.")
+ ? _("remote-tracking branch '%s' not found.")
: _("branch '%s' not found."), bname.buf);
ret = 1;
continue;
@@ -257,7 +257,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
if (delete_ref(name, sha1, REF_NODEREF)) {
error(remote_branch
- ? _("Error deleting remote branch '%s'")
+ ? _("Error deleting remote-tracking branch '%s'")
: _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
@@ -265,7 +265,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
}
if (!quiet) {
printf(remote_branch
- ? _("Deleted remote branch %s (was %s).\n")
+ ? _("Deleted remote-tracking branch %s (was %s).\n")
: _("Deleted branch %s (was %s).\n"),
bname.buf,
(flags & REF_ISBROKEN) ? "broken"
@@ -771,7 +771,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION";
static int edit_branch_description(const char *branch_name)
{
- FILE *fp;
int status;
struct strbuf buf = STRBUF_INIT;
struct strbuf name = STRBUF_INIT;
@@ -784,8 +783,7 @@ static int edit_branch_description(const char *branch_name)
" %s\n"
"Lines starting with '%c' will be stripped.\n",
branch_name, comment_line_char);
- fp = fopen(git_path(edit_description), "w");
- if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+ if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
strbuf_release(&buf);
return error(_("could not write branch description template: %s"),
strerror(errno));
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 92a8a60..4883a43 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -42,6 +42,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
if (!strcmp(cmd, "verify")) {
close(bundle_fd);
+ if (argc != 1) {
+ usage(builtin_bundle_usage);
+ return 1;
+ }
if (verify_bundle(&header, 1))
return 1;
fprintf(stderr, _("%s is okay\n"), bundle_file);
@@ -52,6 +56,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
return !!list_bundle_refs(&header, argc, argv);
}
if (!strcmp(cmd, "create")) {
+ if (argc < 2) {
+ usage(builtin_bundle_usage);
+ return 1;
+ }
if (!startup_info->have_repository)
die(_("Need a repository to create a bundle."));
return !!create_bundle(&header, bundle_file, argc, argv);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index df99df4..ecb4888 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -9,13 +9,20 @@
#include "userdiff.h"
#include "streaming.h"
-static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
+static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
+ int unknown_type)
{
unsigned char sha1[20];
enum object_type type;
char *buf;
unsigned long size;
struct object_context obj_context;
+ struct object_info oi = {NULL};
+ struct strbuf sb = STRBUF_INIT;
+ unsigned flags = LOOKUP_REPLACE_OBJECT;
+
+ if (unknown_type)
+ flags |= LOOKUP_UNKNOWN_OBJECT;
if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
die("Not a valid object name %s", obj_name);
@@ -23,20 +30,22 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
buf = NULL;
switch (opt) {
case 't':
- type = sha1_object_info(sha1, NULL);
- if (type > 0) {
- printf("%s\n", typename(type));
+ oi.typename = &sb;
+ if (sha1_object_info_extended(sha1, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ if (sb.len) {
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
return 0;
}
break;
case 's':
- type = sha1_object_info(sha1, &size);
- if (type > 0) {
- printf("%lu\n", size);
- return 0;
- }
- break;
+ oi.sizep = &size;
+ if (sha1_object_info_extended(sha1, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ printf("%lu\n", size);
+ return 0;
case 'e':
return !has_sha1_file(sha1);
@@ -323,7 +332,7 @@ static int batch_objects(struct batch_options *opt)
}
static const char * const cat_file_usage[] = {
- N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"),
+ N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"),
N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
NULL
};
@@ -359,16 +368,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
+ int unknown_type = 0;
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
- OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'),
- OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'),
- OPT_SET_INT('e', NULL, &opt,
+ OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
+ OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
+ OPT_CMDMODE('e', NULL, &opt,
N_("exit with zero when there's no error"), 'e'),
- OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
- OPT_SET_INT(0, "textconv", &opt,
+ OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
+ OPT_CMDMODE(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
+ OPT_BOOL( 0, "allow-unknown-type", &unknown_type,
+ N_("allow -s and -t to work with broken/corrupt objects")),
{ OPTION_CALLBACK, 0, "batch", &batch, "format",
N_("show info and content of objects fed from the standard input"),
PARSE_OPT_OPTARG, batch_option_callback },
@@ -380,9 +392,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
git_config(git_cat_file_config, NULL);
- if (argc != 3 && argc != 2)
- usage_with_options(cat_file_usage, options);
-
argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
if (opt) {
@@ -405,5 +414,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
if (batch.enabled)
return batch_objects(&batch);
- return cat_one_file(opt, exp_type, obj_name);
+ if (unknown_type && opt != 't' && opt != 's')
+ die("git cat-file --allow-unknown-type: use with -s or -t");
+ return cat_one_file(opt, exp_type, obj_name, unknown_type);
}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 9ca2da1..8028c37 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -241,7 +241,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
- const char *p;
+ char *p;
if (all)
die("git checkout-index: don't mix '--all' and explicit filenames");
@@ -249,8 +249,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
die("git checkout-index: don't mix '--stdin' and explicit filenames");
p = prefix_path(prefix, prefix_length, arg);
checkout_file(p, prefix);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
+ free(p);
}
if (read_from_stdin) {
@@ -260,7 +259,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
die("git checkout-index: don't mix '--all' and '--stdin'");
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
+ char *p;
if (line_termination && buf.buf[0] == '"') {
strbuf_reset(&nbuf);
if (unquote_c_style(&nbuf, buf.buf, NULL))
@@ -269,8 +268,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
}
p = prefix_path(prefix, prefix_length, buf.buf);
checkout_file(p, prefix);
- if (p < buf.buf || p > buf.buf + buf.len)
- free((char *)p);
+ free(p);
}
strbuf_release(&nbuf);
strbuf_release(&buf);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3e141fc..2f92328 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -20,6 +20,7 @@
#include "resolve-undo.h"
#include "submodule.h"
#include "argv-array.h"
+#include "sigchain.h"
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
@@ -36,6 +37,7 @@ struct checkout_opts {
int writeout_stage;
int overwrite_ignore;
int ignore_skipworktree;
+ int ignore_other_worktrees;
const char *new_branch;
const char *new_branch_force;
@@ -48,6 +50,10 @@ struct checkout_opts {
const char *prefix;
struct pathspec pathspec;
struct tree *source_tree;
+
+ const char *new_worktree;
+ const char **saved_argv;
+ int new_worktree_mode;
};
static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -267,6 +273,9 @@ static int checkout_paths(const struct checkout_opts *opts,
die(_("Cannot update paths and switch to branch '%s' at the same time."),
opts->new_branch);
+ if (opts->new_worktree)
+ die(_("'%s' cannot be used with updating paths"), "--to");
+
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
&opts->pathspec);
@@ -441,6 +450,11 @@ struct branch_info {
const char *name; /* The short name used */
const char *path; /* The full name of a real branch */
struct commit *commit; /* The named commit */
+ /*
+ * if not null the branch is detached because it's already
+ * checked out in this checkout
+ */
+ char *checkout;
};
static void setup_branch_path(struct branch_info *branch)
@@ -502,7 +516,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.dir->flags |= DIR_SHOW_IGNORED;
setup_standard_excludes(topts.dir);
}
- tree = parse_tree_indirect(old->commit ?
+ tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
old->commit->object.sha1 :
EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -606,18 +620,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
if (opts->new_orphan_branch) {
if (opts->new_branch_log && !log_all_ref_updates) {
int temp;
- char log_file[PATH_MAX];
- char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+ struct strbuf log_file = STRBUF_INIT;
+ int ret;
+ const char *ref_name;
+ ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
temp = log_all_ref_updates;
log_all_ref_updates = 1;
- if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+ ret = log_ref_setup(ref_name, &log_file);
+ log_all_ref_updates = temp;
+ strbuf_release(&log_file);
+ if (ret) {
fprintf(stderr, _("Can not do reflog for '%s'\n"),
opts->new_orphan_branch);
- log_all_ref_updates = temp;
return;
}
- log_all_ref_updates = temp;
}
}
else
@@ -743,10 +760,17 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
if (advice_detached_head)
fprintf(stderr,
- _(
+ Q_(
+ /* The singular version */
+ "If you want to keep it by creating a new branch, "
+ "this may be a good time\nto do so with:\n\n"
+ " git branch <new-branch-name> %s\n\n",
+ /* The plural version */
"If you want to keep them by creating a new branch, "
"this may be a good time\nto do so with:\n\n"
- " git branch <new-branch-name> %s\n\n"),
+ " git branch <new-branch-name> %s\n\n",
+ /* Give ngettext() the count */
+ lost),
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
}
@@ -815,7 +839,8 @@ static int switch_branches(const struct checkout_opts *opts,
return ret;
}
- if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+ if (!opts->quiet && !old.path && old.commit &&
+ new->commit != old.commit && !opts->new_worktree_mode)
orphaned_commit_warning(old.commit, new->commit);
update_refs_for_switch(opts, &old, new);
@@ -825,6 +850,138 @@ static int switch_branches(const struct checkout_opts *opts,
return ret || writeout_error;
}
+static char *junk_work_tree;
+static char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (!is_junk || getpid() != junk_pid)
+ return;
+ if (junk_git_dir) {
+ strbuf_addstr(&sb, junk_git_dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_reset(&sb);
+ }
+ if (junk_work_tree) {
+ strbuf_addstr(&sb, junk_work_tree);
+ remove_dir_recursively(&sb, 0);
+ }
+ strbuf_release(&sb);
+}
+
+static void remove_junk_on_signal(int signo)
+{
+ remove_junk();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+ struct branch_info *new)
+{
+ struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ const char *path = opts->new_worktree, *name;
+ struct stat st;
+ struct child_process cp;
+ int counter = 0, len, ret;
+
+ if (!new->commit)
+ die(_("no branch specified"));
+ if (file_exists(path) && !is_empty_dir(path))
+ die(_("'%s' already exists"), path);
+
+ len = strlen(path);
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+
+ for (name = path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
+ strbuf_addstr(&sb_repo,
+ git_path("worktrees/%.*s", (int)(path + len - name), name));
+ len = sb_repo.len;
+ if (safe_create_leading_directories_const(sb_repo.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_repo.buf);
+ while (!stat(sb_repo.buf, &st)) {
+ counter++;
+ strbuf_setlen(&sb_repo, len);
+ strbuf_addf(&sb_repo, "%d", counter);
+ }
+ name = strrchr(sb_repo.buf, '/') + 1;
+
+ junk_pid = getpid();
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
+ if (mkdir(sb_repo.buf, 0777))
+ die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+ junk_git_dir = xstrdup(sb_repo.buf);
+ is_junk = 1;
+
+ /*
+ * lock the incomplete repo so prune won't delete it, unlock
+ * after the preparation is over.
+ */
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ write_file(sb.buf, 1, "initializing\n");
+
+ strbuf_addf(&sb_git, "%s/.git", path);
+ if (safe_create_leading_directories_const(sb_git.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_git.buf);
+ junk_work_tree = xstrdup(path);
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+ write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
+ write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
+ real_path(get_git_common_dir()), name);
+ /*
+ * This is to keep resolve_ref() happy. We need a valid HEAD
+ * or is_git_directory() will reject the directory. Any valid
+ * value would do because this value will be ignored and
+ * replaced at the next (real) checkout.
+ */
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+ write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+ write_file(sb.buf, 1, "../..\n");
+
+ if (!opts->quiet)
+ fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+ setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+ setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+ setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = opts->saved_argv;
+ ret = run_command(&cp);
+ if (!ret) {
+ is_junk = 0;
+ free(junk_work_tree);
+ free(junk_git_dir);
+ junk_work_tree = NULL;
+ junk_git_dir = NULL;
+ }
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ unlink_or_warn(sb.buf);
+ strbuf_release(&sb);
+ strbuf_release(&sb_repo);
+ strbuf_release(&sb_git);
+ return ret;
+}
+
static int git_checkout_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -880,13 +1037,80 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
return NULL;
}
+static void check_linked_checkout(struct branch_info *new, const char *id)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ struct strbuf gitdir = STRBUF_INIT;
+ const char *start, *end;
+
+ if (id)
+ strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
+ else
+ strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+
+ if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
+ !skip_prefix(sb.buf, "ref:", &start))
+ goto done;
+ while (isspace(*start))
+ start++;
+ end = start;
+ while (*end && !isspace(*end))
+ end++;
+ if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
+ goto done;
+ if (id) {
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
+ if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
+ goto done;
+ strbuf_rtrim(&gitdir);
+ } else
+ strbuf_addstr(&gitdir, get_git_common_dir());
+ die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
+done:
+ strbuf_release(&path);
+ strbuf_release(&sb);
+ strbuf_release(&gitdir);
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir;
+ struct dirent *d;
+
+ strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
+ if ((dir = opendir(path.buf)) == NULL) {
+ strbuf_release(&path);
+ return;
+ }
+
+ /*
+ * $GIT_COMMON_DIR/HEAD is practically outside
+ * $GIT_DIR so resolve_ref_unsafe() won't work (it
+ * uses git_path). Parse the ref ourselves.
+ */
+ check_linked_checkout(new, NULL);
+
+ while ((d = readdir(dir)) != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ check_linked_checkout(new, d->d_name);
+ }
+ strbuf_release(&path);
+ closedir(dir);
+}
+
static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new,
- struct tree **source_tree,
- unsigned char rev[20],
- const char **new_branch)
+ struct checkout_opts *opts,
+ unsigned char rev[20])
{
+ struct tree **source_tree = &opts->source_tree;
+ const char **new_branch = &opts->new_branch;
+ int force_detach = opts->force_detach;
int argcount = 0;
unsigned char branch_rev[20];
const char *arg;
@@ -1007,6 +1231,17 @@ static int parse_branchname_arg(int argc, const char **argv,
else
new->path = NULL; /* not an existing branch */
+ if (new->path && !force_detach && !*new_branch) {
+ unsigned char sha1[20];
+ int flag;
+ char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
+ if (head_ref &&
+ (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
+ !opts->ignore_other_worktrees)
+ check_linked_checkouts(new);
+ free(head_ref);
+ }
+
new->commit = lookup_commit_reference_gently(rev, 1);
if (!new->commit) {
/* not a commit */
@@ -1086,6 +1321,9 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("Cannot switch branch to a non-commit '%s'"),
new->name);
+ if (opts->new_worktree)
+ return prepare_linked_checkout(opts, new);
+
if (!new->commit && opts->new_branch) {
unsigned char rev[20];
int flag;
@@ -1128,6 +1366,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
N_("do not limit pathspecs to sparse entries only")),
OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
N_("second guess 'git checkout <no-such-branch>'")),
+ OPT_FILENAME(0, "to", &opts.new_worktree,
+ N_("check a branch out in a separate working directory")),
+ OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+ N_("do not check if another worktree is holding the given ref")),
OPT_END(),
};
@@ -1136,6 +1378,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
opts.overwrite_ignore = 1;
opts.prefix = prefix;
+ opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
+ memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
gitmodules_config();
git_config(git_checkout_config, &opts);
@@ -1144,6 +1389,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
+ /* recursive execution from checkout_new_worktree() */
+ opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
+ if (opts.new_worktree_mode)
+ opts.new_worktree = NULL;
+
+ if (!opts.new_worktree)
+ setup_work_tree();
+
if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -1197,8 +1450,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
opts.track == BRANCH_TRACK_UNSPECIFIED &&
!opts.new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
- &new, &opts.source_tree,
- rev, &opts.new_branch);
+ &new, &opts, rev);
argv += n;
argc -= n;
}
diff --git a/builtin/clone.c b/builtin/clone.c
index 53a2e5a..166a645 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -293,16 +293,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
struct strbuf line = STRBUF_INIT;
while (strbuf_getline(&line, in, '\n') != EOF) {
- char *abs_path, abs_buf[PATH_MAX];
+ char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
add_to_alternates_file(line.buf);
continue;
}
- abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
- normalize_path_copy(abs_buf, abs_path);
- add_to_alternates_file(abs_buf);
+ abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
+ normalize_path_copy(abs_path, abs_path);
+ add_to_alternates_file(abs_path);
+ free(abs_path);
}
strbuf_release(&line);
fclose(in);
diff --git a/builtin/commit.c b/builtin/commit.c
index da79ac4..d6515a2 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -170,7 +170,7 @@ static void determine_whence(struct wt_status *s)
whence = FROM_MERGE;
else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
whence = FROM_CHERRY_PICK;
- if (file_exists(git_path("sequencer")))
+ if (file_exists(git_path(SEQ_DIR)))
sequencer_in_use = 1;
}
else
@@ -1398,12 +1398,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
static const char *implicit_ident_advice(void)
{
- char *user_config = NULL;
- char *xdg_config = NULL;
- int config_exists;
+ char *user_config = expand_user_path("~/.gitconfig");
+ char *xdg_config = xdg_config_home("config");
+ int config_exists = file_exists(user_config) || file_exists(xdg_config);
- home_config_paths(&user_config, &xdg_config, "config");
- config_exists = file_exists(user_config) || file_exists(xdg_config);
free(user_config);
free(xdg_config);
diff --git a/builtin/config.c b/builtin/config.c
index d32c532..7188405 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -193,7 +193,7 @@ static int get_value(const char *key_, const char *regex_)
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
- fprintf(stderr, "Invalid key pattern: %s\n", key_);
+ error("invalid key pattern: %s", key_);
free(key_regexp);
key_regexp = NULL;
ret = CONFIG_INVALID_PATTERN;
@@ -214,7 +214,7 @@ static int get_value(const char *key_, const char *regex_)
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
- fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ error("invalid pattern: %s", regex_);
free(regexp);
regexp = NULL;
ret = CONFIG_INVALID_PATTERN;
@@ -455,9 +455,9 @@ static char *default_user_config(void)
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf,
_("# This is Git's per-user configuration file.\n"
- "[core]\n"
+ "[user]\n"
"# Please adapt and uncomment the following lines:\n"
- "# user = %s\n"
+ "# name = %s\n"
"# email = %s\n"),
ident_default_name(),
ident_default_email());
@@ -488,10 +488,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
- char *user_config = NULL;
- char *xdg_config = NULL;
-
- home_config_paths(&user_config, &xdg_config, "config");
+ char *user_config = expand_user_path("~/.gitconfig");
+ char *xdg_config = xdg_config_home("config");
if (!user_config)
/*
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index e47ef0b..ad0c799 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -70,8 +70,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
/* we do not take arguments other than flags for now */
if (argc)
usage_with_options(count_objects_usage, opts);
- if (verbose)
+ if (verbose) {
report_garbage = real_report_garbage;
+ report_linked_checkout_garbage();
+ }
for_each_loose_file_in_objdir(get_object_directory(),
count_loose, count_cruft, NULL, NULL);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index f951265..7910419 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -588,7 +588,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
struct strbuf note = STRBUF_INIT;
const char *what, *kind;
struct ref *rm;
- char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+ char *url;
+ const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
int want_status;
fp = fopen(filename, "a");
@@ -822,7 +823,7 @@ static void check_not_current_branch(struct ref *ref_map)
static int truncate_fetch_head(void)
{
- char *filename = git_path("FETCH_HEAD");
+ const char *filename = git_path("FETCH_HEAD");
FILE *fp = fopen(filename, "w");
if (!fp)
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 1d962dc..05f4c26 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -223,16 +223,14 @@ static void add_branch_desc(struct strbuf *out, const char *name)
#define util_as_integral(elem) ((intptr_t)((elem)->util))
-static void record_person(int which, struct string_list *people,
- struct commit *commit)
+static void record_person_from_buf(int which, struct string_list *people,
+ const char *buffer)
{
- const char *buffer;
char *name_buf, *name, *name_end;
struct string_list_item *elem;
const char *field;
field = (which == 'a') ? "\nauthor " : "\ncommitter ";
- buffer = get_commit_buffer(commit, NULL);
name = strstr(buffer, field);
if (!name)
return;
@@ -245,7 +243,6 @@ static void record_person(int which, struct string_list *people,
if (name_end < name)
return;
name_buf = xmemdupz(name, name_end - name + 1);
- unuse_commit_buffer(commit, buffer);
elem = string_list_lookup(people, name_buf);
if (!elem) {
@@ -256,6 +253,15 @@ static void record_person(int which, struct string_list *people,
free(name_buf);
}
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ const char *buffer = get_commit_buffer(commit, NULL);
+ record_person_from_buf(which, people, buffer);
+ unuse_commit_buffer(commit, buffer);
+}
+
static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
{
const struct string_list_item *a = a_, *b = b_;
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 0c75786..4783896 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj)
printf("dangling %s %s\n", typename(obj->type),
sha1_to_hex(obj->sha1));
if (write_lost_and_found) {
- char *filename = git_path("lost-found/%s/%s",
+ const char *filename = git_path("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
sha1_to_hex(obj->sha1));
FILE *f;
- if (safe_create_leading_directories(filename)) {
+ if (safe_create_leading_directories_const(filename)) {
error("Could not create lost-found");
return;
}
diff --git a/builtin/gc.c b/builtin/gc.c
index 5c634af..36fe333 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
static int detach_auto = 1;
static const char *prune_expire = "2.weeks.ago";
+static const char *prune_worktrees_expire = "3.months.ago";
static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
static struct argv_array reflog = ARGV_ARRAY_INIT;
static struct argv_array repack = ARGV_ARRAY_INIT;
static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
static struct argv_array rerere = ARGV_ARRAY_INIT;
static char *pidfile;
@@ -55,6 +57,17 @@ static void remove_pidfile_on_signal(int signo)
raise(signo);
}
+static void git_config_date_string(const char *key, const char **output)
+{
+ if (git_config_get_string_const(key, output))
+ return;
+ if (strcmp(*output, "now")) {
+ unsigned long now = approxidate("now");
+ if (approxidate(*output) >= now)
+ git_die_config(key, _("Invalid %s: '%s'"), key, *output);
+ }
+}
+
static void gc_config(void)
{
const char *value;
@@ -71,16 +84,8 @@ static void gc_config(void)
git_config_get_int("gc.auto", &gc_auto_threshold);
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
git_config_get_bool("gc.autodetach", &detach_auto);
-
- if (!git_config_get_string_const("gc.pruneexpire", &prune_expire)) {
- if (strcmp(prune_expire, "now")) {
- unsigned long now = approxidate("now");
- if (approxidate(prune_expire) >= now) {
- git_die_config("gc.pruneexpire", _("Invalid gc.pruneexpire: '%s'"),
- prune_expire);
- }
- }
- }
+ git_config_date_string("gc.pruneexpire", &prune_expire);
+ git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire);
git_config(git_default_config, NULL);
}
@@ -287,7 +292,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
- argv_array_pushl(&prune, "prune", "--expire", NULL );
+ argv_array_pushl(&prune, "prune", "--expire", NULL);
+ argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
argv_array_pushl(&rerere, "rerere", "gc", NULL);
gc_config();
@@ -357,6 +363,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
return error(FAILED_RUN, prune.argv[0]);
}
+ if (prune_worktrees_expire) {
+ argv_array_push(&prune_worktrees, prune_worktrees_expire);
+ if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, prune_worktrees.argv[0]);
+ }
+
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
return error(FAILED_RUN, rerere.argv[0]);
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 207b90c..07fef3c 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -22,10 +22,8 @@ static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigne
if (strbuf_read(&buf, fd, 4096) < 0)
ret = -1;
- else if (flags & HASH_WRITE_OBJECT)
- ret = write_sha1_file(buf.buf, buf.len, type, sha1);
else
- ret = hash_sha1_file(buf.buf, buf.len, type, sha1);
+ ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags);
strbuf_release(&buf);
return ret;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index cf654df..7ea2020 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -18,16 +18,14 @@ static const char index_pack_usage[] =
struct object_entry {
struct pack_idx_entry idx;
unsigned long size;
- unsigned int hdr_size;
- enum object_type type;
- enum object_type real_type;
- unsigned delta_depth;
- int base_object_no;
+ unsigned char hdr_size;
+ signed char type;
+ signed char real_type;
};
-union delta_base {
- unsigned char sha1[20];
- off_t offset;
+struct object_stat {
+ unsigned delta_depth;
+ int base_object_no;
};
struct base_data {
@@ -49,25 +47,28 @@ struct thread_local {
int pack_fd;
};
-/*
- * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
- * to memcmp() only the first 20 bytes.
- */
-#define UNION_BASE_SZ 20
-
#define FLAG_LINK (1u<<20)
#define FLAG_CHECKED (1u<<21)
-struct delta_entry {
- union delta_base base;
+struct ofs_delta_entry {
+ off_t offset;
+ int obj_no;
+};
+
+struct ref_delta_entry {
+ unsigned char sha1[20];
int obj_no;
};
static struct object_entry *objects;
-static struct delta_entry *deltas;
+static struct object_stat *obj_stat;
+static struct ofs_delta_entry *ofs_deltas;
+static struct ref_delta_entry *ref_deltas;
static struct thread_local nothread_data;
static int nr_objects;
-static int nr_deltas;
+static int nr_ofs_deltas;
+static int nr_ref_deltas;
+static int ref_deltas_alloc;
static int nr_resolved_deltas;
static int nr_threads;
@@ -476,7 +477,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
}
static void *unpack_raw_entry(struct object_entry *obj,
- union delta_base *delta_base,
+ off_t *ofs_offset,
+ unsigned char *ref_sha1,
unsigned char *sha1)
{
unsigned char *p;
@@ -505,11 +507,10 @@ static void *unpack_raw_entry(struct object_entry *obj,
switch (obj->type) {
case OBJ_REF_DELTA:
- hashcpy(delta_base->sha1, fill(20));
+ hashcpy(ref_sha1, fill(20));
use(20);
break;
case OBJ_OFS_DELTA:
- memset(delta_base, 0, sizeof(*delta_base));
p = fill(1);
c = *p;
use(1);
@@ -523,8 +524,8 @@ static void *unpack_raw_entry(struct object_entry *obj,
use(1);
base_offset = (base_offset << 7) + (c & 127);
}
- delta_base->offset = obj->idx.offset - base_offset;
- if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
+ *ofs_offset = obj->idx.offset - base_offset;
+ if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset)
bad_object(obj->idx.offset, _("delta base offset is out of bound"));
break;
case OBJ_COMMIT:
@@ -608,55 +609,108 @@ static void *get_data_from_pack(struct object_entry *obj)
return unpack_data(obj, NULL, NULL);
}
-static int compare_delta_bases(const union delta_base *base1,
- const union delta_base *base2,
- enum object_type type1,
- enum object_type type2)
+static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
+ enum object_type type1,
+ enum object_type type2)
{
int cmp = type1 - type2;
if (cmp)
return cmp;
- return memcmp(base1, base2, UNION_BASE_SZ);
+ return offset1 - offset2;
}
-static int find_delta(const union delta_base *base, enum object_type type)
+static int find_ofs_delta(const off_t offset, enum object_type type)
{
- int first = 0, last = nr_deltas;
-
- while (first < last) {
- int next = (first + last) / 2;
- struct delta_entry *delta = &deltas[next];
- int cmp;
-
- cmp = compare_delta_bases(base, &delta->base,
- type, objects[delta->obj_no].type);
- if (!cmp)
- return next;
- if (cmp < 0) {
- last = next;
- continue;
- }
- first = next+1;
- }
- return -first-1;
+ int first = 0, last = nr_ofs_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct ofs_delta_entry *delta = &ofs_deltas[next];
+ int cmp;
+
+ cmp = compare_ofs_delta_bases(offset, delta->offset,
+ type, objects[delta->obj_no].type);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
}
-static void find_delta_children(const union delta_base *base,
- int *first_index, int *last_index,
- enum object_type type)
+static void find_ofs_delta_children(off_t offset,
+ int *first_index, int *last_index,
+ enum object_type type)
{
- int first = find_delta(base, type);
+ int first = find_ofs_delta(offset, type);
int last = first;
- int end = nr_deltas - 1;
+ int end = nr_ofs_deltas - 1;
if (first < 0) {
*first_index = 0;
*last_index = -1;
return;
}
- while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
+ while (first > 0 && ofs_deltas[first - 1].offset == offset)
--first;
- while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
+ while (last < end && ofs_deltas[last + 1].offset == offset)
+ ++last;
+ *first_index = first;
+ *last_index = last;
+}
+
+static int compare_ref_delta_bases(const unsigned char *sha1,
+ const unsigned char *sha2,
+ enum object_type type1,
+ enum object_type type2)
+{
+ int cmp = type1 - type2;
+ if (cmp)
+ return cmp;
+ return hashcmp(sha1, sha2);
+}
+
+static int find_ref_delta(const unsigned char *sha1, enum object_type type)
+{
+ int first = 0, last = nr_ref_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct ref_delta_entry *delta = &ref_deltas[next];
+ int cmp;
+
+ cmp = compare_ref_delta_bases(sha1, delta->sha1,
+ type, objects[delta->obj_no].type);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+static void find_ref_delta_children(const unsigned char *sha1,
+ int *first_index, int *last_index,
+ enum object_type type)
+{
+ int first = find_ref_delta(sha1, type);
+ int last = first;
+ int end = nr_ref_deltas - 1;
+
+ if (first < 0) {
+ *first_index = 0;
+ *last_index = -1;
+ return;
+ }
+ while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1))
+ --first;
+ while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1))
++last;
*first_index = first;
*last_index = last;
@@ -873,13 +927,15 @@ static void resolve_delta(struct object_entry *delta_obj,
void *base_data, *delta_data;
if (show_stat) {
- delta_obj->delta_depth = base->obj->delta_depth + 1;
+ int i = delta_obj - objects;
+ int j = base->obj - objects;
+ obj_stat[i].delta_depth = obj_stat[j].delta_depth + 1;
deepest_delta_lock();
- if (deepest_delta < delta_obj->delta_depth)
- deepest_delta = delta_obj->delta_depth;
+ if (deepest_delta < obj_stat[i].delta_depth)
+ deepest_delta = obj_stat[i].delta_depth;
deepest_delta_unlock();
+ obj_stat[i].base_object_no = j;
}
- delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
result->obj = delta_obj;
@@ -902,7 +958,7 @@ static void resolve_delta(struct object_entry *delta_obj,
* "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
* and return false.
*/
-static int compare_and_swap_type(enum object_type *type,
+static int compare_and_swap_type(signed char *type,
enum object_type want,
enum object_type set)
{
@@ -921,16 +977,13 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
struct base_data *prev_base)
{
if (base->ref_last == -1 && base->ofs_last == -1) {
- union delta_base base_spec;
+ find_ref_delta_children(base->obj->idx.sha1,
+ &base->ref_first, &base->ref_last,
+ OBJ_REF_DELTA);
- hashcpy(base_spec.sha1, base->obj->idx.sha1);
- find_delta_children(&base_spec,
- &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
-
- memset(&base_spec, 0, sizeof(base_spec));
- base_spec.offset = base->obj->idx.offset;
- find_delta_children(&base_spec,
- &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
+ find_ofs_delta_children(base->obj->idx.offset,
+ &base->ofs_first, &base->ofs_last,
+ OBJ_OFS_DELTA);
if (base->ref_last == -1 && base->ofs_last == -1) {
free(base->data);
@@ -941,7 +994,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
}
if (base->ref_first <= base->ref_last) {
- struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+ struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no;
struct base_data *result = alloc_base_data();
if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
@@ -957,7 +1010,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
}
if (base->ofs_first <= base->ofs_last) {
- struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+ struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
struct base_data *result = alloc_base_data();
assert(child->real_type == OBJ_OFS_DELTA);
@@ -993,15 +1046,20 @@ static void find_unresolved_deltas(struct base_data *base)
}
}
-static int compare_delta_entry(const void *a, const void *b)
+static int compare_ofs_delta_entry(const void *a, const void *b)
{
- const struct delta_entry *delta_a = a;
- const struct delta_entry *delta_b = b;
+ const struct ofs_delta_entry *delta_a = a;
+ const struct ofs_delta_entry *delta_b = b;
- /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
- return compare_delta_bases(&delta_a->base, &delta_b->base,
- objects[delta_a->obj_no].type,
- objects[delta_b->obj_no].type);
+ return delta_a->offset - delta_b->offset;
+}
+
+static int compare_ref_delta_entry(const void *a, const void *b)
+{
+ const struct ref_delta_entry *delta_a = a;
+ const struct ref_delta_entry *delta_b = b;
+
+ return hashcmp(delta_a->sha1, delta_b->sha1);
}
static void resolve_base(struct object_entry *obj)
@@ -1047,7 +1105,8 @@ static void *threaded_second_pass(void *data)
static void parse_pack_objects(unsigned char *sha1)
{
int i, nr_delays = 0;
- struct delta_entry *delta = deltas;
+ struct ofs_delta_entry *ofs_delta = ofs_deltas;
+ unsigned char ref_delta_sha1[20];
struct stat st;
if (verbose)
@@ -1056,12 +1115,18 @@ static void parse_pack_objects(unsigned char *sha1)
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
+ void *data = unpack_raw_entry(obj, &ofs_delta->offset,
+ ref_delta_sha1, obj->idx.sha1);
obj->real_type = obj->type;
- if (is_delta_type(obj->type)) {
- nr_deltas++;
- delta->obj_no = i;
- delta++;
+ if (obj->type == OBJ_OFS_DELTA) {
+ nr_ofs_deltas++;
+ ofs_delta->obj_no = i;
+ ofs_delta++;
+ } else if (obj->type == OBJ_REF_DELTA) {
+ ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc);
+ hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1);
+ ref_deltas[nr_ref_deltas].obj_no = i;
+ nr_ref_deltas++;
} else if (!data) {
/* large blobs, check later */
obj->real_type = OBJ_BAD;
@@ -1112,15 +1177,18 @@ static void resolve_deltas(void)
{
int i;
- if (!nr_deltas)
+ if (!nr_ofs_deltas && !nr_ref_deltas)
return;
/* Sort deltas by base SHA1/offset for fast searching */
- qsort(deltas, nr_deltas, sizeof(struct delta_entry),
- compare_delta_entry);
+ qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry),
+ compare_ofs_delta_entry);
+ qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry),
+ compare_ref_delta_entry);
if (verbose)
- progress = start_progress(_("Resolving deltas"), nr_deltas);
+ progress = start_progress(_("Resolving deltas"),
+ nr_ref_deltas + nr_ofs_deltas);
#ifndef NO_PTHREADS
nr_dispatched = 0;
@@ -1158,7 +1226,7 @@ static void resolve_deltas(void)
static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
{
- if (nr_deltas == nr_resolved_deltas) {
+ if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) {
stop_progress(&progress);
/* Flush remaining pack final 20-byte SHA1. */
flush();
@@ -1169,7 +1237,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
struct sha1file *f;
unsigned char read_sha1[20], tail_sha1[20];
struct strbuf msg = STRBUF_INIT;
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas;
int nr_objects_initial = nr_objects;
if (nr_unresolved <= 0)
die(_("confusion beyond insanity"));
@@ -1191,11 +1259,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
die(_("Unexpected tail checksum for %s "
"(disk corruption?)"), curr_pack);
}
- if (nr_deltas != nr_resolved_deltas)
+ if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas)
die(Q_("pack has %d unresolved delta",
"pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas),
- nr_deltas - nr_resolved_deltas);
+ nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas),
+ nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas);
}
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
@@ -1254,14 +1322,14 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f,
static int delta_pos_compare(const void *_a, const void *_b)
{
- struct delta_entry *a = *(struct delta_entry **)_a;
- struct delta_entry *b = *(struct delta_entry **)_b;
+ struct ref_delta_entry *a = *(struct ref_delta_entry **)_a;
+ struct ref_delta_entry *b = *(struct ref_delta_entry **)_b;
return a->obj_no - b->obj_no;
}
static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
{
- struct delta_entry **sorted_by_pos;
+ struct ref_delta_entry **sorted_by_pos;
int i, n = 0;
/*
@@ -1275,28 +1343,25 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
* resolving deltas in the same order as their position in the pack.
*/
sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
- for (i = 0; i < nr_deltas; i++) {
- if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
- continue;
- sorted_by_pos[n++] = &deltas[i];
- }
+ for (i = 0; i < nr_ref_deltas; i++)
+ sorted_by_pos[n++] = &ref_deltas[i];
qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
for (i = 0; i < n; i++) {
- struct delta_entry *d = sorted_by_pos[i];
+ struct ref_delta_entry *d = sorted_by_pos[i];
enum object_type type;
struct base_data *base_obj = alloc_base_data();
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+ base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size);
if (!base_obj->data)
continue;
- if (check_sha1_signature(d->base.sha1, base_obj->data,
+ if (check_sha1_signature(d->sha1, base_obj->data,
base_obj->size, typename(type)))
- die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
- base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+ die(_("local object %s is corrupt"), sha1_to_hex(d->sha1));
+ base_obj->obj = append_obj_to_pack(f, d->sha1,
base_obj->data, base_obj->size, type);
find_unresolved_deltas(base_obj);
display_progress(progress, nr_resolved_deltas);
@@ -1488,7 +1553,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
static void show_pack_info(int stat_only)
{
- int i, baseobjects = nr_objects - nr_deltas;
+ int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas;
unsigned long *chain_histogram = NULL;
if (deepest_delta)
@@ -1498,7 +1563,7 @@ static void show_pack_info(int stat_only)
struct object_entry *obj = &objects[i];
if (is_delta_type(obj->type))
- chain_histogram[obj->delta_depth - 1]++;
+ chain_histogram[obj_stat[i].delta_depth - 1]++;
if (stat_only)
continue;
printf("%s %-6s %lu %lu %"PRIuMAX,
@@ -1507,8 +1572,8 @@ static void show_pack_info(int stat_only)
(unsigned long)(obj[1].idx.offset - obj->idx.offset),
(uintmax_t)obj->idx.offset);
if (is_delta_type(obj->type)) {
- struct object_entry *bobj = &objects[obj->base_object_no];
- printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+ struct object_entry *bobj = &objects[obj_stat[i].base_object_no];
+ printf(" %u %s", obj_stat[i].delta_depth, sha1_to_hex(bobj->idx.sha1));
}
putchar('\n');
}
@@ -1671,11 +1736,14 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
curr_pack = open_pack_file(pack_name);
parse_pack_header();
objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
- deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+ if (show_stat)
+ obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat));
+ ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
parse_pack_objects(pack_sha1);
resolve_deltas();
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
- free(deltas);
+ free(ofs_deltas);
+ free(ref_deltas);
if (strict)
foreign_nr = check_objects();
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 6723d39..4335738 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -182,6 +182,20 @@ static int git_init_db_config(const char *k, const char *v, void *cb)
return 0;
}
+/*
+ * If the git_dir is not directly inside the working tree, then git will not
+ * find it by default, and we need to set the worktree explicitly.
+ */
+static int needs_work_tree_config(const char *git_dir, const char *work_tree)
+{
+ if (!strcmp(work_tree, "/") && !strcmp(git_dir, "/.git"))
+ return 0;
+ if (skip_prefix(git_dir, work_tree, &git_dir) &&
+ !strcmp(git_dir, "/.git"))
+ return 0;
+ return 1;
+}
+
static int create_default_files(const char *template_path)
{
const char *git_dir = get_git_dir();
@@ -274,10 +288,8 @@ static int create_default_files(const char *template_path)
/* allow template config file to override the default */
if (log_all_ref_updates == -1)
git_config_set("core.logallrefupdates", "true");
- if (!starts_with(git_dir, work_tree) ||
- strcmp(git_dir + strlen(work_tree), "/.git")) {
+ if (needs_work_tree_config(git_dir, work_tree))
git_config_set("core.worktree", work_tree);
- }
}
if (!reinit) {
@@ -350,7 +362,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
static void separate_git_dir(const char *git_dir)
{
struct stat st;
- FILE *fp;
if (!stat(git_link, &st)) {
const char *src;
@@ -366,11 +377,7 @@ static void separate_git_dir(const char *git_dir)
die_errno(_("unable to move %s to %s"), src, git_dir);
}
- fp = fopen(git_link, "w");
- if (!fp)
- die(_("Could not create git link %s"), git_link);
- fprintf(fp, "gitdir: %s\n", git_dir);
- fclose(fp);
+ write_file(git_link, 1, "gitdir: %s\n", git_dir);
}
int init_db(const char *template_dir, unsigned int flags)
diff --git a/builtin/merge.c b/builtin/merge.c
index 3b0f8f9..f89f60e 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -492,8 +492,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
}
if (len) {
struct strbuf truname = STRBUF_INIT;
- strbuf_addstr(&truname, "refs/heads/");
- strbuf_addstr(&truname, remote);
+ strbuf_addf(&truname, "refs/heads/%s", remote);
strbuf_setlen(&truname, truname.len - len);
if (ref_exists(truname.buf)) {
strbuf_addf(msg,
@@ -504,28 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_release(&truname);
goto cleanup;
}
- }
-
- if (!strcmp(remote, "FETCH_HEAD") &&
- !access(git_path("FETCH_HEAD"), R_OK)) {
- const char *filename;
- FILE *fp;
- struct strbuf line = STRBUF_INIT;
- char *ptr;
-
- filename = git_path("FETCH_HEAD");
- fp = fopen(filename, "r");
- if (!fp)
- die_errno(_("could not open '%s' for reading"),
- filename);
- strbuf_getline(&line, fp, '\n');
- fclose(fp);
- ptr = strstr(line.buf, "\tnot-for-merge\t");
- if (ptr)
- strbuf_remove(&line, ptr-line.buf+1, 13);
- strbuf_addbuf(msg, &line);
- strbuf_release(&line);
- goto cleanup;
+ strbuf_release(&truname);
}
if (remote_head->util) {
@@ -1037,28 +1015,24 @@ static int default_edit_option(void)
st_stdin.st_mode == st_stdout.st_mode);
}
-static struct commit_list *collect_parents(struct commit *head_commit,
- int *head_subsumed,
- int argc, const char **argv)
+static struct commit_list *reduce_parents(struct commit *head_commit,
+ int *head_subsumed,
+ struct commit_list *remoteheads)
{
- int i;
- struct commit_list *remoteheads = NULL, *parents, *next;
- struct commit_list **remotes = &remoteheads;
+ struct commit_list *parents, *next, **remotes = &remoteheads;
- if (head_commit)
- remotes = &commit_list_insert(head_commit, remotes)->next;
- for (i = 0; i < argc; i++) {
- struct commit *commit = get_merge_parent(argv[i]);
- if (!commit)
- help_unknown_ref(argv[i], "merge",
- "not something we can merge");
- remotes = &commit_list_insert(commit, remotes)->next;
- }
- *remotes = NULL;
+ /*
+ * Is the current HEAD reachable from another commit being
+ * merged? If so we do not want to record it as a parent of
+ * the resulting merge, unless --no-ff is given. We will flip
+ * this variable to 0 when we find HEAD among the independent
+ * tips being merged.
+ */
+ *head_subsumed = 1;
+ /* Find what parents to record by checking independent ones. */
parents = reduce_heads(remoteheads);
- *head_subsumed = 1; /* we will flip this to 0 when we find it */
for (remoteheads = NULL, remotes = &remoteheads;
parents;
parents = next) {
@@ -1068,7 +1042,119 @@ static struct commit_list *collect_parents(struct commit *head_commit,
*head_subsumed = 0;
else
remotes = &commit_list_insert(commit, remotes)->next;
+ free(parents);
+ }
+ return remoteheads;
+}
+
+static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg)
+{
+ struct fmt_merge_msg_opts opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !have_message;
+ opts.shortlog_len = shortlog_len;
+ opts.credit_people = (0 < option_edit);
+
+ fmt_merge_msg(merge_names, merge_msg, &opts);
+ if (merge_msg->len)
+ strbuf_setlen(merge_msg, merge_msg->len - 1);
+}
+
+static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names)
+{
+ const char *filename;
+ int fd, pos, npos;
+ struct strbuf fetch_head_file = STRBUF_INIT;
+
+ if (!merge_names)
+ merge_names = &fetch_head_file;
+
+ filename = git_path("FETCH_HEAD");
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ die_errno(_("could not open '%s' for reading"), filename);
+
+ if (strbuf_read(merge_names, fd, 0) < 0)
+ die_errno(_("could not read '%s'"), filename);
+ if (close(fd) < 0)
+ die_errno(_("could not close '%s'"), filename);
+
+ for (pos = 0; pos < merge_names->len; pos = npos) {
+ unsigned char sha1[20];
+ char *ptr;
+ struct commit *commit;
+
+ ptr = strchr(merge_names->buf + pos, '\n');
+ if (ptr)
+ npos = ptr - merge_names->buf + 1;
+ else
+ npos = merge_names->len;
+
+ if (npos - pos < 40 + 2 ||
+ get_sha1_hex(merge_names->buf + pos, sha1))
+ commit = NULL; /* bad */
+ else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
+ continue; /* not-for-merge */
+ else {
+ char saved = merge_names->buf[pos + 40];
+ merge_names->buf[pos + 40] = '\0';
+ commit = get_merge_parent(merge_names->buf + pos);
+ merge_names->buf[pos + 40] = saved;
+ }
+ if (!commit) {
+ if (ptr)
+ *ptr = '\0';
+ die("not something we can merge in %s: %s",
+ filename, merge_names->buf + pos);
+ }
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+
+ if (merge_names == &fetch_head_file)
+ strbuf_release(&fetch_head_file);
+}
+
+static struct commit_list *collect_parents(struct commit *head_commit,
+ int *head_subsumed,
+ int argc, const char **argv,
+ struct strbuf *merge_msg)
+{
+ int i;
+ struct commit_list *remoteheads = NULL;
+ struct commit_list **remotes = &remoteheads;
+ struct strbuf merge_names = STRBUF_INIT, *autogen = NULL;
+
+ if (merge_msg && (!have_message || shortlog_len))
+ autogen = &merge_names;
+
+ if (head_commit)
+ remotes = &commit_list_insert(head_commit, remotes)->next;
+
+ if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) {
+ handle_fetch_head(remotes, autogen);
+ remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+ } else {
+ for (i = 0; i < argc; i++) {
+ struct commit *commit = get_merge_parent(argv[i]);
+ if (!commit)
+ help_unknown_ref(argv[i], "merge",
+ "not something we can merge");
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+ if (autogen) {
+ struct commit_list *p;
+ for (p = remoteheads; p; p = p->next)
+ merge_name(merge_remote_util(p->item)->name, autogen);
+ }
}
+
+ if (autogen) {
+ prepare_merge_message(autogen, merge_msg);
+ strbuf_release(autogen);
+ }
+
return remoteheads;
}
@@ -1158,61 +1244,62 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
option_commit = 0;
}
- if (!abort_current_merge) {
- if (!argc) {
- if (default_to_upstream)
- argc = setup_with_upstream(&argv);
- else
- die(_("No commit specified and merge.defaultToUpstream not set."));
- } else if (argc == 1 && !strcmp(argv[0], "-"))
- argv[0] = "@{-1}";
+ if (!argc) {
+ if (default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else
+ die(_("No commit specified and merge.defaultToUpstream not set."));
+ } else if (argc == 1 && !strcmp(argv[0], "-")) {
+ argv[0] = "@{-1}";
}
+
if (!argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
- /*
- * This could be traditional "merge <msg> HEAD <commit>..." and
- * the way we can tell it is to see if the second token is HEAD,
- * but some people might have misused the interface and used a
- * commit-ish that is the same as HEAD there instead.
- * Traditional format never would have "-m" so it is an
- * additional safety measure to check for it.
- */
-
- if (!have_message && head_commit &&
- is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
- strbuf_addstr(&merge_msg, argv[0]);
- head_arg = argv[1];
- argv += 2;
- argc -= 2;
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- } else if (!head_commit) {
+ if (!head_commit) {
struct commit *remote_head;
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
* We do the same for "git pull".
*/
- if (argc != 1)
- die(_("Can merge only exactly one commit into "
- "empty head"));
if (squash)
die(_("Squash commit into empty head not supported yet"));
if (fast_forward == FF_NO)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, NULL);
remote_head = remoteheads->item;
if (!remote_head)
die(_("%s - not something we can merge"), argv[0]);
+ if (remoteheads->next)
+ die(_("Can merge only exactly one commit into empty head"));
read_empty(remote_head->object.sha1, 0);
update_ref("initial pull", "HEAD", remote_head->object.sha1,
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
- } else {
- struct strbuf merge_names = STRBUF_INIT;
+ }
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * commit-ish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ if (!have_message &&
+ is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
+ warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, NULL);
+ } else {
/* We are invoked directly as the first-class UI. */
head_arg = "HEAD";
@@ -1221,21 +1308,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* the standard merge summary message to be appended
* to the given message.
*/
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- for (p = remoteheads; p; p = p->next)
- merge_name(merge_remote_util(p->item)->name, &merge_names);
-
- if (!have_message || shortlog_len) {
- struct fmt_merge_msg_opts opts;
- memset(&opts, 0, sizeof(opts));
- opts.add_title = !have_message;
- opts.shortlog_len = shortlog_len;
- opts.credit_people = (0 < option_edit);
-
- fmt_merge_msg(&merge_names, &merge_msg, &opts);
- if (merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len - 1);
- }
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, &merge_msg);
}
if (!head_commit || !argc)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index c3a7516..c067107 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -961,10 +961,8 @@ static int want_object_in_pack(const unsigned char *sha1,
off_t offset = find_pack_entry_one(sha1, p);
if (offset) {
if (!*found_pack) {
- if (!is_pack_valid(p)) {
- warning("packfile %s cannot be accessed", p->pack_name);
+ if (!is_pack_valid(p))
continue;
- }
*found_offset = offset;
*found_pack = p;
}
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 77db873..ba34dac 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -1,14 +1,14 @@
#include "builtin.h"
-static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result)
+static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
{
char name[50];
if (!patchlen)
return;
- memcpy(name, sha1_to_hex(id), 41);
- printf("%s %s\n", sha1_to_hex(result), name);
+ memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
+ printf("%s %s\n", oid_to_hex(result), name);
}
static int remove_space(char *line)
@@ -53,23 +53,23 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
return 1;
}
-static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx)
+static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
{
- unsigned char hash[20];
+ unsigned char hash[GIT_SHA1_RAWSZ];
unsigned short carry = 0;
int i;
git_SHA1_Final(hash, ctx);
git_SHA1_Init(ctx);
/* 20-byte sum, with carry */
- for (i = 0; i < 20; ++i) {
- carry += result[i] + hash[i];
- result[i] = carry;
+ for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
+ carry += result->hash[i] + hash[i];
+ result->hash[i] = carry;
carry >>= 8;
}
}
-static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
+static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
struct strbuf *line_buf, int stable)
{
int patchlen = 0, found_next = 0;
@@ -77,7 +77,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
git_SHA_CTX ctx;
git_SHA1_Init(&ctx);
- hashclr(result);
+ oidclr(result);
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
char *line = line_buf->buf;
@@ -93,7 +93,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line))
continue;
- if (!get_sha1_hex(p, next_sha1)) {
+ if (!get_oid_hex(p, next_oid)) {
found_next = 1;
break;
}
@@ -143,7 +143,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
}
if (!found_next)
- hashclr(next_sha1);
+ oidclr(next_oid);
flush_one_hunk(result, &ctx);
@@ -152,15 +152,15 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
static void generate_id_list(int stable)
{
- unsigned char sha1[20], n[20], result[20];
+ struct object_id oid, n, result;
int patchlen;
struct strbuf line_buf = STRBUF_INIT;
- hashclr(sha1);
+ oidclr(&oid);
while (!feof(stdin)) {
- patchlen = get_one_patchid(n, result, &line_buf, stable);
- flush_current_id(patchlen, sha1, result);
- hashcpy(sha1, n);
+ patchlen = get_one_patchid(&n, &result, &line_buf, stable);
+ flush_current_id(patchlen, &oid, &result);
+ oidcpy(&oid, &n);
}
strbuf_release(&line_buf);
}
diff --git a/builtin/prune.c b/builtin/prune.c
index 17094ad..0c73246 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -76,6 +76,95 @@ static int prune_subdir(int nr, const char *path, void *data)
return 0;
}
+static int prune_worktree(const char *id, struct strbuf *reason)
+{
+ struct stat st;
+ char *path;
+ int fd, len;
+
+ if (!is_directory(git_path("worktrees/%s", id))) {
+ strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+ return 1;
+ }
+ if (file_exists(git_path("worktrees/%s/locked", id)))
+ return 0;
+ if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
+ return 1;
+ }
+ fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
+ if (fd < 0) {
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
+ id, strerror(errno));
+ return 1;
+ }
+ len = st.st_size;
+ path = xmalloc(len + 1);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
+ len--;
+ if (!len) {
+ strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+ free(path);
+ return 1;
+ }
+ path[len] = '\0';
+ if (!file_exists(path)) {
+ struct stat st_link;
+ free(path);
+ /*
+ * the repo is moved manually and has not been
+ * accessed since?
+ */
+ if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
+ st_link.st_nlink > 1)
+ return 0;
+ if (st.st_mtime <= expire) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ free(path);
+ return 0;
+}
+
+static void prune_worktrees(void)
+{
+ struct strbuf reason = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir = opendir(git_path("worktrees"));
+ struct dirent *d;
+ int ret;
+ if (!dir)
+ return;
+ while ((d = readdir(dir)) != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ strbuf_reset(&reason);
+ if (!prune_worktree(d->d_name, &reason))
+ continue;
+ if (show_only || verbose)
+ printf("%s\n", reason.buf);
+ if (show_only)
+ continue;
+ strbuf_reset(&path);
+ strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
+ ret = remove_dir_recursively(&path, 0);
+ if (ret < 0 && errno == ENOTDIR)
+ ret = unlink(path.buf);
+ if (ret)
+ error(_("failed to remove: %s"), strerror(errno));
+ }
+ closedir(dir);
+ if (!show_only)
+ rmdir(git_path("worktrees"));
+ strbuf_release(&reason);
+ strbuf_release(&path);
+}
+
/*
* Write errors (particularly out of space) can result in
* failed temporary packs (and more rarely indexes and other
@@ -102,10 +191,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct progress *progress = NULL;
+ int do_prune_worktrees = 0;
const struct option options[] = {
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("expire objects older than <time>")),
OPT_END()
@@ -119,6 +210,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
init_revisions(&revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+
+ if (do_prune_worktrees) {
+ if (argc)
+ die(_("--worktrees does not take extra arguments"));
+ prune_worktrees();
+ return 0;
+ }
+
while (argc--) {
unsigned char sha1[20];
const char *name = *argv++;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 5292bb5..d2ec52b 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1008,7 +1008,7 @@ static void run_update_post_hook(struct command *commands)
int argc;
const char **argv;
struct child_process proc = CHILD_PROCESS_INIT;
- char *hook;
+ const char *hook;
hook = find_hook("post-update");
for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
diff --git a/builtin/remote.c b/builtin/remote.c
index 5d3ab90..ad57fc9 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -584,7 +584,7 @@ static int migrate_file(struct remote *remote)
{
struct strbuf buf = STRBUF_INIT;
int i;
- char *path = NULL;
+ const char *path = NULL;
strbuf_addf(&buf, "remote.%s.url", remote->name);
for (i = 0; i < remote->url_nr; i++)
diff --git a/builtin/repack.c b/builtin/repack.c
index f2edeb0..af7340c 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -285,7 +285,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
failed = 0;
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
- char *fname, *fname_old;
+ const char *fname_old;
+ char *fname;
fname = mkpathdup("%s/pack-%s%s", packdir,
item->string, exts[ext].name);
if (!file_exists(fname)) {
@@ -313,7 +314,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (failed) {
struct string_list rollback_failure = STRING_LIST_INIT_DUP;
for_each_string_list_item(item, &rollback) {
- char *fname, *fname_old;
+ const char *fname_old;
+ char *fname;
fname = mkpathdup("%s/%s", packdir, item->string);
fname_old = mkpath("%s/old-%s", packdir, item->string);
if (rename(fname_old, fname))
@@ -366,7 +368,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
/* Remove the "old-" files */
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
- char *fname;
+ const char *fname;
fname = mkpath("%s/old-%s%s",
packdir,
item->string,
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 3626c61..4d10dd9 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -533,6 +533,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+ if (!strcmp(arg, "--git-path")) {
+ if (!argv[i + 1])
+ die("--git-path requires an argument");
+ puts(git_path("%s", argv[i + 1]));
+ i++;
+ continue;
+ }
if (as_is) {
if (show_file(arg, output_prefix) && as_is < 2)
verify_filename(prefix, arg, 0);
@@ -755,6 +762,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
free(cwd);
continue;
}
+ if (!strcmp(arg, "--git-common-dir")) {
+ puts(get_git_common_dir());
+ continue;
+ }
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir = argv[++i];
if (!gitdir)
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index f3fb5fb..e69fb7c 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -718,7 +718,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
}
/* If nothing is specified, show all branches by default */
- if (ac + all_heads + all_remotes == 0)
+ if (ac <= topics && all_heads + all_remotes == 0)
all_heads = 1;
if (reflog) {
@@ -785,13 +785,13 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
}
free(ref);
}
- else if (all_heads + all_remotes)
- snarf_refs(all_heads, all_remotes);
else {
while (0 < ac) {
append_one_rev(*av);
ac--; av++;
}
+ if (all_heads + all_remotes)
+ snarf_refs(all_heads, all_remotes);
}
head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 6271b54..0665b31 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -532,10 +532,9 @@ static int do_unresolve(int ac, const char **av,
for (i = 1; i < ac; i++) {
const char *arg = av[i];
- const char *p = prefix_path(prefix, prefix_length, arg);
+ char *p = prefix_path(prefix, prefix_length, arg);
err |= unresolve_one(p);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
+ free(p);
}
return err;
}
@@ -871,14 +870,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
case PARSE_OPT_DONE:
{
const char *path = ctx.argv[0];
- const char *p;
+ char *p;
setup_work_tree();
p = prefix_path(prefix, prefix_length, path);
update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- free((char *)p);
+ free(p);
ctx.argc--;
ctx.argv++;
break;
@@ -909,7 +908,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
setup_work_tree();
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
+ char *p;
if (line_termination && buf.buf[0] == '"') {
strbuf_reset(&nbuf);
if (unquote_c_style(&nbuf, buf.buf, NULL))
@@ -920,7 +919,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- free((char *)p);
+ free(p);
}
strbuf_release(&nbuf);
strbuf_release(&buf);
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 8d157eb..7cffc3a 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -24,7 +24,7 @@ static struct bulk_checkin_state {
static void finish_bulk_checkin(struct bulk_checkin_state *state)
{
- unsigned char sha1[20];
+ struct object_id oid;
struct strbuf packname = STRBUF_INIT;
int i;
@@ -36,11 +36,11 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
unlink(state->pack_tmp_name);
goto clear_exit;
} else if (state->nr_written == 1) {
- sha1close(state->f, sha1, CSUM_FSYNC);
+ sha1close(state->f, oid.hash, CSUM_FSYNC);
} else {
- int fd = sha1close(state->f, sha1, 0);
- fixup_pack_header_footer(fd, sha1, state->pack_tmp_name,
- state->nr_written, sha1,
+ int fd = sha1close(state->f, oid.hash, 0);
+ fixup_pack_header_footer(fd, oid.hash, state->pack_tmp_name,
+ state->nr_written, oid.hash,
state->offset);
close(fd);
}
@@ -48,7 +48,7 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
strbuf_addf(&packname, "%s/pack/pack-", get_object_directory());
finish_tmp_packfile(&packname, state->pack_tmp_name,
state->written, state->nr_written,
- &state->pack_idx_opts, sha1);
+ &state->pack_idx_opts, oid.hash);
for (i = 0; i < state->nr_written; i++)
free(state->written[i]);
diff --git a/cache.h b/cache.h
index 3d3244b..c97d807 100644
--- a/cache.h
+++ b/cache.h
@@ -43,6 +43,14 @@ int git_deflate_end_gently(git_zstream *);
int git_deflate(git_zstream *, int flush);
unsigned long git_deflate_bound(git_zstream *, unsigned long);
+/* The length in bytes and in hex digits of an object name (SHA-1 value). */
+#define GIT_SHA1_RAWSZ 20
+#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
+
+struct object_id {
+ unsigned char hash[GIT_SHA1_RAWSZ];
+};
+
#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
#define DTYPE(de) ((de)->d_type)
#else
@@ -370,6 +378,7 @@ static inline enum object_type object_type(unsigned int mode)
/* Double-check local_repo_env below if you add to this list. */
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
@@ -423,11 +432,13 @@ extern int is_inside_git_dir(void);
extern char *git_work_tree_cfg;
extern int is_inside_work_tree(void);
extern const char *get_git_dir(void);
+extern const char *get_git_common_dir(void);
extern int is_git_directory(const char *path);
extern char *get_object_directory(void);
extern char *get_index_file(void);
extern char *get_graft_file(void);
extern int set_git_dir(const char *path);
+extern int get_common_dir(struct strbuf *sb, const char *gitdir);
extern const char *get_git_namespace(void);
extern const char *strip_namespace(const char *namespaced_ref);
extern const char *get_git_work_tree(void);
@@ -612,6 +623,7 @@ extern int core_apply_sparse_checkout;
extern int precomposed_unicode;
extern int protect_hfs;
extern int protect_ntfs;
+extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
/*
* Include broken refs in all ref iterations, which will
@@ -682,18 +694,19 @@ extern int check_repository_format(void);
extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
-extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
- __attribute__((format (printf, 3, 4)));
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+ __attribute__((format (printf, 2, 3)));
extern char *git_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
extern char *mkpathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
/* Return a statically allocated filename matching the sha1 signature */
-extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path_submodule(const char *path, const char *fmt, ...)
+extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path_submodule(const char *path, const char *fmt, ...)
__attribute__((format (printf, 2, 3)));
+extern void report_linked_checkout_garbage(void);
/*
* Return the name of the file in the local object database that would
@@ -718,13 +731,13 @@ extern char *sha1_pack_name(const unsigned char *sha1);
extern char *sha1_pack_index_name(const unsigned char *sha1);
extern const char *find_unique_abbrev(const unsigned char *sha1, int);
-extern const unsigned char null_sha1[20];
+extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
{
int i;
- for (i = 0; i < 20; i++, sha1++, sha2++) {
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++, sha1++, sha2++) {
if (*sha1 != *sha2)
return *sha1 - *sha2;
}
@@ -732,20 +745,42 @@ static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
return 0;
}
+static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
+{
+ return hashcmp(oid1->hash, oid2->hash);
+}
+
static inline int is_null_sha1(const unsigned char *sha1)
{
return !hashcmp(sha1, null_sha1);
}
+static inline int is_null_oid(const struct object_id *oid)
+{
+ return !hashcmp(oid->hash, null_sha1);
+}
+
static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
{
- memcpy(sha_dst, sha_src, 20);
+ memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ);
}
+
+static inline void oidcpy(struct object_id *dst, const struct object_id *src)
+{
+ hashcpy(dst->hash, src->hash);
+}
+
static inline void hashclr(unsigned char *hash)
{
- memset(hash, 0, 20);
+ memset(hash, 0, GIT_SHA1_RAWSZ);
+}
+
+static inline void oidclr(struct object_id *oid)
+{
+ hashclr(oid->hash);
}
+
#define EMPTY_TREE_SHA1_HEX \
"4b825dc642cb6eb9a060e54bf8d69288fbee4904"
#define EMPTY_TREE_SHA1_BIN_LITERAL \
@@ -816,7 +851,6 @@ enum scld_error safe_create_leading_directories(char *path);
enum scld_error safe_create_leading_directories_const(const char *path);
int mkdir_in_gitdir(const char *path);
-extern void home_config_paths(char **global, char **xdg, char *file);
extern char *expand_user_path(const char *path);
const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)
@@ -836,8 +870,16 @@ char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
extern int is_ntfs_dotgit(const char *name);
+/**
+ * Return a newly allocated string with the evaluation of
+ * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
+ * "$HOME/.config/git/$filename". Return NULL upon error.
+ */
+extern char *xdg_config_home(const char *filename);
+
/* object replacement */
#define LOOKUP_REPLACE_OBJECT 1
+#define LOOKUP_UNKNOWN_OBJECT 2
extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
@@ -874,6 +916,7 @@ static inline const unsigned char *lookup_replace_object_extended(const unsigned
extern int sha1_object_info(const unsigned char *, unsigned long *);
extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
+extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags);
extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
extern int force_object_loose(const unsigned char *sha1, time_t mtime);
extern int git_open_noatime(const char *name);
@@ -952,8 +995,10 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
* null-terminated string.
*/
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
+extern int get_oid_hex(const char *hex, struct object_id *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
+extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
extern int read_ref_full(const char *refname, int resolve_flags,
unsigned char *sha1, int *flags);
extern int read_ref(const char *refname, unsigned char *sha1);
@@ -1174,6 +1219,7 @@ extern struct packed_git {
int pack_fd;
unsigned pack_local:1,
pack_keep:1,
+ freshened:1,
do_not_close:1;
unsigned char sha1[20];
/* something like ".git/objects/pack/xxxxx.pack" */
@@ -1289,14 +1335,16 @@ int for_each_loose_file_in_objdir_buf(struct strbuf *path,
/*
* Iterate over loose and packed objects in both the local
- * repository and any alternates repositories.
+ * repository and any alternates repositories (unless the
+ * LOCAL_ONLY flag is set).
*/
+#define FOR_EACH_OBJECT_LOCAL_ONLY 0x1
typedef int each_packed_object_fn(const unsigned char *sha1,
struct packed_git *pack,
uint32_t pos,
void *data);
-extern int for_each_loose_object(each_loose_object_fn, void *);
-extern int for_each_packed_object(each_packed_object_fn, void *);
+extern int for_each_loose_object(each_loose_object_fn, void *, unsigned flags);
+extern int for_each_packed_object(each_packed_object_fn, void *, unsigned flags);
struct object_info {
/* Request */
@@ -1304,6 +1352,7 @@ struct object_info {
unsigned long *sizep;
unsigned long *disk_sizep;
unsigned char *delta_base_sha1;
+ struct strbuf *typename;
/* Response */
enum {
@@ -1508,6 +1557,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
{
return write_in_full(fd, str, strlen(str));
}
+__attribute__((format (printf, 3, 4)))
+extern int write_file(const char *path, int fatal, const char *fmt, ...);
/* pager.c */
extern void setup_pager(void);
diff --git a/combine-diff.c b/combine-diff.c
index 91edce5..8eb7278 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -44,9 +44,9 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
memset(p->parent, 0,
sizeof(p->parent[0]) * num_parent);
- hashcpy(p->sha1, q->queue[i]->two->sha1);
+ hashcpy(p->oid.hash, q->queue[i]->two->sha1);
p->mode = q->queue[i]->two->mode;
- hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
+ hashcpy(p->parent[n].oid.hash, q->queue[i]->one->sha1);
p->parent[n].mode = q->queue[i]->one->mode;
p->parent[n].status = q->queue[i]->status;
*tail = p;
@@ -77,7 +77,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
continue;
}
- hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
+ hashcpy(p->parent[n].oid.hash, q->queue[i]->one->sha1);
p->parent[n].mode = q->queue[i]->one->mode;
p->parent[n].status = q->queue[i]->status;
@@ -284,7 +284,7 @@ static struct lline *coalesce_lines(struct lline *base, int *lenbase,
return base;
}
-static char *grab_blob(const unsigned char *sha1, unsigned int mode,
+static char *grab_blob(const struct object_id *oid, unsigned int mode,
unsigned long *size, struct userdiff_driver *textconv,
const char *path)
{
@@ -294,20 +294,20 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode,
if (S_ISGITLINK(mode)) {
blob = xmalloc(100);
*size = snprintf(blob, 100,
- "Subproject commit %s\n", sha1_to_hex(sha1));
- } else if (is_null_sha1(sha1)) {
+ "Subproject commit %s\n", oid_to_hex(oid));
+ } else if (is_null_oid(oid)) {
/* deleted blob */
*size = 0;
return xcalloc(1, 1);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(path);
- fill_filespec(df, sha1, 1, mode);
+ fill_filespec(df, oid->hash, 1, mode);
*size = fill_textconv(textconv, df, &blob);
free_filespec(df);
} else {
- blob = read_sha1_file(sha1, &type, size);
+ blob = read_sha1_file(oid->hash, &type, size);
if (type != OBJ_BLOB)
- die("object '%s' is not a blob!", sha1_to_hex(sha1));
+ die("object '%s' is not a blob!", oid_to_hex(oid));
}
return blob;
}
@@ -389,7 +389,7 @@ static void consume_line(void *state_, char *line, unsigned long len)
}
}
-static void combine_diff(const unsigned char *parent, unsigned int mode,
+static void combine_diff(const struct object_id *parent, unsigned int mode,
mmfile_t *result_file,
struct sline *sline, unsigned int cnt, int n,
int num_parent, int result_deleted,
@@ -897,7 +897,7 @@ static void show_combined_header(struct combine_diff_path *elem,
int show_file_header)
{
struct diff_options *opt = &rev->diffopt;
- int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+ int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? GIT_SHA1_HEXSZ : DEFAULT_ABBREV;
const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
@@ -914,11 +914,11 @@ static void show_combined_header(struct combine_diff_path *elem,
"", elem->path, line_prefix, c_meta, c_reset);
printf("%s%sindex ", line_prefix, c_meta);
for (i = 0; i < num_parent; i++) {
- abb = find_unique_abbrev(elem->parent[i].sha1,
+ abb = find_unique_abbrev(elem->parent[i].oid.hash,
abbrev);
printf("%s%s", i ? "," : "", abb);
}
- abb = find_unique_abbrev(elem->sha1, abbrev);
+ abb = find_unique_abbrev(elem->oid.hash, abbrev);
printf("..%s%s\n", abb, c_reset);
if (mode_differs) {
@@ -991,7 +991,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
/* Read the result of merge first */
if (!working_tree_file)
- result = grab_blob(elem->sha1, elem->mode, &result_size,
+ result = grab_blob(&elem->oid, elem->mode, &result_size,
textconv, elem->path);
else {
/* Used by diff-tree to read from the working tree */
@@ -1013,12 +1013,12 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
result = strbuf_detach(&buf, NULL);
elem->mode = canon_mode(st.st_mode);
} else if (S_ISDIR(st.st_mode)) {
- unsigned char sha1[20];
- if (resolve_gitlink_ref(elem->path, "HEAD", sha1) < 0)
- result = grab_blob(elem->sha1, elem->mode,
+ struct object_id oid;
+ if (resolve_gitlink_ref(elem->path, "HEAD", oid.hash) < 0)
+ result = grab_blob(&elem->oid, elem->mode,
&result_size, NULL, NULL);
else
- result = grab_blob(sha1, elem->mode,
+ result = grab_blob(&oid, elem->mode,
&result_size, NULL, NULL);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(elem->path);
@@ -1090,7 +1090,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
for (i = 0; !is_binary && i < num_parent; i++) {
char *buf;
unsigned long size;
- buf = grab_blob(elem->parent[i].sha1,
+ buf = grab_blob(&elem->parent[i].oid,
elem->parent[i].mode,
&size, NULL, NULL);
if (buffer_is_binary(buf, size))
@@ -1139,14 +1139,14 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
for (i = 0; i < num_parent; i++) {
int j;
for (j = 0; j < i; j++) {
- if (!hashcmp(elem->parent[i].sha1,
- elem->parent[j].sha1)) {
+ if (!oidcmp(&elem->parent[i].oid,
+ &elem->parent[j].oid)) {
reuse_combine_diff(sline, cnt, i, j);
break;
}
}
if (i <= j)
- combine_diff(elem->parent[i].sha1,
+ combine_diff(&elem->parent[i].oid,
elem->parent[i].mode,
&result_file, sline,
cnt, i, num_parent, result_deleted,
@@ -1206,9 +1206,9 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
/* Show sha1's */
for (i = 0; i < num_parent; i++)
- printf(" %s", diff_unique_abbrev(p->parent[i].sha1,
+ printf(" %s", diff_unique_abbrev(p->parent[i].oid.hash,
opt->abbrev));
- printf(" %s ", diff_unique_abbrev(p->sha1, opt->abbrev));
+ printf(" %s ", diff_unique_abbrev(p->oid.hash, opt->abbrev));
}
if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) {
@@ -1271,16 +1271,16 @@ static struct diff_filepair *combined_pair(struct combine_diff_path *p,
for (i = 0; i < num_parent; i++) {
pair->one[i].path = p->path;
pair->one[i].mode = p->parent[i].mode;
- hashcpy(pair->one[i].sha1, p->parent[i].sha1);
- pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1);
+ hashcpy(pair->one[i].sha1, p->parent[i].oid.hash);
+ pair->one[i].sha1_valid = !is_null_oid(&p->parent[i].oid);
pair->one[i].has_more_entries = 1;
}
pair->one[num_parent - 1].has_more_entries = 0;
pair->two->path = p->path;
pair->two->mode = p->mode;
- hashcpy(pair->two->sha1, p->sha1);
- pair->two->sha1_valid = !is_null_sha1(p->sha1);
+ hashcpy(pair->two->sha1, p->oid.hash);
+ pair->two->sha1_valid = !is_null_oid(&p->oid);
return pair;
}
diff --git a/command-list.txt b/command-list.txt
index f1eae08..54d8d21 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -1,5 +1,5 @@
# List of known git commands.
-# command name category [deprecated] [common]
+# command name category [deprecated] [common]
git-add mainporcelain common
git-am mainporcelain
git-annotate ancillaryinterrogators
@@ -40,21 +40,21 @@ git-diff-files plumbinginterrogators
git-diff-index plumbinginterrogators
git-diff-tree plumbinginterrogators
git-difftool ancillaryinterrogators
-git-fast-export ancillarymanipulators
-git-fast-import ancillarymanipulators
+git-fast-export ancillarymanipulators
+git-fast-import ancillarymanipulators
git-fetch mainporcelain common
git-fetch-pack synchingrepositories
git-filter-branch ancillarymanipulators
git-fmt-merge-msg purehelpers
git-for-each-ref plumbinginterrogators
git-format-patch mainporcelain
-git-fsck ancillaryinterrogators
+git-fsck ancillaryinterrogators
git-gc mainporcelain
git-get-tar-commit-id ancillaryinterrogators
git-grep mainporcelain common
git-gui mainporcelain
git-hash-object plumbingmanipulators
-git-help ancillaryinterrogators
+git-help ancillaryinterrogators
git-http-backend synchingrepositories
git-http-fetch synchelpers
git-http-push synchelpers
diff --git a/commit.c b/commit.c
index a8c7577..2d9de80 100644
--- a/commit.c
+++ b/commit.c
@@ -55,12 +55,12 @@ struct commit *lookup_commit(const unsigned char *sha1)
struct commit *lookup_commit_reference_by_name(const char *name)
{
- unsigned char sha1[20];
+ struct object_id oid;
struct commit *commit;
- if (get_sha1_committish(name, sha1))
+ if (get_sha1_committish(name, oid.hash))
return NULL;
- commit = lookup_commit_reference(sha1);
+ commit = lookup_commit_reference(oid.hash);
if (parse_commit(commit))
return NULL;
return commit;
@@ -99,7 +99,7 @@ static int commit_graft_alloc, commit_graft_nr;
static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
{
struct commit_graft **commit_graft_table = table;
- return commit_graft_table[index]->sha1;
+ return commit_graft_table[index]->oid.hash;
}
static int commit_graft_pos(const unsigned char *sha1)
@@ -110,7 +110,7 @@ static int commit_graft_pos(const unsigned char *sha1)
int register_commit_graft(struct commit_graft *graft, int ignore_dups)
{
- int pos = commit_graft_pos(graft->sha1);
+ int pos = commit_graft_pos(graft->oid.hash);
if (0 <= pos) {
if (ignore_dups)
@@ -138,22 +138,23 @@ struct commit_graft *read_graft_line(char *buf, int len)
/* The format is just "Commit Parent1 Parent2 ...\n" */
int i;
struct commit_graft *graft = NULL;
+ const int entry_size = GIT_SHA1_HEXSZ + 1;
while (len && isspace(buf[len-1]))
buf[--len] = '\0';
if (buf[0] == '#' || buf[0] == '\0')
return NULL;
- if ((len + 1) % 41)
+ if ((len + 1) % entry_size)
goto bad_graft_data;
- i = (len + 1) / 41 - 1;
- graft = xmalloc(sizeof(*graft) + 20 * i);
+ i = (len + 1) / entry_size - 1;
+ graft = xmalloc(sizeof(*graft) + GIT_SHA1_RAWSZ * i);
graft->nr_parent = i;
- if (get_sha1_hex(buf, graft->sha1))
+ if (get_oid_hex(buf, &graft->oid))
goto bad_graft_data;
- for (i = 40; i < len; i += 41) {
+ for (i = GIT_SHA1_HEXSZ; i < len; i += entry_size) {
if (buf[i] != ' ')
goto bad_graft_data;
- if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
+ if (get_sha1_hex(buf + i + 1, graft->parent[i/entry_size].hash))
goto bad_graft_data;
}
return graft;
@@ -302,39 +303,42 @@ int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long s
{
const char *tail = buffer;
const char *bufptr = buffer;
- unsigned char parent[20];
+ struct object_id parent;
struct commit_list **pptr;
struct commit_graft *graft;
+ const int tree_entry_len = GIT_SHA1_HEXSZ + 5;
+ const int parent_entry_len = GIT_SHA1_HEXSZ + 7;
if (item->object.parsed)
return 0;
item->object.parsed = 1;
tail += size;
- if (tail <= bufptr + 46 || memcmp(bufptr, "tree ", 5) || bufptr[45] != '\n')
+ if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) ||
+ bufptr[tree_entry_len] != '\n')
return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
- if (get_sha1_hex(bufptr + 5, parent) < 0)
+ if (get_sha1_hex(bufptr + 5, parent.hash) < 0)
return error("bad tree pointer in commit %s",
sha1_to_hex(item->object.sha1));
- item->tree = lookup_tree(parent);
- bufptr += 46; /* "tree " + "hex sha1" + "\n" */
+ item->tree = lookup_tree(parent.hash);
+ bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */
pptr = &item->parents;
graft = lookup_commit_graft(item->object.sha1);
- while (bufptr + 48 < tail && !memcmp(bufptr, "parent ", 7)) {
+ while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) {
struct commit *new_parent;
- if (tail <= bufptr + 48 ||
- get_sha1_hex(bufptr + 7, parent) ||
- bufptr[47] != '\n')
+ if (tail <= bufptr + parent_entry_len + 1 ||
+ get_sha1_hex(bufptr + 7, parent.hash) ||
+ bufptr[parent_entry_len] != '\n')
return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
- bufptr += 48;
+ bufptr += parent_entry_len + 1;
/*
* The clone is shallow if nr_parent < 0, and we must
* not traverse its real parents even when we unhide them.
*/
if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
continue;
- new_parent = lookup_commit(parent);
+ new_parent = lookup_commit(parent.hash);
if (new_parent)
pptr = &commit_list_insert(new_parent, pptr)->next;
}
@@ -342,7 +346,7 @@ int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long s
int i;
struct commit *new_parent;
for (i = 0; i < graft->nr_parent; i++) {
- new_parent = lookup_commit(graft->parent[i]);
+ new_parent = lookup_commit(graft->parent[i].hash);
if (!new_parent)
continue;
pptr = &commit_list_insert(new_parent, pptr)->next;
@@ -1580,10 +1584,10 @@ struct commit *get_merge_parent(const char *name)
{
struct object *obj;
struct commit *commit;
- unsigned char sha1[20];
- if (get_sha1(name, sha1))
+ struct object_id oid;
+ if (get_sha1(name, oid.hash))
return NULL;
- obj = parse_object(sha1);
+ obj = parse_object(oid.hash);
commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
if (commit && !commit->util) {
struct merge_remote_desc *desc;
diff --git a/commit.h b/commit.h
index 9f189cb..ed3a1d5 100644
--- a/commit.h
+++ b/commit.h
@@ -226,9 +226,9 @@ enum rev_sort_order {
void sort_in_topological_order(struct commit_list **, enum rev_sort_order);
struct commit_graft {
- unsigned char sha1[20];
+ struct object_id oid;
int nr_parent; /* < 0 if shallow commit */
- unsigned char parent[FLEX_ARRAY][20]; /* more */
+ struct object_id parent[FLEX_ARRAY]; /* more */
};
typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
diff --git a/compat/mingw.h b/compat/mingw.h
index 5e499cf..98c5e44 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -98,8 +98,6 @@ static inline unsigned int alarm(unsigned int seconds)
{ return 0; }
static inline int fsync(int fd)
{ return _commit(fd); }
-static inline pid_t getppid(void)
-{ return 1; }
static inline void sync(void)
{}
static inline uid_t getuid(void)
@@ -121,6 +119,12 @@ static inline int sigaddset(sigset_t *set, int signum)
#define SIG_UNBLOCK 0
static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{ return 0; }
+static inline pid_t getppid(void)
+{ return 1; }
+static inline pid_t getpgid(pid_t pid)
+{ return pid == 0 ? getpid() : pid; }
+static inline pid_t tcgetpgrp(int fd)
+{ return getpid(); }
/*
* simple adaptors
diff --git a/config.c b/config.c
index 66c0a51..ab46462 100644
--- a/config.c
+++ b/config.c
@@ -12,6 +12,7 @@
#include "quote.h"
#include "hashmap.h"
#include "string-list.h"
+#include "utf8.h"
struct config_source {
struct config_source *prev;
@@ -49,7 +50,7 @@ static struct config_set the_config_set;
static int config_file_fgetc(struct config_source *conf)
{
- return fgetc(conf->u.file);
+ return getc_unlocked(conf->u.file);
}
static int config_file_ungetc(int c, struct config_source *conf)
@@ -417,8 +418,7 @@ static int git_parse_source(config_fn_t fn, void *data)
struct strbuf *var = &cf->var;
/* U+FEFF Byte Order Mark in UTF8 */
- static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
- const unsigned char *bomptr = utf8_bom;
+ const char *bomptr = utf8_bom;
for (;;) {
int c = get_next_char();
@@ -426,7 +426,7 @@ static int git_parse_source(config_fn_t fn, void *data)
/* We are at the file beginning; skip UTF8-encoded BOM
* if present. Sane editors won't put this in on their
* own, but e.g. Windows Notepad will do it happily. */
- if ((unsigned char) c == *bomptr) {
+ if (c == (*bomptr & 0377)) {
bomptr++;
continue;
} else {
@@ -1088,7 +1088,9 @@ 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, filename, filename, f, data);
+ funlockfile(f);
fclose(f);
}
return ret;
@@ -1185,10 +1187,8 @@ int git_config_system(void)
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
- char *xdg_config = NULL;
- char *user_config = NULL;
-
- home_config_paths(&user_config, &xdg_config, "config");
+ char *xdg_config = xdg_config_home("config");
+ char *user_config = expand_user_path("~/.gitconfig");
if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
diff --git a/config.mak.uname b/config.mak.uname
index f4e77cb..d26665f 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -36,6 +36,7 @@ ifeq ($(uname_S),Linux)
HAVE_DEV_TTY = YesPlease
HAVE_CLOCK_GETTIME = YesPlease
HAVE_CLOCK_MONOTONIC = YesPlease
+ HAVE_GETDELIM = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
HAVE_ALLOCA_H = YesPlease
diff --git a/connect.c b/connect.c
index 391d211..c0144d8 100644
--- a/connect.c
+++ b/connect.c
@@ -724,7 +724,7 @@ struct child_process *git_connect(int fd[2], const char *url,
conn->in = conn->out = -1;
if (protocol == PROTO_SSH) {
const char *ssh;
- int putty;
+ int putty, tortoiseplink = 0;
char *ssh_host = hostandport;
const char *port = NULL;
get_host_and_port(&ssh_host, &port);
@@ -743,28 +743,40 @@ struct child_process *git_connect(int fd[2], const char *url,
free(path);
free(conn);
return NULL;
+ }
+
+ ssh = getenv("GIT_SSH_COMMAND");
+ if (ssh) {
+ conn->use_shell = 1;
+ putty = 0;
} else {
- ssh = getenv("GIT_SSH_COMMAND");
- if (ssh) {
- conn->use_shell = 1;
- putty = 0;
- } else {
- ssh = getenv("GIT_SSH");
- if (!ssh)
- ssh = "ssh";
- putty = !!strcasestr(ssh, "plink");
- }
-
- argv_array_push(&conn->args, ssh);
- if (putty && !strcasestr(ssh, "tortoiseplink"))
- argv_array_push(&conn->args, "-batch");
- if (port) {
- /* P is for PuTTY, p is for OpenSSH */
- argv_array_push(&conn->args, putty ? "-P" : "-p");
- argv_array_push(&conn->args, port);
- }
- argv_array_push(&conn->args, ssh_host);
+ const char *base;
+ char *ssh_dup;
+
+ ssh = getenv("GIT_SSH");
+ if (!ssh)
+ ssh = "ssh";
+
+ ssh_dup = xstrdup(ssh);
+ base = basename(ssh_dup);
+
+ tortoiseplink = !strcasecmp(base, "tortoiseplink") ||
+ !strcasecmp(base, "tortoiseplink.exe");
+ putty = !strcasecmp(base, "plink") ||
+ !strcasecmp(base, "plink.exe") || tortoiseplink;
+
+ free(ssh_dup);
+ }
+
+ argv_array_push(&conn->args, ssh);
+ if (tortoiseplink)
+ argv_array_push(&conn->args, "-batch");
+ if (port) {
+ /* P is for PuTTY, p is for OpenSSH */
+ argv_array_push(&conn->args, putty ? "-P" : "-p");
+ argv_array_push(&conn->args, port);
}
+ argv_array_push(&conn->args, ssh_host);
} else {
/* remove repo-local variables from the environment */
conn->env = local_repo_env;
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5944c82..bfc74e9 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -665,8 +665,8 @@ __git_list_porcelain_commands ()
checkout-index) : plumbing;;
commit-tree) : plumbing;;
count-objects) : infrequent;;
- credential-cache) : credentials helper;;
- credential-store) : credentials helper;;
+ credential) : credentials;;
+ credential-*) : credentials helper;;
cvsexportcommit) : export;;
cvsimport) : import;;
cvsserver) : daemon;;
@@ -735,35 +735,29 @@ __git_list_porcelain_commands ()
__git_porcelain_commands=
__git_compute_porcelain_commands ()
{
- __git_compute_all_commands
test -n "$__git_porcelain_commands" ||
__git_porcelain_commands=$(__git_list_porcelain_commands)
}
-__git_pretty_aliases ()
+# Lists all set config variables starting with the given section prefix,
+# with the prefix removed.
+__git_get_config_variables ()
{
- local i IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do
- case "$i" in
- pretty.*)
- i="${i#pretty.}"
- echo "${i/ */}"
- ;;
- esac
+ local section="$1" i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" config --get-regexp "^$section\..*" 2>/dev/null); do
+ i="${i#$section.}"
+ echo "${i/ */}"
done
}
+__git_pretty_aliases ()
+{
+ __git_get_config_variables "pretty"
+}
+
__git_aliases ()
{
- local i IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do
- case "$i" in
- alias.*)
- i="${i#alias.}"
- echo "${i/ */}"
- ;;
- esac
- done
+ __git_get_config_variables "alias"
}
# __git_aliased_command requires 1 argument
@@ -1448,7 +1442,7 @@ _git_log ()
return
;;
--decorate=*)
- __gitcomp "long short" "" "${cur##--decorate=}"
+ __gitcomp "full short no" "" "${cur##--decorate=}"
return
;;
--*)
@@ -2123,6 +2117,7 @@ _git_config ()
http.noEPSV
http.postBuffer
http.proxy
+ http.sslCipherList
http.sslCAInfo
http.sslCAPath
http.sslCert
@@ -2260,12 +2255,7 @@ _git_remote ()
__git_complete_remote_or_refspec
;;
update)
- local i c='' IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do
- i="${i#remotes.}"
- c="$c ${i/ */}"
- done
- __gitcomp "$c"
+ __gitcomp "$(__git_get_config_variables "remotes")"
;;
*)
;;
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index fa1a583..07bd77c 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -26,7 +26,7 @@ b,branch= create a new branch from the split subtree
ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD
- options for 'add', 'merge', 'pull' and 'push'
+ options for 'add', 'merge', and 'pull'
squash merge subtree changes as a single commit
"
eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
@@ -51,14 +51,21 @@ prefix=
debug()
{
if [ -n "$debug" ]; then
- echo "$@" >&2
+ printf "%s\n" "$*" >&2
fi
}
say()
{
if [ -z "$quiet" ]; then
- echo "$@" >&2
+ printf "%s\n" "$*" >&2
+ fi
+}
+
+progress()
+{
+ if [ -z "$quiet" ]; then
+ printf "%s\r" "$*" >&2
fi
}
@@ -599,7 +606,7 @@ cmd_split()
eval "$grl" |
while read rev parents; do
revcount=$(($revcount + 1))
- say -n "$revcount/$revmax ($createcount) "
+ progress "$revcount/$revmax ($createcount)"
debug "Processing commit: $rev"
exists=$(cache_get $rev)
if [ -n "$exists" ]; then
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
index 54e4b4a..60d76cd 100644
--- a/contrib/subtree/git-subtree.txt
+++ b/contrib/subtree/git-subtree.txt
@@ -146,7 +146,7 @@ OPTIONS
OPTIONS FOR add, merge, push, pull
----------------------------------
--squash::
- This option is only valid for add, merge, push and pull
+ This option is only valid for add, merge, and pull
commands.
+
Instead of merging the entire history from the subtree project, produce
diff --git a/credential-store.c b/credential-store.c
index 925d3f4..f692509 100644
--- a/credential-store.c
+++ b/credential-store.c
@@ -6,7 +6,7 @@
static struct lock_file credential_lock;
-static void parse_credential_file(const char *fn,
+static int parse_credential_file(const char *fn,
struct credential *c,
void (*match_cb)(struct credential *),
void (*other_cb)(struct strbuf *))
@@ -14,18 +14,20 @@ static void parse_credential_file(const char *fn,
FILE *fh;
struct strbuf line = STRBUF_INIT;
struct credential entry = CREDENTIAL_INIT;
+ int found_credential = 0;
fh = fopen(fn, "r");
if (!fh) {
- if (errno != ENOENT)
+ if (errno != ENOENT && errno != EACCES)
die_errno("unable to open %s", fn);
- return;
+ return found_credential;
}
while (strbuf_getline(&line, fh, '\n') != EOF) {
credential_from_url(&entry, line.buf);
if (entry.username && entry.password &&
credential_match(c, &entry)) {
+ found_credential = 1;
if (match_cb) {
match_cb(&entry);
break;
@@ -38,6 +40,7 @@ static void parse_credential_file(const char *fn,
credential_clear(&entry);
strbuf_release(&line);
fclose(fh);
+ return found_credential;
}
static void print_entry(struct credential *c)
@@ -64,21 +67,10 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
die_errno("unable to commit credential store");
}
-static void store_credential(const char *fn, struct credential *c)
+static void store_credential_file(const char *fn, struct credential *c)
{
struct strbuf buf = STRBUF_INIT;
- /*
- * Sanity check that what we are storing is actually sensible.
- * In particular, we can't make a URL without a protocol field.
- * Without either a host or pathname (depending on the scheme),
- * we have no primary key. And without a username and password,
- * we are not actually storing a credential.
- */
- if (!c->protocol || !(c->host || c->path) ||
- !c->username || !c->password)
- return;
-
strbuf_addf(&buf, "%s://", c->protocol);
strbuf_addstr_urlencode(&buf, c->username, 1);
strbuf_addch(&buf, ':');
@@ -95,8 +87,37 @@ static void store_credential(const char *fn, struct credential *c)
strbuf_release(&buf);
}
-static void remove_credential(const char *fn, struct credential *c)
+static void store_credential(const struct string_list *fns, struct credential *c)
+{
+ struct string_list_item *fn;
+
+ /*
+ * Sanity check that what we are storing is actually sensible.
+ * In particular, we can't make a URL without a protocol field.
+ * Without either a host or pathname (depending on the scheme),
+ * we have no primary key. And without a username and password,
+ * we are not actually storing a credential.
+ */
+ if (!c->protocol || !(c->host || c->path) || !c->username || !c->password)
+ return;
+
+ for_each_string_list_item(fn, fns)
+ if (!access(fn->string, F_OK)) {
+ store_credential_file(fn->string, c);
+ return;
+ }
+ /*
+ * Write credential to the filename specified by fns->items[0], thus
+ * creating it
+ */
+ if (fns->nr)
+ store_credential_file(fns->items[0].string, c);
+}
+
+static void remove_credential(const struct string_list *fns, struct credential *c)
{
+ struct string_list_item *fn;
+
/*
* Sanity check that we actually have something to match
* against. The input we get is a restrictive pattern,
@@ -105,14 +126,20 @@ static void remove_credential(const char *fn, struct credential *c)
* to empty input. So explicitly disallow it, and require that the
* pattern have some actual content to match.
*/
- if (c->protocol || c->host || c->path || c->username)
- rewrite_credential_file(fn, c, NULL);
+ if (!c->protocol && !c->host && !c->path && !c->username)
+ return;
+ for_each_string_list_item(fn, fns)
+ if (!access(fn->string, F_OK))
+ rewrite_credential_file(fn->string, c, NULL);
}
-static int lookup_credential(const char *fn, struct credential *c)
+static void lookup_credential(const struct string_list *fns, struct credential *c)
{
- parse_credential_file(fn, c, print_entry, NULL);
- return c->username && c->password;
+ struct string_list_item *fn;
+
+ for_each_string_list_item(fn, fns)
+ if (parse_credential_file(fn->string, c, print_entry, NULL))
+ return; /* Found credential */
}
int main(int argc, char **argv)
@@ -123,6 +150,7 @@ int main(int argc, char **argv)
};
const char *op;
struct credential c = CREDENTIAL_INIT;
+ struct string_list fns = STRING_LIST_INIT_DUP;
char *file = NULL;
struct option options[] = {
OPT_STRING(0, "file", &file, "path",
@@ -137,22 +165,30 @@ int main(int argc, char **argv)
usage_with_options(usage, options);
op = argv[0];
- if (!file)
- file = expand_user_path("~/.git-credentials");
- if (!file)
+ if (file) {
+ string_list_append(&fns, file);
+ } else {
+ if ((file = expand_user_path("~/.git-credentials")))
+ string_list_append_nodup(&fns, file);
+ file = xdg_config_home("credentials");
+ if (file)
+ string_list_append_nodup(&fns, file);
+ }
+ if (!fns.nr)
die("unable to set up default path; use --file");
if (credential_read(&c, stdin) < 0)
die("unable to read credential");
if (!strcmp(op, "get"))
- lookup_credential(file, &c);
+ lookup_credential(&fns, &c);
else if (!strcmp(op, "erase"))
- remove_credential(file, &c);
+ remove_credential(&fns, &c);
else if (!strcmp(op, "store"))
- store_credential(file, &c);
+ store_credential(&fns, &c);
else
; /* Ignore unknown operation. */
+ string_list_clear(&fns, 0);
return 0;
}
diff --git a/daemon.c b/daemon.c
index 9ee2187..d3d3e43 100644
--- a/daemon.c
+++ b/daemon.c
@@ -633,7 +633,7 @@ static void lookup_hostname(struct hostinfo *hi)
char **ap;
static char addrbuf[HOST_NAME_MAX + 1];
- hent = gethostbyname(hostname.buf);
+ hent = gethostbyname(hi->hostname.buf);
if (hent) {
ap = hent->h_addr_list;
memset(&sa, 0, sizeof sa);
@@ -1166,15 +1166,6 @@ static struct credentials *prepare_credentials(const char *user_name,
}
#endif
-static void store_pid(const char *path)
-{
- FILE *f = fopen(path, "w");
- if (!f)
- die_errno("cannot open pid file '%s'", path);
- if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
- die_errno("failed to write pid file '%s'", path);
-}
-
static int serve(struct string_list *listen_addr, int listen_port,
struct credentials *cred)
{
@@ -1385,7 +1376,7 @@ int main(int argc, char **argv)
sanitize_stdfds();
if (pid_file)
- store_pid(pid_file);
+ write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
/* prepare argv for serving-processes */
cld_argv = xmalloc(sizeof (char *) * (argc + 2));
diff --git a/date.c b/date.c
index 3eba2df..733d1b2 100644
--- a/date.c
+++ b/date.c
@@ -704,10 +704,17 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
date += match;
}
- /* mktime uses local timezone */
+ /* do not use mktime(), which uses local timezone, here */
*timestamp = tm_to_time_t(&tm);
+ if (*timestamp == -1)
+ return -1;
+
if (*offset == -1) {
- time_t temp_time = mktime(&tm);
+ time_t temp_time;
+
+ /* gmtime_r() in match_digit() may have clobbered it */
+ tm.tm_isdst = -1;
+ temp_time = mktime(&tm);
if ((time_t)*timestamp > temp_time) {
*offset = ((time_t)*timestamp - temp_time) / 60;
} else {
@@ -715,9 +722,6 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
}
}
- if (*timestamp == -1)
- return -1;
-
if (!tm_gmt)
*timestamp -= *offset * 60;
return 0; /* success */
diff --git a/diff-lib.c b/diff-lib.c
index a85c497..0d8c535 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -125,7 +125,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
dpath->next = NULL;
memcpy(dpath->path, ce->name, path_len);
dpath->path[path_len] = '\0';
- hashclr(dpath->sha1);
+ oidclr(&dpath->oid);
memset(&(dpath->parent[0]), 0,
sizeof(struct combine_diff_parent)*5);
@@ -155,7 +155,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (2 <= stage) {
int mode = nce->ce_mode;
num_compare_stages++;
- hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
+ hashcpy(dpath->parent[stage-2].oid.hash, nce->sha1);
dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
dpath->parent[stage-2].status =
DIFF_STATUS_MODIFIED;
@@ -212,6 +212,11 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
ce->sha1, !is_null_sha1(ce->sha1),
ce->name, 0);
continue;
+ } else if (ce->ce_flags & CE_INTENT_TO_ADD) {
+ diff_addremove(&revs->diffopt, '+', ce->ce_mode,
+ EMPTY_BLOB_SHA1_BIN, 0,
+ ce->name, 0);
+ continue;
}
changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
@@ -339,14 +344,14 @@ static int show_modified(struct rev_info *revs,
memcpy(p->path, new->name, pathlen);
p->path[pathlen] = 0;
p->mode = mode;
- hashclr(p->sha1);
+ oidclr(&p->oid);
memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
p->parent[0].status = DIFF_STATUS_MODIFIED;
p->parent[0].mode = new->ce_mode;
- hashcpy(p->parent[0].sha1, new->sha1);
+ hashcpy(p->parent[0].oid.hash, new->sha1);
p->parent[1].status = DIFF_STATUS_MODIFIED;
p->parent[1].mode = old->ce_mode;
- hashcpy(p->parent[1].sha1, old->sha1);
+ hashcpy(p->parent[1].oid.hash, old->sha1);
show_combined_diff(p, 2, revs->dense_combined_merges, revs);
free(p);
return 0;
@@ -376,6 +381,13 @@ static void do_oneway_diff(struct unpack_trees_options *o,
struct rev_info *revs = o->unpack_data;
int match_missing, cached;
+ /* i-t-a entries do not actually exist in the index */
+ if (idx && (idx->ce_flags & CE_INTENT_TO_ADD)) {
+ idx = NULL;
+ if (!tree)
+ return; /* nothing to diff.. */
+ }
+
/* if the entry is not checked out, don't examine work tree */
cached = o->index_only ||
(idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
diff --git a/diff-no-index.c b/diff-no-index.c
index 265709b..0320605 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -97,8 +97,27 @@ static int queue_diff(struct diff_options *o,
if (get_mode(name1, &mode1) || get_mode(name2, &mode2))
return -1;
- if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
- return error("file/directory conflict: %s, %s", name1, name2);
+ if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2)) {
+ struct diff_filespec *d1, *d2;
+
+ if (S_ISDIR(mode1)) {
+ /* 2 is file that is created */
+ d1 = noindex_filespec(NULL, 0);
+ d2 = noindex_filespec(name2, mode2);
+ name2 = NULL;
+ mode2 = 0;
+ } else {
+ /* 1 is file that is deleted */
+ d1 = noindex_filespec(name1, mode1);
+ d2 = noindex_filespec(NULL, 0);
+ name1 = NULL;
+ mode1 = 0;
+ }
+ /* emit that file */
+ diff_queue(&diff_queued_diff, d1, d2);
+
+ /* and then let the entire directory be created or deleted */
+ }
if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
struct strbuf buffer1 = STRBUF_INIT;
@@ -182,12 +201,50 @@ static int queue_diff(struct diff_options *o,
}
}
+/* append basename of F to D */
+static void append_basename(struct strbuf *path, const char *dir, const char *file)
+{
+ const char *tail = strrchr(file, '/');
+
+ strbuf_addstr(path, dir);
+ while (path->len && path->buf[path->len - 1] == '/')
+ path->len--;
+ strbuf_addch(path, '/');
+ strbuf_addstr(path, tail ? tail + 1 : file);
+}
+
+/*
+ * DWIM "diff D F" into "diff D/F F" and "diff F D" into "diff F D/F"
+ * Note that we append the basename of F to D/, so "diff a/b/file D"
+ * becomes "diff a/b/file D/file", not "diff a/b/file D/a/b/file".
+ */
+static void fixup_paths(const char **path, struct strbuf *replacement)
+{
+ unsigned int isdir0, isdir1;
+
+ if (path[0] == file_from_standard_input ||
+ path[1] == file_from_standard_input)
+ return;
+ isdir0 = is_directory(path[0]);
+ isdir1 = is_directory(path[1]);
+ if (isdir0 == isdir1)
+ return;
+ if (isdir0) {
+ append_basename(replacement, path[0], path[1]);
+ path[0] = replacement->buf;
+ } else {
+ append_basename(replacement, path[1], path[0]);
+ path[1] = replacement->buf;
+ }
+}
+
void diff_no_index(struct rev_info *revs,
int argc, const char **argv,
const char *prefix)
{
int i, prefixlen;
const char *paths[2];
+ struct strbuf replacement = STRBUF_INIT;
diff_setup(&revs->diffopt);
for (i = 1; i < argc - 2; ) {
@@ -217,6 +274,9 @@ void diff_no_index(struct rev_info *revs,
p = xstrdup(prefix_filename(prefix, prefixlen, p));
paths[i] = p;
}
+
+ fixup_paths(paths, &replacement);
+
revs->diffopt.skip_stat_unmatch = 1;
if (!revs->diffopt.output_format)
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
@@ -235,6 +295,8 @@ void diff_no_index(struct rev_info *revs,
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
+ strbuf_release(&replacement);
+
/*
* The return code for --no-index imitates diff(1):
* 0 = no changes, 1 = changes, else error
diff --git a/diff.h b/diff.h
index b4a624d..f6fdf49 100644
--- a/diff.h
+++ b/diff.h
@@ -6,6 +6,7 @@
#include "tree-walk.h"
#include "pathspec.h"
+#include "object.h"
struct rev_info;
struct diff_options;
@@ -207,11 +208,11 @@ struct combine_diff_path {
struct combine_diff_path *next;
char *path;
unsigned int mode;
- unsigned char sha1[20];
+ struct object_id oid;
struct combine_diff_parent {
char status;
unsigned int mode;
- unsigned char sha1[20];
+ struct object_id oid;
} parent[FLEX_ARRAY];
};
#define combine_diff_path_size(n, l) \
diff --git a/dir.c b/dir.c
index 0943a81..4183acc 100644
--- a/dir.c
+++ b/dir.c
@@ -12,6 +12,7 @@
#include "refs.h"
#include "wildmatch.h"
#include "pathspec.h"
+#include "utf8.h"
struct path_simplify {
int len;
@@ -617,7 +618,12 @@ int add_excludes_from_file_to_list(const char *fname,
}
el->filebuf = buf;
+
+ if (skip_utf8_bom(&buf, size))
+ size -= buf - el->filebuf;
+
entry = buf;
+
for (i = 0; i < size; i++) {
if (buf[i] == '\n') {
if (entry != buf + i && entry[0] != '#') {
@@ -1665,18 +1671,19 @@ int remove_dir_recursively(struct strbuf *path, int flag)
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
- char *xdg_path;
dir->exclude_per_dir = ".gitignore";
+
+ /* core.excludefile defaulting to $XDG_HOME/git/ignore */
+ if (!excludes_file)
+ excludes_file = xdg_config_home("ignore");
+ if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
+ add_excludes_from_file(dir, excludes_file);
+
+ /* per repository user preference */
path = git_path("info/exclude");
- if (!excludes_file) {
- home_config_paths(NULL, &xdg_path, "ignore");
- excludes_file = xdg_path;
- }
if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file(dir, path);
- if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
- add_excludes_from_file(dir, excludes_file);
}
int remove_path(const char *name)
diff --git a/environment.c b/environment.c
index a40044c..61c685b 100644
--- a/environment.c
+++ b/environment.c
@@ -92,8 +92,9 @@ static char *work_tree;
static const char *namespace;
static size_t namespace_len;
-static const char *git_dir;
+static const char *git_dir, *git_common_dir;
static char *git_object_dir, *git_index_file, *git_graft_file;
+int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
/*
* Repository-local GIT_* environment variables; see cache.h for details.
@@ -111,6 +112,7 @@ const char * const local_repo_env[] = {
NO_REPLACE_OBJECTS_ENVIRONMENT,
GIT_PREFIX_ENVIRONMENT,
GIT_SHALLOW_FILE_ENVIRONMENT,
+ GIT_COMMON_DIR_ENVIRONMENT,
NULL
};
@@ -135,14 +137,23 @@ static char *expand_namespace(const char *raw_namespace)
return strbuf_detach(&buf, NULL);
}
-static char *git_path_from_env(const char *envvar, const char *path)
+static char *git_path_from_env(const char *envvar, const char *git_dir,
+ const char *path, int *fromenv)
{
const char *value = getenv(envvar);
- return value ? xstrdup(value) : git_pathdup("%s", path);
+ if (!value) {
+ char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2);
+ sprintf(buf, "%s/%s", git_dir, path);
+ return buf;
+ }
+ if (fromenv)
+ *fromenv = 1;
+ return xstrdup(value);
}
static void setup_git_env(void)
{
+ struct strbuf sb = STRBUF_INIT;
const char *gitfile;
const char *shallow_file;
@@ -151,9 +162,15 @@ static void setup_git_env(void)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
gitfile = read_gitfile(git_dir);
git_dir = xstrdup(gitfile ? gitfile : git_dir);
- git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects");
- git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index");
- git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts");
+ if (get_common_dir(&sb, git_dir))
+ git_common_dir_env = 1;
+ git_common_dir = strbuf_detach(&sb, NULL);
+ git_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,
+ "objects", &git_db_env);
+ git_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,
+ "index", &git_index_env);
+ git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_common_dir,
+ "info/grafts", &git_graft_env);
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
check_replace_refs = 0;
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
@@ -176,6 +193,11 @@ const char *get_git_dir(void)
return git_dir;
}
+const char *get_git_common_dir(void)
+{
+ return git_common_dir;
+}
+
const char *get_git_namespace(void)
{
if (!namespace)
diff --git a/exec_cmd.c b/exec_cmd.c
index 8ab37b5..e85f0fd 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -96,7 +96,6 @@ void setup_path(void)
struct strbuf new_path = STRBUF_INIT;
add_path(&new_path, git_exec_path());
- add_path(&new_path, argv0_path);
if (old_path)
strbuf_addstr(&new_path, old_path);
diff --git a/fast-import.c b/fast-import.c
index e78ca10..6378726 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -405,7 +405,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
static void write_crash_report(const char *err)
{
- char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
+ const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
FILE *rpt = fopen(loc, "w");
struct branch *b;
unsigned long lu;
@@ -3113,12 +3113,9 @@ static void parse_progress(void)
static char* make_fast_import_path(const char *path)
{
- struct strbuf abs_path = STRBUF_INIT;
-
if (!relative_marks_paths || is_absolute_path(path))
return xstrdup(path);
- strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
- return strbuf_detach(&abs_path, NULL);
+ return xstrdup(git_path("info/fast-import/%s", path));
}
static void option_import_marks(const char *marks,
diff --git a/git-am.sh b/git-am.sh
index a67d0f9..761befb 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -827,10 +827,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
continue
fi
- if test -x "$GIT_DIR"/hooks/applypatch-msg
+ hook="$(git rev-parse --git-path hooks/applypatch-msg)"
+ if test -x "$hook"
then
- "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
- stop_here $this
+ "$hook" "$dotest/final-commit" || stop_here $this
fi
if test -f "$dotest/final-commit"
@@ -904,9 +904,10 @@ did you forget to use 'git add'?"
stop_here_user_resolve $this
fi
- if test -x "$GIT_DIR"/hooks/pre-applypatch
+ hook="$(git rev-parse --git-path hooks/pre-applypatch)"
+ if test -x "$hook"
then
- "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+ "$hook" || stop_here $this
fi
tree=$(git write-tree) &&
@@ -933,18 +934,17 @@ did you forget to use 'git add'?"
echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
fi
- if test -x "$GIT_DIR"/hooks/post-applypatch
- then
- "$GIT_DIR"/hooks/post-applypatch
- fi
+ hook="$(git rev-parse --git-path hooks/post-applypatch)"
+ test -x "$hook" && "$hook"
go_next
done
if test -s "$dotest"/rewritten; then
git notes copy --for-rewrite=rebase < "$dotest"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ if test -x "$hook"; then
+ "$hook" rebase < "$dotest"/rewritten
fi
fi
diff --git a/git-compat-util.h b/git-compat-util.h
index bc8fc8c..b7a97fb 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -3,6 +3,23 @@
#define _FILE_OFFSET_BITS 64
+
+/* Derived from Linux "Features Test Macro" header
+ * Convenience macros to test the versions of gcc (or
+ * a compatible compiler).
+ * Use them like this:
+ * #if GIT_GNUC_PREREQ (2,8)
+ * ... code requiring gcc 2.8 or later ...
+ * #endif
+*/
+#if defined(__GNUC__) && defined(__GNUC_MINOR__)
+# define GIT_GNUC_PREREQ(maj, min) \
+ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+ #define GIT_GNUC_PREREQ(maj, min) 0
+#endif
+
+
#ifndef FLEX_ARRAY
/*
* See if our compiler is known to support flexible array members.
@@ -25,7 +42,42 @@
#endif
#endif
-#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+/*
+ * BUILD_ASSERT_OR_ZERO - assert a build-time dependency, as an expression.
+ * @cond: the compile-time condition which must be true.
+ *
+ * Your compile will fail if the condition isn't true, or can't be evaluated
+ * by the compiler. This can be used in an expression: its value is "0".
+ *
+ * Example:
+ * #define foo_to_char(foo) \
+ * ((char *)(foo) \
+ * + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
+ */
+#define BUILD_ASSERT_OR_ZERO(cond) \
+ (sizeof(char [1 - 2*!(cond)]) - 1)
+
+#if defined(__GNUC__) && (__GNUC__ >= 3)
+# if GIT_GNUC_PREREQ(3, 1)
+ /* &arr[0] degrades to a pointer: a different type from an array */
+# define BARF_UNLESS_AN_ARRAY(arr) \
+ BUILD_ASSERT_OR_ZERO(!__builtin_types_compatible_p(__typeof__(arr), \
+ __typeof__(&(arr)[0])))
+# else
+# define BARF_UNLESS_AN_ARRAY(arr) 0
+# endif
+#endif
+/*
+ * ARRAY_SIZE - get the number of elements in a visible array
+ * <at> x: the array whose size you want.
+ *
+ * This does not work on pointers, or arrays declared as [], or
+ * function parameters. With correct compiler support, such usage
+ * will cause a build error (see the build_assert_or_zero macro).
+ */
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]) + BARF_UNLESS_AN_ARRAY(x))
+
#define bitsizeof(x) (CHAR_BIT * sizeof(x))
#define maximum_signed_value_of_type(a) \
@@ -883,4 +935,10 @@ struct tm *git_gmtime_r(const time_t *, struct tm *);
# define SHELL_PATH "/bin/sh"
#endif
+#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
+#define flockfile(fh)
+#define funlockfile(fh)
+#define getc_unlocked(fh) getc(fh)
+#endif
+
#endif
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 73d367c..82ecb03 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -1162,7 +1162,7 @@ if ($orig_branch) {
die "Fast-forward update failed: $?\n" if $?;
}
else {
- system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
+ system(qw(git merge -m cvsimport), "$remote/$opt_o");
die "Could not merge $opt_o into the current branch.\n" if $?;
}
} else {
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index e6e99f5..5b3f63d 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -346,7 +346,15 @@ while read commit parents; do
die "parent filter failed: $filter_parent"
fi
- sed -e '1,/^$/d' <../commit | \
+ {
+ while read -r header_line && test -n "$header_line"
+ do
+ # skip header lines...
+ :;
+ done
+ # and output the actual commit message
+ cat
+ } <../commit |
eval "$filter_msg" > ../message ||
die "msg filter failed: $filter_msg"
workdir=$workdir @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
diff --git a/git-p4.py b/git-p4.py
index 549022e..41a77e6 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -368,7 +368,7 @@ def getP4OpenedType(file):
# Returns the perforce file type for the given file.
result = p4_read_pipe(["opened", wildcard_encode(file)])
- match = re.match(".*\((.+)\)\r?$", result)
+ match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
if match:
return match.group(1)
else:
@@ -502,12 +502,14 @@ def p4Cmd(cmd):
def p4Where(depotPath):
if not depotPath.endswith("/"):
depotPath += "/"
- depotPath = depotPath + "..."
- outputList = p4CmdList(["where", depotPath])
+ depotPathLong = depotPath + "..."
+ outputList = p4CmdList(["where", depotPathLong])
output = None
for entry in outputList:
if "depotFile" in entry:
- if entry["depotFile"] == depotPath:
+ # Search for the base client side depot path, as long as it starts with the branch's P4 path.
+ # The base path always ends with "/...".
+ if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
output = entry
break
elif "data" in entry:
@@ -740,17 +742,43 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
def originP4BranchesExist():
return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
-def p4ChangesForPaths(depotPaths, changeRange):
+def p4ChangesForPaths(depotPaths, changeRange, block_size):
assert depotPaths
- cmd = ['changes']
- for p in depotPaths:
- cmd += ["%s...%s" % (p, changeRange)]
- output = p4_read_pipe_lines(cmd)
+ assert block_size
+
+ # Parse the change range into start and end
+ if changeRange is None or changeRange == '':
+ changeStart = '@1'
+ changeEnd = '#head'
+ else:
+ parts = changeRange.split(',')
+ assert len(parts) == 2
+ changeStart = parts[0]
+ changeEnd = parts[1]
+ # Accumulate change numbers in a dictionary to avoid duplicates
changes = {}
- for line in output:
- changeNum = int(line.split(" ")[1])
- changes[changeNum] = True
+
+ for p in depotPaths:
+ # Retrieve changes a block at a time, to prevent running
+ # into a MaxScanRows error from the server.
+ start = changeStart
+ end = changeEnd
+ get_another_block = True
+ while get_another_block:
+ new_changes = []
+ cmd = ['changes']
+ cmd += ['-m', str(block_size)]
+ cmd += ["%s...%s,%s" % (p, start, end)]
+ for line in p4_read_pipe_lines(cmd):
+ changeNum = int(line.split(" ")[1])
+ new_changes.append(changeNum)
+ changes[changeNum] = True
+ if len(new_changes) == block_size:
+ get_another_block = True
+ end = '@' + str(min(new_changes))
+ else:
+ get_another_block = False
changelist = changes.keys()
changelist.sort()
@@ -1627,7 +1655,10 @@ class P4Submit(Command, P4UserMap):
if self.useClientSpec:
self.clientSpecDirs = getClientSpec()
- if self.useClientSpec:
+ # Check for the existance of P4 branches
+ branchesDetected = (len(p4BranchesInGit().keys()) > 1)
+
+ if self.useClientSpec and not branchesDetected:
# all files are relative to the client spec
self.clientPath = getClientRoot()
else:
@@ -1911,7 +1942,10 @@ class P4Sync(Command, P4UserMap):
optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
help="Import into refs/heads/ , not refs/remotes"),
- optparse.make_option("--max-changes", dest="maxChanges"),
+ optparse.make_option("--max-changes", dest="maxChanges",
+ help="Maximum number of changes to import"),
+ optparse.make_option("--changes-block-size", dest="changes_block_size", type="int",
+ help="Internal block size to use when iteratively calling p4 changes"),
optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
@@ -1940,6 +1974,7 @@ class P4Sync(Command, P4UserMap):
self.syncWithOrigin = True
self.importIntoRemotes = True
self.maxChanges = ""
+ self.changes_block_size = 500
self.keepRepoPath = False
self.depotPaths = None
self.p4BranchesInGit = []
@@ -2586,7 +2621,7 @@ class P4Sync(Command, P4UserMap):
branchPrefix = self.depotPaths[0] + branch + "/"
range = "@1,%s" % maxChange
#print "prefix" + branchPrefix
- changes = p4ChangesForPaths([branchPrefix], range)
+ changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)
if len(changes) <= 0:
return False
firstChange = changes[0]
@@ -3002,7 +3037,7 @@ class P4Sync(Command, P4UserMap):
if self.verbose:
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
self.changeRange)
- changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
+ changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)
if len(self.maxChanges) > 0:
changes = changes[:min(int(self.maxChanges), len(changes))]
diff --git a/git-pull.sh b/git-pull.sh
index 30fc6f5..9005171 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -231,7 +231,7 @@ test true = "$rebase" && {
if ! git rev-parse -q --verify HEAD >/dev/null
then
# On an unborn branch
- if test -f "$GIT_DIR/index"
+ if test -f "$(git rev-parse --git-path index)"
then
die "$(gettext "updating an unborn branch with changes added to the index")"
fi
@@ -314,7 +314,6 @@ then
fi
fi
-merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
case "$rebase" in
true)
eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
@@ -325,7 +324,7 @@ true)
eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
eval="$eval $gpg_sign_args"
- eval="$eval \"\$merge_name\" HEAD $merge_head"
+ eval="$eval FETCH_HEAD"
;;
esac
eval "exec $eval"
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index f7deeb0..bab0dcc 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -132,6 +132,16 @@ mark_action_done () {
fi
}
+# Put the last action marked done at the beginning of the todo list
+# again. If there has not been an action marked done yet, leave the list of
+# items on the todo list unchanged.
+reschedule_last_action () {
+ tail -n 1 "$done" | cat - "$todo" >"$todo".new
+ sed -e \$d <"$done" >"$done".new
+ mv -f "$todo".new "$todo"
+ mv -f "$done".new "$done"
+}
+
append_todo_help () {
git stripspace --comment-lines >>"$todo" <<\EOF
@@ -252,6 +262,12 @@ pick_one () {
output eval git cherry-pick \
${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
"$strategy_args" $empty_args $ff "$@"
+
+ # If cherry-pick dies it leaves the to-be-picked commit unrecorded. Reschedule
+ # previous task so this commit is not lost.
+ ret=$?
+ case "$ret" in [01]) ;; *) reschedule_last_action ;; esac
+ return $ret
}
pick_one_preserving_merges () {
@@ -642,9 +658,9 @@ do_next () {
git notes copy --for-rewrite=rebase < "$rewritten_list" ||
true # we don't care if this copying failed
} &&
- if test -x "$GIT_DIR"/hooks/post-rewrite &&
- test -s "$rewritten_list"; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ if test -x "$hook" && test -s "$rewritten_list"; then
+ "$hook" rebase < "$rewritten_list"
true # we don't care if this hook failed
fi &&
warn "Successfully rebased and updated $head_name."
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index d3fb67d..2cc2a6d 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -94,10 +94,8 @@ finish_rb_merge () {
if test -s "$state_dir"/rewritten
then
git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite
- then
- "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
- fi
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
fi
say All done.
}
diff --git a/git-rebase.sh b/git-rebase.sh
index 55da9db..1757404 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -202,9 +202,9 @@ run_specific_rebase () {
run_pre_rebase_hook () {
if test -z "$ok_to_skip_pre_rebase" &&
- test -x "$GIT_DIR/hooks/pre-rebase"
+ test -x "$(git rev-parse --git-path hooks/pre-rebase)"
then
- "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
+ "$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
die "$(gettext "The pre-rebase hook refused to rebase.")"
fi
}
@@ -582,7 +582,7 @@ then
# Lazily switch to the target branch if needed...
test -z "$switch_to" ||
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \
- git checkout "$switch_to" --
+ git checkout -q "$switch_to" --
say "$(eval_gettext "Current branch \$branch_name is up to date.")"
finish_rebase
exit 0
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index c42c6e6..4691fbc 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -344,7 +344,7 @@ git_dir_init () {
echo >&2 "Unable to determine absolute path of git directory"
exit 1
}
- : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+ : ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"}
}
if test -z "$NONGIT_OK"
diff --git a/git-stash.sh b/git-stash.sh
index d4cf818..7911f30 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -20,7 +20,7 @@ require_work_tree
cd_to_toplevel
TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
trap 'rm -f "$TMP-"* "$TMPindex"' 0
ref_stash=refs/stash
@@ -184,7 +184,7 @@ store_stash () {
fi
# Make sure the reflog for stash is kept.
- : >>"$GIT_DIR/logs/$ref_stash"
+ : >>"$(git rev-parse --git-path logs/$ref_stash)"
git update-ref -m "$stash_msg" $ref_stash $w_commit
ret=$?
test $ret != 0 && test -z $quiet &&
@@ -259,7 +259,7 @@ save_stash () {
say "$(gettext "No local changes to save")"
exit 0
fi
- test -f "$GIT_DIR/logs/$ref_stash" ||
+ test -f "$(git rev-parse --git-path logs/$ref_stash)" ||
clear_stash || die "$(gettext "Cannot initialize stash")"
create_stash "$stash_msg" $untracked
@@ -442,6 +442,8 @@ apply_stash () {
assert_stash_like "$@"
git update-index -q --refresh || die "$(gettext "unable to refresh index")"
+ git diff-index --cached --quiet --ignore-submodules HEAD -- ||
+ die "$(gettext "Cannot apply stash: Your index contains uncommitted changes.")"
# current index state
c_tree=$(git write-tree) ||
diff --git a/git.c b/git.c
index 42a4ee5..44374b1 100644
--- a/git.c
+++ b/git.c
@@ -382,7 +382,7 @@ static struct cmd_struct commands[] = {
{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
{ "check-mailmap", cmd_check_mailmap, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
- { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
+ { "checkout", cmd_checkout, RUN_SETUP },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "cherry", cmd_cherry, RUN_SETUP },
diff --git a/hex.c b/hex.c
index cfd9d72..899b74a 100644
--- a/hex.c
+++ b/hex.c
@@ -38,7 +38,7 @@ const signed char hexval_table[256] = {
int get_sha1_hex(const char *hex, unsigned char *sha1)
{
int i;
- for (i = 0; i < 20; i++) {
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
unsigned int val;
/*
* hex[1]=='\0' is caught when val is checked below,
@@ -56,15 +56,20 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
return 0;
}
+int get_oid_hex(const char *hex, struct object_id *oid)
+{
+ return get_sha1_hex(hex, oid->hash);
+}
+
char *sha1_to_hex(const unsigned char *sha1)
{
static int bufno;
- static char hexbuffer[4][41];
+ static char hexbuffer[4][GIT_SHA1_HEXSZ + 1];
static const char hex[] = "0123456789abcdef";
char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
int i;
- for (i = 0; i < 20; i++) {
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
unsigned int val = *sha1++;
*buf++ = hex[val >> 4];
*buf++ = hex[val & 0xf];
@@ -73,3 +78,8 @@ char *sha1_to_hex(const unsigned char *sha1)
return buffer;
}
+
+char *oid_to_hex(const struct object_id *oid)
+{
+ return sha1_to_hex(oid->hash);
+}
diff --git a/http.c b/http.c
index 4b179f6..f0c5bbc 100644
--- a/http.c
+++ b/http.c
@@ -36,6 +36,7 @@ char curl_errorstr[CURL_ERROR_SIZE];
static int curl_ssl_verify = -1;
static int curl_ssl_try;
static const char *ssl_cert;
+static const char *ssl_cipherlist;
#if LIBCURL_VERSION_NUM >= 0x070903
static const char *ssl_key;
#endif
@@ -187,6 +188,8 @@ static int http_options(const char *var, const char *value, void *cb)
curl_ssl_verify = git_config_bool(var, value);
return 0;
}
+ if (!strcmp("http.sslcipherlist", var))
+ return git_config_string(&ssl_cipherlist, var, value);
if (!strcmp("http.sslcert", var))
return git_config_string(&ssl_cert, var, value);
#if LIBCURL_VERSION_NUM >= 0x070903
@@ -361,6 +364,13 @@ static CURL *get_curl_handle(void)
if (http_proactive_auth)
init_curl_http_auth(result);
+ if (getenv("GIT_SSL_CIPHER_LIST"))
+ ssl_cipherlist = getenv("GIT_SSL_CIPHER_LIST");
+
+ if (ssl_cipherlist != NULL && *ssl_cipherlist)
+ curl_easy_setopt(result, CURLOPT_SSL_CIPHER_LIST,
+ ssl_cipherlist);
+
if (ssl_cert != NULL)
curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
if (has_cert_password())
diff --git a/line-log.c b/line-log.c
index a490efe..a5ed9e3 100644
--- a/line-log.c
+++ b/line-log.c
@@ -575,7 +575,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
name_part = skip_range_arg(item->string);
if (!name_part || *name_part != ':' || !name_part[1])
- die("-L argument '%s' not of the form start,end:file",
+ die("-L argument not 'start,end:file' or ':funcname:file': %s",
item->string);
range_part = xstrndup(item->string, name_part - item->string);
name_part++;
@@ -1099,6 +1099,7 @@ static int process_all_files(struct line_log_data **range_out,
rg->pair = diff_filepair_dup(queue->queue[i]);
memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges));
}
+ free(pairdiff);
}
return changed;
diff --git a/lockfile.c b/lockfile.c
index 9889277..30e65e9 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -157,6 +157,80 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
return lk->fd;
}
+static int sleep_microseconds(long us)
+{
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = us;
+ return select(0, NULL, NULL, NULL, &tv);
+}
+
+/*
+ * Constants defining the gaps between attempts to lock a file. The
+ * first backoff period is approximately INITIAL_BACKOFF_MS
+ * milliseconds. The longest backoff period is approximately
+ * (BACKOFF_MAX_MULTIPLIER * INITIAL_BACKOFF_MS) milliseconds.
+ */
+#define INITIAL_BACKOFF_MS 1L
+#define BACKOFF_MAX_MULTIPLIER 1000
+
+/*
+ * Try locking path, retrying with quadratic backoff for at least
+ * timeout_ms milliseconds. If timeout_ms is 0, try locking the file
+ * exactly once. If timeout_ms is -1, try indefinitely.
+ */
+static int lock_file_timeout(struct lock_file *lk, const char *path,
+ int flags, long timeout_ms)
+{
+ int n = 1;
+ int multiplier = 1;
+ long remaining_us = 0;
+ static int random_initialized = 0;
+
+ if (timeout_ms == 0)
+ return lock_file(lk, path, flags);
+
+ if (!random_initialized) {
+ srandom((unsigned int)getpid());
+ random_initialized = 1;
+ }
+
+ if (timeout_ms > 0) {
+ /* avoid overflow */
+ if (timeout_ms <= LONG_MAX / 1000)
+ remaining_us = timeout_ms * 1000;
+ else
+ remaining_us = LONG_MAX;
+ }
+
+ while (1) {
+ long backoff_ms, wait_us;
+ int fd;
+
+ fd = lock_file(lk, path, flags);
+
+ if (fd >= 0)
+ return fd; /* success */
+ else if (errno != EEXIST)
+ return -1; /* failure other than lock held */
+ else if (timeout_ms > 0 && remaining_us <= 0)
+ return -1; /* failure due to timeout */
+
+ backoff_ms = multiplier * INITIAL_BACKOFF_MS;
+ /* back off for between 0.75*backoff_ms and 1.25*backoff_ms */
+ wait_us = (750 + random() % 500) * backoff_ms;
+ sleep_microseconds(wait_us);
+ remaining_us -= wait_us;
+
+ /* Recursion: (n+1)^2 = n^2 + 2n + 1 */
+ multiplier += 2*n + 1;
+ if (multiplier > BACKOFF_MAX_MULTIPLIER)
+ multiplier = BACKOFF_MAX_MULTIPLIER;
+ else
+ n++;
+ }
+}
+
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
if (err == EEXIST) {
@@ -179,9 +253,10 @@ NORETURN void unable_to_lock_die(const char *path, int err)
}
/* This should return a meaningful errno on failure */
-int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
+int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path,
+ int flags, long timeout_ms)
{
- int fd = lock_file(lk, path, flags);
+ int fd = lock_file_timeout(lk, path, flags, timeout_ms);
if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
unable_to_lock_die(path, errno);
return fd;
diff --git a/lockfile.h b/lockfile.h
index cd2ec95..b4abc61 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -74,8 +74,20 @@ struct lock_file {
extern void unable_to_lock_message(const char *path, int err,
struct strbuf *buf);
extern NORETURN void unable_to_lock_die(const char *path, int err);
-extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
-extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
+extern int hold_lock_file_for_update_timeout(
+ struct lock_file *lk, const char *path,
+ int flags, long timeout_ms);
+
+static inline int hold_lock_file_for_update(
+ struct lock_file *lk, const char *path,
+ int flags)
+{
+ return hold_lock_file_for_update_timeout(lk, path, flags, 0);
+}
+
+extern int hold_lock_file_for_append(struct lock_file *lk, const char *path,
+ int flags);
+
extern FILE *fdopen_lock_file(struct lock_file *, const char *mode);
extern char *get_locked_file_path(struct lock_file *);
extern int commit_lock_file_to(struct lock_file *, const char *path);
diff --git a/log-tree.c b/log-tree.c
index 2c1ed0f..8dba7be 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -13,6 +13,8 @@
#include "line-log.h"
static struct decoration name_decoration = { "object names" };
+static int decoration_loaded;
+static int decoration_flags;
static char decoration_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
@@ -92,6 +94,8 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
struct object *obj;
enum decoration_type type = DECORATION_NONE;
+ assert(cb_data == NULL);
+
if (starts_with(refname, "refs/replace/")) {
unsigned char original_sha1[20];
if (!check_replace_refs)
@@ -121,8 +125,6 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
else if (!strcmp(refname, "HEAD"))
type = DECORATION_REF_HEAD;
- if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
- refname = prettify_refname(refname);
add_name_decoration(type, refname, obj);
while (obj->type == OBJ_TAG) {
obj = ((struct tag *)obj)->tagged;
@@ -137,7 +139,7 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
{
- struct commit *commit = lookup_commit(graft->sha1);
+ struct commit *commit = lookup_commit(graft->oid.hash);
if (!commit)
return 0;
add_name_decoration(DECORATION_GRAFTED, "grafted", &commit->object);
@@ -146,11 +148,11 @@ static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
void load_ref_decorations(int flags)
{
- static int loaded;
- if (!loaded) {
- loaded = 1;
- for_each_ref(add_ref_decoration, &flags);
- head_ref(add_ref_decoration, &flags);
+ if (!decoration_loaded) {
+ decoration_loaded = 1;
+ decoration_flags = flags;
+ for_each_ref(add_ref_decoration, NULL);
+ head_ref(add_ref_decoration, NULL);
for_each_commit_graft(add_graft_decoration, NULL);
}
}
@@ -196,7 +198,8 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d
branch_name = resolve_ref_unsafe("HEAD", 0, unused, &rru_flags);
if (!(rru_flags & REF_ISSYMREF))
return NULL;
- if (!skip_prefix(branch_name, "refs/heads/", &branch_name))
+
+ if (!starts_with(branch_name, "refs/"))
return NULL;
/* OK, do we have that ref in the list? */
@@ -209,6 +212,14 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d
return NULL;
}
+static void show_name(struct strbuf *sb, const struct name_decoration *decoration)
+{
+ if (decoration_flags == DECORATE_SHORT_REFS)
+ strbuf_addstr(sb, prettify_refname(decoration->name));
+ else
+ strbuf_addstr(sb, decoration->name);
+}
+
/*
* The caller makes sure there is no funny color before calling.
* format_decorations_extended makes sure the same after return.
@@ -246,7 +257,7 @@ void format_decorations_extended(struct strbuf *sb,
if (decoration->type == DECORATION_REF_TAG)
strbuf_addstr(sb, "tag: ");
- strbuf_addstr(sb, decoration->name);
+ show_name(sb, decoration);
if (current_and_HEAD &&
decoration->type == DECORATION_REF_HEAD) {
@@ -255,7 +266,7 @@ void format_decorations_extended(struct strbuf *sb,
strbuf_addstr(sb, " -> ");
strbuf_addstr(sb, color_reset);
strbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type));
- strbuf_addstr(sb, current_and_HEAD->name);
+ show_name(sb, current_and_HEAD);
}
strbuf_addstr(sb, color_reset);
diff --git a/notes-merge.c b/notes-merge.c
index 109ff4e..0b2b82c 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -280,7 +280,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
"(%s exists).", git_path("NOTES_MERGE_*"));
}
- if (safe_create_leading_directories(git_path(
+ if (safe_create_leading_directories_const(git_path(
NOTES_MERGE_WORKTREE "/.test")))
die_errno("unable to create directory %s",
git_path(NOTES_MERGE_WORKTREE));
@@ -295,8 +295,8 @@ static void write_buf_to_worktree(const unsigned char *obj,
const char *buf, unsigned long size)
{
int fd;
- char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
- if (safe_create_leading_directories(path))
+ const char *path = git_path(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);
diff --git a/object.c b/object.c
index 23d6c96..980ac5f 100644
--- a/object.c
+++ b/object.c
@@ -41,7 +41,8 @@ int type_from_string_gently(const char *str, ssize_t len, int gentle)
len = strlen(str);
for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
- if (!strncmp(str, object_type_strings[i], len))
+ if (!strncmp(str, object_type_strings[i], len) &&
+ object_type_strings[i][len] == '\0')
return i;
if (gentle)
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 365f9d9..62a98cc 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -986,6 +986,8 @@ void test_bitmap_walk(struct rev_info *revs)
fprintf(stderr, "OK!\n");
else
fprintf(stderr, "Mismatch!\n");
+
+ free(result);
}
static int rebuild_bitmap(uint32_t *reposition,
diff --git a/path.c b/path.c
index 595da81..10f4cbf 100644
--- a/path.c
+++ b/path.c
@@ -4,6 +4,7 @@
#include "cache.h"
#include "strbuf.h"
#include "string-list.h"
+#include "dir.h"
static int get_st_mode_bits(const char *path, int *mode)
{
@@ -16,11 +17,15 @@ static int get_st_mode_bits(const char *path, int *mode)
static char bad_path[] = "/bad-path/";
-static char *get_pathname(void)
+static struct strbuf *get_pathname(void)
{
- static char pathname_array[4][PATH_MAX];
+ static struct strbuf pathname_array[4] = {
+ STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+ };
static int index;
- return pathname_array[3 & ++index];
+ struct strbuf *sb = &pathname_array[3 & ++index];
+ strbuf_reset(sb);
+ return sb;
}
static char *cleanup_path(char *path)
@@ -34,6 +39,13 @@ static char *cleanup_path(char *path)
return path;
}
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+ char *path = cleanup_path(sb->buf);
+ if (path > sb->buf)
+ strbuf_remove(sb, 0, path - sb->buf);
+}
+
char *mksnpath(char *buf, size_t n, const char *fmt, ...)
{
va_list args;
@@ -49,152 +61,192 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
return cleanup_path(buf);
}
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+static int dir_prefix(const char *buf, const char *dir)
{
- const char *git_dir = get_git_dir();
- size_t len;
+ int len = strlen(dir);
+ return !strncmp(buf, dir, len) &&
+ (is_dir_sep(buf[len]) || buf[len] == '\0');
+}
- len = strlen(git_dir);
- if (n < len + 1)
- goto bad;
- memcpy(buf, git_dir, len);
- if (len && !is_dir_sep(git_dir[len-1]))
- buf[len++] = '/';
- len += vsnprintf(buf + len, n - len, fmt, args);
- if (len >= n)
- goto bad;
- return cleanup_path(buf);
-bad:
- strlcpy(buf, bad_path, n);
- return buf;
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+ int len = strlen(dir);
+ if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+ return 0;
+ while (is_dir_sep(buf[len]))
+ len++;
+ return !strcmp(buf + len, file);
}
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+static void replace_dir(struct strbuf *buf, int len, const char *newdir)
{
- char *ret;
- va_list args;
- va_start(args, fmt);
- ret = vsnpath(buf, n, fmt, args);
- va_end(args);
- return ret;
+ int newlen = strlen(newdir);
+ int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
+ !is_dir_sep(newdir[newlen - 1]);
+ if (need_sep)
+ len--; /* keep one char, to be replaced with '/' */
+ strbuf_splice(buf, 0, len, newdir, newlen);
+ if (need_sep)
+ buf->buf[newlen] = '/';
}
-char *git_pathdup(const char *fmt, ...)
+static const char *common_list[] = {
+ "/branches", "/hooks", "/info", "!/logs", "/lost-found",
+ "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
+ "config", "!gc.pid", "packed-refs", "shallow",
+ NULL
+};
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+ char *base = buf->buf + git_dir_len;
+ const char **p;
+
+ if (is_dir_file(base, "logs", "HEAD") ||
+ is_dir_file(base, "info", "sparse-checkout"))
+ return; /* keep this in $GIT_DIR */
+ for (p = common_list; *p; p++) {
+ const char *path = *p;
+ int is_dir = 0;
+ if (*path == '!')
+ path++;
+ if (*path == '/') {
+ path++;
+ is_dir = 1;
+ }
+ if (is_dir && dir_prefix(base, path)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ if (!is_dir && !strcmp(base, path)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ }
+}
+
+void report_linked_checkout_garbage(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char **p;
+ int len;
+
+ if (!git_common_dir_env)
+ return;
+ strbuf_addf(&sb, "%s/", get_git_dir());
+ len = sb.len;
+ for (p = common_list; *p; p++) {
+ const char *path = *p;
+ if (*path == '!')
+ continue;
+ strbuf_setlen(&sb, len);
+ strbuf_addstr(&sb, path);
+ if (file_exists(sb.buf))
+ report_garbage("unused in linked checkout", sb.buf);
+ }
+ strbuf_release(&sb);
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+ const char *base = buf->buf + git_dir_len;
+ if (git_graft_env && is_dir_file(base, "info", "grafts"))
+ strbuf_splice(buf, 0, buf->len,
+ get_graft_file(), strlen(get_graft_file()));
+ else if (git_index_env && !strcmp(base, "index"))
+ strbuf_splice(buf, 0, buf->len,
+ get_index_file(), strlen(get_index_file()));
+ else if (git_db_env && dir_prefix(base, "objects"))
+ replace_dir(buf, git_dir_len + 7, get_object_directory());
+ else if (git_common_dir_env)
+ update_common_dir(buf, git_dir_len);
+}
+
+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
+{
+ int gitdir_len;
+ strbuf_addstr(buf, get_git_dir());
+ if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+ strbuf_addch(buf, '/');
+ gitdir_len = buf->len;
+ strbuf_vaddf(buf, fmt, args);
+ adjust_git_path(buf, gitdir_len);
+ strbuf_cleanup_path(buf);
+}
+
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
{
- char path[PATH_MAX], *ret;
va_list args;
va_start(args, fmt);
- ret = vsnpath(path, sizeof(path), fmt, args);
+ do_git_path(sb, fmt, args);
va_end(args);
- return xstrdup(ret);
}
-char *mkpathdup(const char *fmt, ...)
+const char *git_path(const char *fmt, ...)
{
- char *path;
- struct strbuf sb = STRBUF_INIT;
+ struct strbuf *pathname = get_pathname();
va_list args;
-
va_start(args, fmt);
- strbuf_vaddf(&sb, fmt, args);
+ do_git_path(pathname, fmt, args);
va_end(args);
- path = xstrdup(cleanup_path(sb.buf));
-
- strbuf_release(&sb);
- return path;
+ return pathname->buf;
}
-char *mkpath(const char *fmt, ...)
+char *git_pathdup(const char *fmt, ...)
{
+ struct strbuf path = STRBUF_INIT;
va_list args;
- unsigned len;
- char *pathname = get_pathname();
-
va_start(args, fmt);
- len = vsnprintf(pathname, PATH_MAX, fmt, args);
+ do_git_path(&path, fmt, args);
va_end(args);
- if (len >= PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ return strbuf_detach(&path, NULL);
}
-char *git_path(const char *fmt, ...)
+char *mkpathdup(const char *fmt, ...)
{
- char *pathname = get_pathname();
+ struct strbuf sb = STRBUF_INIT;
va_list args;
- char *ret;
-
va_start(args, fmt);
- ret = vsnpath(pathname, PATH_MAX, fmt, args);
+ strbuf_vaddf(&sb, fmt, args);
va_end(args);
- return ret;
+ strbuf_cleanup_path(&sb);
+ return strbuf_detach(&sb, NULL);
}
-void home_config_paths(char **global, char **xdg, char *file)
+const char *mkpath(const char *fmt, ...)
{
- char *xdg_home = getenv("XDG_CONFIG_HOME");
- char *home = getenv("HOME");
- char *to_free = NULL;
-
- if (!home) {
- if (global)
- *global = NULL;
- } else {
- if (!xdg_home) {
- to_free = mkpathdup("%s/.config", home);
- xdg_home = to_free;
- }
- if (global)
- *global = mkpathdup("%s/.gitconfig", home);
- }
-
- if (xdg) {
- if (!xdg_home)
- *xdg = NULL;
- else
- *xdg = mkpathdup("%s/git/%s", xdg_home, file);
- }
-
- free(to_free);
+ va_list args;
+ struct strbuf *pathname = get_pathname();
+ va_start(args, fmt);
+ strbuf_vaddf(pathname, fmt, args);
+ va_end(args);
+ return cleanup_path(pathname->buf);
}
-char *git_path_submodule(const char *path, const char *fmt, ...)
+const char *git_path_submodule(const char *path, const char *fmt, ...)
{
- char *pathname = get_pathname();
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf *buf = get_pathname();
const char *git_dir;
va_list args;
- unsigned len;
- len = strlen(path);
- if (len > PATH_MAX-100)
- return bad_path;
+ strbuf_addstr(buf, path);
+ if (buf->len && buf->buf[buf->len - 1] != '/')
+ strbuf_addch(buf, '/');
+ strbuf_addstr(buf, ".git");
- strbuf_addstr(&buf, path);
- if (len && path[len-1] != '/')
- strbuf_addch(&buf, '/');
- strbuf_addstr(&buf, ".git");
-
- git_dir = read_gitfile(buf.buf);
+ git_dir = read_gitfile(buf->buf);
if (git_dir) {
- strbuf_reset(&buf);
- strbuf_addstr(&buf, git_dir);
+ strbuf_reset(buf);
+ strbuf_addstr(buf, git_dir);
}
- strbuf_addch(&buf, '/');
-
- if (buf.len >= PATH_MAX)
- return bad_path;
- memcpy(pathname, buf.buf, buf.len + 1);
-
- strbuf_release(&buf);
- len = strlen(pathname);
+ strbuf_addch(buf, '/');
va_start(args, fmt);
- len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ strbuf_vaddf(buf, fmt, args);
va_end(args);
- if (len >= PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ strbuf_cleanup_path(buf);
+ return buf->buf;
}
int validate_headref(const char *path)
@@ -851,3 +903,18 @@ int is_ntfs_dotgit(const char *name)
len = -1;
}
}
+
+char *xdg_config_home(const char *filename)
+{
+ const char *home, *config_home;
+
+ assert(filename);
+ config_home = getenv("XDG_CONFIG_HOME");
+ if (config_home && *config_home)
+ return mkpathdup("%s/git/%s", config_home, filename);
+
+ home = getenv("HOME");
+ if (home)
+ return mkpathdup("%s/.config/git/%s", home, filename);
+ return NULL;
+}
diff --git a/progress.c b/progress.c
index 412e6b1..43d9228 100644
--- a/progress.c
+++ b/progress.c
@@ -72,6 +72,11 @@ static void clear_progress_signal(void)
progress_update = 0;
}
+static int is_foreground_fd(int fd)
+{
+ return getpgid(0) == tcgetpgrp(fd);
+}
+
static int display(struct progress *progress, unsigned n, const char *done)
{
const char *eol, *tp;
@@ -98,16 +103,21 @@ static int display(struct progress *progress, unsigned n, const char *done)
unsigned percent = n * 100 / progress->total;
if (percent != progress->last_percent || progress_update) {
progress->last_percent = percent;
- fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
- progress->title, percent, n,
- progress->total, tp, eol);
- fflush(stderr);
+ if (is_foreground_fd(fileno(stderr)) || done) {
+ fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
+ progress->title, percent, n,
+ progress->total, tp, eol);
+ fflush(stderr);
+ }
progress_update = 0;
return 1;
}
} else if (progress_update) {
- fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol);
- fflush(stderr);
+ if (is_foreground_fd(fileno(stderr)) || done) {
+ fprintf(stderr, "%s: %u%s%s",
+ progress->title, n, tp, eol);
+ fflush(stderr);
+ }
progress_update = 0;
return 1;
}
diff --git a/reachable.c b/reachable.c
index a647267..69fa685 100644
--- a/reachable.c
+++ b/reachable.c
@@ -142,10 +142,12 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,
data.revs = revs;
data.timestamp = timestamp;
- r = for_each_loose_object(add_recent_loose, &data);
+ r = for_each_loose_object(add_recent_loose, &data,
+ FOR_EACH_OBJECT_LOCAL_ONLY);
if (r)
return r;
- return for_each_packed_object(add_recent_packed, &data);
+ return for_each_packed_object(add_recent_packed, &data,
+ FOR_EACH_OBJECT_LOCAL_ONLY);
}
void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
diff --git a/refs.c b/refs.c
index 47e4e53..8480d8d 100644
--- a/refs.c
+++ b/refs.c
@@ -11,7 +11,6 @@ struct ref_lock {
char *orig_ref_name;
struct lock_file *lk;
unsigned char old_sha1[20];
- int lock_fd;
};
/*
@@ -58,6 +57,12 @@ static unsigned char refname_disposition[256] = {
#define REF_HAVE_OLD 0x10
/*
+ * Used as a flag in ref_update::flags when the lockfile needs to be
+ * committed.
+ */
+#define REF_NEEDS_COMMIT 0x20
+
+/*
* Try to read one refname component from the front of refname.
* Return the length of the component found, or -1 if the component is
* not legal. It is legal if it is something reasonable to have under
@@ -263,7 +268,7 @@ struct ref_dir {
* presence of an empty subdirectory does not block the creation of a
* similarly-named reference. (The fact that reference names with the
* same leading components can conflict *with each other* is a
- * separate issue that is regulated by is_refname_available().)
+ * separate issue that is regulated by verify_refname_available().)
*
* Please note that the name field contains the fully-qualified
* reference (or subdirectory) name. Space could be saved by only
@@ -344,8 +349,6 @@ static struct ref_entry *create_ref_entry(const char *refname,
if (check_name &&
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
die("Reference has invalid format: '%s'", refname);
- if (!check_name && !refname_is_safe(refname))
- die("Reference has invalid name: '%s'", refname);
len = strlen(refname) + 1;
ref = xmalloc(sizeof(struct ref_entry) + len);
hashcpy(ref->u.value.sha1, sha1);
@@ -841,121 +844,181 @@ static void prime_ref_dir(struct ref_dir *dir)
}
}
-static int entry_matches(struct ref_entry *entry, const struct string_list *list)
-{
- return list && string_list_has_string(list, entry->name);
-}
-
struct nonmatching_ref_data {
const struct string_list *skip;
- struct ref_entry *found;
+ const char *conflicting_refname;
};
static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
{
struct nonmatching_ref_data *data = vdata;
- if (entry_matches(entry, data->skip))
+ if (data->skip && string_list_has_string(data->skip, entry->name))
return 0;
- data->found = entry;
+ data->conflicting_refname = entry->name;
return 1;
}
-static void report_refname_conflict(struct ref_entry *entry,
- const char *refname)
-{
- error("'%s' exists; cannot create '%s'", entry->name, refname);
-}
-
/*
- * Return true iff a reference named refname could be created without
- * conflicting with the name of an existing reference in dir. If
- * skip is non-NULL, ignore potential conflicts with refs in skip
- * (e.g., because they are scheduled for deletion in the same
- * operation).
+ * Return 0 if a reference named refname could be created without
+ * conflicting with the name of an existing reference in dir.
+ * Otherwise, return a negative value and write an explanation to err.
+ * If extras is non-NULL, it is a list of additional refnames with
+ * which refname is not allowed to conflict. If skip is non-NULL,
+ * ignore potential conflicts with refs in skip (e.g., because they
+ * are scheduled for deletion in the same operation). Behavior is
+ * undefined if the same name is listed in both extras and skip.
*
* Two reference names conflict if one of them exactly matches the
- * leading components of the other; e.g., "foo/bar" conflicts with
- * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
- * "foo/barbados".
+ * leading components of the other; e.g., "refs/foo/bar" conflicts
+ * with both "refs/foo" and with "refs/foo/bar/baz" but not with
+ * "refs/foo/bar" or "refs/foo/barbados".
*
- * skip must be sorted.
+ * extras and skip must be sorted.
*/
-static int is_refname_available(const char *refname,
- const struct string_list *skip,
- struct ref_dir *dir)
+static int verify_refname_available(const char *refname,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ struct ref_dir *dir,
+ struct strbuf *err)
{
const char *slash;
- size_t len;
int pos;
- char *dirname;
+ struct strbuf dirname = STRBUF_INIT;
+ int ret = -1;
+
+ /*
+ * For the sake of comments in this function, suppose that
+ * refname is "refs/foo/bar".
+ */
+ assert(err);
+
+ strbuf_grow(&dirname, strlen(refname) + 1);
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+ /* Expand dirname to the new prefix, not including the trailing slash: */
+ strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);
+
/*
- * We are still at a leading dir of the refname; we are
- * looking for a conflict with a leaf entry.
- *
- * If we find one, we still must make sure it is
- * not in "skip".
+ * We are still at a leading dir of the refname (e.g.,
+ * "refs/foo"; if there is a reference with that name,
+ * it is a conflict, *unless* it is in skip.
*/
- pos = search_ref_dir(dir, refname, slash - refname);
- if (pos >= 0) {
- struct ref_entry *entry = dir->entries[pos];
- if (entry_matches(entry, skip))
- return 1;
- report_refname_conflict(entry, refname);
- return 0;
+ if (dir) {
+ pos = search_ref_dir(dir, dirname.buf, dirname.len);
+ if (pos >= 0 &&
+ (!skip || !string_list_has_string(skip, dirname.buf))) {
+ /*
+ * We found a reference whose name is
+ * a proper prefix of refname; e.g.,
+ * "refs/foo", and is not in skip.
+ */
+ strbuf_addf(err, "'%s' exists; cannot create '%s'",
+ dirname.buf, refname);
+ goto cleanup;
+ }
}
+ if (extras && string_list_has_string(extras, dirname.buf) &&
+ (!skip || !string_list_has_string(skip, dirname.buf))) {
+ strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
+ refname, dirname.buf);
+ goto cleanup;
+ }
/*
* Otherwise, we can try to continue our search with
- * the next component; if we come up empty, we know
- * there is nothing under this whole prefix.
+ * the next component. So try to look up the
+ * directory, e.g., "refs/foo/". If we come up empty,
+ * we know there is nothing under this whole prefix,
+ * but even in that case we still have to continue the
+ * search for conflicts with extras.
*/
- pos = search_ref_dir(dir, refname, slash + 1 - refname);
- if (pos < 0)
- return 1;
-
- dir = get_ref_dir(dir->entries[pos]);
+ strbuf_addch(&dirname, '/');
+ if (dir) {
+ pos = search_ref_dir(dir, dirname.buf, dirname.len);
+ if (pos < 0) {
+ /*
+ * There was no directory "refs/foo/",
+ * so there is nothing under this
+ * whole prefix. So there is no need
+ * to continue looking for conflicting
+ * references. But we need to continue
+ * looking for conflicting extras.
+ */
+ dir = NULL;
+ } else {
+ dir = get_ref_dir(dir->entries[pos]);
+ }
+ }
}
/*
- * We are at the leaf of our refname; we want to
- * make sure there are no directories which match it.
+ * We are at the leaf of our refname (e.g., "refs/foo/bar").
+ * There is no point in searching for a reference with that
+ * name, because a refname isn't considered to conflict with
+ * itself. But we still need to check for references whose
+ * names are in the "refs/foo/bar/" namespace, because they
+ * *do* conflict.
*/
- len = strlen(refname);
- dirname = xmallocz(len + 1);
- sprintf(dirname, "%s/", refname);
- pos = search_ref_dir(dir, dirname, len + 1);
- free(dirname);
+ strbuf_addstr(&dirname, refname + dirname.len);
+ strbuf_addch(&dirname, '/');
+
+ if (dir) {
+ pos = search_ref_dir(dir, dirname.buf, dirname.len);
+
+ if (pos >= 0) {
+ /*
+ * We found a directory named "$refname/"
+ * (e.g., "refs/foo/bar/"). It is a problem
+ * iff it contains any ref that is not in
+ * "skip".
+ */
+ struct nonmatching_ref_data data;
+
+ data.skip = skip;
+ data.conflicting_refname = NULL;
+ dir = get_ref_dir(dir->entries[pos]);
+ sort_ref_dir(dir);
+ if (do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) {
+ strbuf_addf(err, "'%s' exists; cannot create '%s'",
+ data.conflicting_refname, refname);
+ goto cleanup;
+ }
+ }
+ }
- if (pos >= 0) {
+ if (extras) {
/*
- * We found a directory named "refname". It is a
- * problem iff it contains any ref that is not
- * in "skip".
+ * Check for entries in extras that start with
+ * "$refname/". We do that by looking for the place
+ * where "$refname/" would be inserted in extras. If
+ * there is an entry at that position that starts with
+ * "$refname/" and is not in skip, then we have a
+ * conflict.
*/
- struct ref_entry *entry = dir->entries[pos];
- struct ref_dir *dir = get_ref_dir(entry);
- struct nonmatching_ref_data data;
+ for (pos = string_list_find_insert_index(extras, dirname.buf, 0);
+ pos < extras->nr; pos++) {
+ const char *extra_refname = extras->items[pos].string;
- data.skip = skip;
- sort_ref_dir(dir);
- if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))
- return 1;
+ if (!starts_with(extra_refname, dirname.buf))
+ break;
- report_refname_conflict(data.found, refname);
- return 0;
+ if (!skip || !string_list_has_string(skip, extra_refname)) {
+ strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
+ refname, extra_refname);
+ goto cleanup;
+ }
+ }
}
- /*
- * There is no point in searching for another leaf
- * node which matches it; such an entry would be the
- * ref we are looking for, not a conflict.
- */
- return 1;
+ /* No conflicts were found */
+ ret = 0;
+
+cleanup:
+ strbuf_release(&dirname);
+ return ret;
}
struct packed_ref_cache {
@@ -1178,6 +1241,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
int flag = REF_ISPACKED;
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname))
+ die("packed refname is dangerous: %s", refname);
hashclr(sha1);
flag |= REF_BAD_NAME | REF_ISBROKEN;
}
@@ -1323,6 +1388,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
}
if (check_refname_format(refname.buf,
REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname.buf))
+ die("loose refname is dangerous: %s", refname.buf);
hashclr(sha1);
flag |= REF_BAD_NAME | REF_ISBROKEN;
}
@@ -1382,7 +1449,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
{
int fd, len;
char buffer[128], *p;
- char *path;
+ const char *path;
if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
return -1;
@@ -1475,7 +1542,11 @@ static int resolve_missing_loose_ref(const char *refname,
}
/* This function needs to return a meaningful errno on failure */
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
+static const char *resolve_ref_unsafe_1(const char *refname,
+ int resolve_flags,
+ unsigned char *sha1,
+ int *flags,
+ struct strbuf *sb_path)
{
int depth = MAXDEPTH;
ssize_t len;
@@ -1506,7 +1577,7 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
bad_name = 1;
}
for (;;) {
- char path[PATH_MAX];
+ const char *path;
struct stat st;
char *buf;
int fd;
@@ -1516,7 +1587,9 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
return NULL;
}
- git_snpath(path, sizeof(path), "%s", refname);
+ strbuf_reset(sb_path);
+ strbuf_git_path(sb_path, "%s", refname);
+ path = sb_path->buf;
/*
* We might have to loop back here to avoid a race
@@ -1643,6 +1716,16 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
}
}
+const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
+ unsigned char *sha1, int *flags)
+{
+ struct strbuf sb_path = STRBUF_INIT;
+ const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
+ sha1, flags, &sb_path);
+ strbuf_release(&sb_path);
+ return ret;
+}
+
char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
{
return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
@@ -2271,10 +2354,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
*/
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_p,
+ struct strbuf *err)
{
- char *ref_file;
+ const char *ref_file;
const char *orig_refname = refname;
struct ref_lock *lock;
int last_errno = 0;
@@ -2283,8 +2368,9 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int resolve_flags = 0;
int attempts_remaining = 3;
+ assert(err);
+
lock = xcalloc(1, sizeof(struct ref_lock));
- lock->lock_fd = -1;
if (mustexist)
resolve_flags |= RESOLVE_REF_READING;
@@ -2305,7 +2391,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
ref_file = git_path("%s", orig_refname);
if (remove_empty_directories(ref_file)) {
last_errno = errno;
- error("there are still refs under '%s'", orig_refname);
+
+ if (!verify_refname_available(orig_refname, extras, skip,
+ get_loose_refs(&ref_cache), err))
+ strbuf_addf(err, "there are still refs under '%s'",
+ orig_refname);
+
goto error_return;
}
refname = resolve_ref_unsafe(orig_refname, resolve_flags,
@@ -2315,8 +2406,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
*type_p = type;
if (!refname) {
last_errno = errno;
- error("unable to resolve reference %s: %s",
- orig_refname, strerror(errno));
+ if (last_errno != ENOTDIR ||
+ !verify_refname_available(orig_refname, extras, skip,
+ get_loose_refs(&ref_cache), err))
+ strbuf_addf(err, "unable to resolve reference %s: %s",
+ orig_refname, strerror(last_errno));
+
goto error_return;
}
/*
@@ -2326,7 +2421,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
* our refname.
*/
if (is_null_sha1(lock->old_sha1) &&
- !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
+ verify_refname_available(refname, extras, skip,
+ get_packed_refs(&ref_cache), err)) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -2343,7 +2439,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
ref_file = git_path("%s", refname);
retry:
- switch (safe_create_leading_directories(ref_file)) {
+ switch (safe_create_leading_directories_const(ref_file)) {
case SCLD_OK:
break; /* success */
case SCLD_VANISHED:
@@ -2352,12 +2448,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
/* fall through */
default:
last_errno = errno;
- error("unable to create directory for %s", ref_file);
+ strbuf_addf(err, "unable to create directory for %s", ref_file);
goto error_return;
}
- lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
- if (lock->lock_fd < 0) {
+ if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) {
last_errno = errno;
if (errno == ENOENT && --attempts_remaining > 0)
/*
@@ -2367,10 +2462,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
*/
goto retry;
else {
- struct strbuf err = STRBUF_INIT;
- unable_to_lock_message(ref_file, errno, &err);
- error("%s", err.buf);
- strbuf_release(&err);
+ unable_to_lock_message(ref_file, errno, err);
goto error_return;
}
}
@@ -2413,9 +2505,19 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
/* This should return a meaningful errno on failure */
int lock_packed_refs(int flags)
{
+ static int timeout_configured = 0;
+ static int timeout_value = 1000;
+
struct packed_ref_cache *packed_ref_cache;
- if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+ if (!timeout_configured) {
+ git_config_get_int("core.packedrefstimeout", &timeout_value);
+ timeout_configured = 1;
+ }
+
+ if (hold_lock_file_for_update_timeout(
+ &packlock, git_path("packed-refs"),
+ flags, timeout_value) < 0)
return -1;
/*
* Get the current packed-refs while holding the lock. If the
@@ -2721,7 +2823,7 @@ static int rename_tmp_log(const char *newrefname)
int attempts_remaining = 4;
retry:
- switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+ switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
case SCLD_OK:
break; /* success */
case SCLD_VANISHED:
@@ -2764,17 +2866,25 @@ static int rename_tmp_log(const char *newrefname)
static int rename_ref_available(const char *oldname, const char *newname)
{
struct string_list skip = STRING_LIST_INIT_NODUP;
+ struct strbuf err = STRBUF_INIT;
int ret;
string_list_insert(&skip, oldname);
- ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache))
- && is_refname_available(newname, &skip, get_loose_refs(&ref_cache));
+ ret = !verify_refname_available(newname, NULL, &skip,
+ get_packed_refs(&ref_cache), &err)
+ && !verify_refname_available(newname, NULL, &skip,
+ get_loose_refs(&ref_cache), &err);
+ if (!ret)
+ error("%s", err.buf);
+
string_list_clear(&skip, 0);
+ strbuf_release(&err);
return ret;
}
-static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,
- const char *logmsg);
+static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1);
+static int commit_ref_update(struct ref_lock *lock,
+ const unsigned char *sha1, const char *logmsg);
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
@@ -2784,6 +2894,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
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);
@@ -2826,13 +2937,16 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
logmoved = log;
- lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err);
if (!lock) {
- error("unable to lock %s for update", newrefname);
+ error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ strbuf_release(&err);
goto rollback;
}
hashcpy(lock->old_sha1, orig_sha1);
- if (write_ref_sha1(lock, orig_sha1, logmsg)) {
+
+ if (write_ref_to_lockfile(lock, orig_sha1) ||
+ commit_ref_update(lock, orig_sha1, logmsg)) {
error("unable to write current sha1 into %s", newrefname);
goto rollback;
}
@@ -2840,15 +2954,17 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err);
if (!lock) {
- error("unable to lock %s for rollback", oldrefname);
+ error("unable to lock %s for rollback: %s", oldrefname, err.buf);
+ strbuf_release(&err);
goto rollbacklog;
}
flag = log_all_ref_updates;
log_all_ref_updates = 0;
- if (write_ref_sha1(lock, orig_sha1, NULL))
+ if (write_ref_to_lockfile(lock, orig_sha1) ||
+ commit_ref_update(lock, orig_sha1, NULL))
error("unable to write current sha1 into %s", oldrefname);
log_all_ref_updates = flag;
@@ -2868,7 +2984,6 @@ static int close_ref(struct ref_lock *lock)
{
if (close_lock_file(lock->lk))
return -1;
- lock->lock_fd = -1;
return 0;
}
@@ -2876,7 +2991,6 @@ static int commit_ref(struct ref_lock *lock)
{
if (commit_lock_file(lock->lk))
return -1;
- lock->lock_fd = -1;
return 0;
}
@@ -2907,11 +3021,15 @@ static int copy_msg(char *buf, const char *msg)
}
/* This function must set a meaningful errno on failure */
-int log_ref_setup(const char *refname, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
{
int logfd, oflags = O_APPEND | O_WRONLY;
+ char *logfile;
- git_snpath(logfile, bufsize, "logs/%s", refname);
+ strbuf_git_path(sb_logfile, "logs/%s", refname);
+ logfile = sb_logfile->buf;
+ /* make sure the rest of the function can't change "logfile" */
+ sb_logfile = NULL;
if (log_all_ref_updates &&
(starts_with(refname, "refs/heads/") ||
starts_with(refname, "refs/remotes/") ||
@@ -2982,18 +3100,22 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
return 0;
}
-static int log_ref_write(const char *refname, const unsigned char *old_sha1,
- const unsigned char *new_sha1, const char *msg)
+static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
+ const unsigned char *new_sha1, const char *msg,
+ struct strbuf *sb_log_file)
{
int logfd, result, oflags = O_APPEND | O_WRONLY;
- char log_file[PATH_MAX];
+ char *log_file;
if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository();
- result = log_ref_setup(refname, log_file, sizeof(log_file));
+ result = log_ref_setup(refname, sb_log_file);
if (result)
return result;
+ log_file = sb_log_file->buf;
+ /* make sure the rest of the function can't change "log_file" */
+ sb_log_file = NULL;
logfd = open(log_file, oflags);
if (logfd < 0)
@@ -3016,17 +3138,26 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
return 0;
}
+static int log_ref_write(const char *refname, const unsigned char *old_sha1,
+ const unsigned char *new_sha1, const char *msg)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);
+ strbuf_release(&sb);
+ return ret;
+}
+
int is_branch(const char *refname)
{
return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
}
/*
- * Write sha1 into the ref specified by the lock. Make sure that errno
- * is sane on error.
+ * Write sha1 into the open lockfile, then close the lockfile. On
+ * errors, rollback the lockfile and set errno to reflect the problem.
*/
-static int write_ref_sha1(struct ref_lock *lock,
- const unsigned char *sha1, const char *logmsg)
+static int write_ref_to_lockfile(struct ref_lock *lock,
+ const unsigned char *sha1)
{
static char term = '\n';
struct object *o;
@@ -3046,8 +3177,8 @@ static int write_ref_sha1(struct ref_lock *lock,
errno = EINVAL;
return -1;
}
- if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
- write_in_full(lock->lock_fd, &term, 1) != 1 ||
+ if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
+ write_in_full(lock->lk->fd, &term, 1) != 1 ||
close_ref(lock) < 0) {
int save_errno = errno;
error("Couldn't write %s", lock->lk->filename.buf);
@@ -3055,6 +3186,17 @@ static int write_ref_sha1(struct ref_lock *lock,
errno = save_errno;
return -1;
}
+ return 0;
+}
+
+/*
+ * Commit a change to a loose reference that has already been written
+ * to the loose reference lockfile. Also update the reflogs if
+ * necessary, using the specified lockmsg (which can be NULL).
+ */
+static int commit_ref_update(struct ref_lock *lock,
+ const unsigned char *sha1, const char *logmsg)
+{
clear_loose_ref_cache(&ref_cache);
if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
@@ -3695,25 +3837,18 @@ int update_ref(const char *msg, const char *refname,
return 0;
}
-static int ref_update_compare(const void *r1, const void *r2)
-{
- const struct ref_update * const *u1 = r1;
- const struct ref_update * const *u2 = r2;
- return strcmp((*u1)->refname, (*u2)->refname);
-}
-
-static int ref_update_reject_duplicates(struct ref_update **updates, int n,
+static int ref_update_reject_duplicates(struct string_list *refnames,
struct strbuf *err)
{
- int i;
+ int i, n = refnames->nr;
assert(err);
for (i = 1; i < n; i++)
- if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
+ if (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) {
strbuf_addf(err,
"Multiple updates for ref '%s' not allowed.",
- updates[i]->refname);
+ refnames->items[i].string);
return 1;
}
return 0;
@@ -3727,6 +3862,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
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;
assert(err);
@@ -3738,63 +3874,101 @@ int ref_transaction_commit(struct ref_transaction *transaction,
return 0;
}
- /* Copy, sort, and reject duplicate refs */
- qsort(updates, n, sizeof(*updates), ref_update_compare);
- if (ref_update_reject_duplicates(updates, n, err)) {
+ /* 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);
+ string_list_sort(&affected_refnames);
+ if (ref_update_reject_duplicates(&affected_refnames, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
- /* Acquire all locks while verifying old values */
+ /*
+ * 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];
- unsigned int flags = update->flags;
- if ((flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1))
- flags |= REF_DELETING;
+ 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),
- NULL,
- flags,
- &update->type);
+ &affected_refnames, NULL,
+ update->flags,
+ &update->type,
+ err);
if (!update->lock) {
+ char *reason;
+
ret = (errno == ENOTDIR)
? TRANSACTION_NAME_CONFLICT
: TRANSACTION_GENERIC_ERROR;
- strbuf_addf(err, "Cannot lock the ref '%s'.",
- update->refname);
+ reason = strbuf_detach(err, NULL);
+ strbuf_addf(err, "Cannot lock ref '%s': %s",
+ update->refname, reason);
+ free(reason);
goto cleanup;
}
- }
-
- /* Perform updates first so live commits remain referenced */
- for (i = 0; i < n; i++) {
- struct ref_update *update = updates[i];
- int flags = update->flags;
-
- if ((flags & REF_HAVE_NEW) && !is_null_sha1(update->new_sha1)) {
+ 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_sha1, update->new_sha1)) {
+ if (!overwriting_symref &&
+ !hashcmp(update->lock->old_sha1, update->new_sha1)) {
/*
* The reference already has the desired
* value, so we don't need to write it.
*/
- unlock_ref(update->lock);
+ } else if (write_ref_to_lockfile(update->lock,
+ update->new_sha1)) {
+ /*
+ * The lock was freed upon failure of
+ * write_ref_to_lockfile():
+ */
+ update->lock = NULL;
+ strbuf_addf(err, "Cannot update the ref '%s'.",
+ update->refname);
+ 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)) {
+ /* freed by commit_ref_update(): */
update->lock = NULL;
- } else if (write_ref_sha1(update->lock, update->new_sha1,
- update->msg)) {
- update->lock = NULL; /* freed by write_ref_sha1 */
strbuf_addf(err, "Cannot update the ref '%s'.",
update->refname);
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
} else {
- /* freed by write_ref_sha1(): */
+ /* freed by commit_ref_update(): */
update->lock = NULL;
}
}
@@ -3803,15 +3977,14 @@ int ref_transaction_commit(struct ref_transaction *transaction,
/* Perform deletes now that updates are safely completed */
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
- int flags = update->flags;
- if ((flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) {
+ if (update->flags & REF_DELETING) {
if (delete_ref_loose(update->lock, update->type, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
- if (!(flags & REF_ISPRUNING))
+ if (!(update->flags & REF_ISPRUNING))
string_list_append(&refs_to_delete,
update->lock->ref_name);
}
@@ -3832,6 +4005,7 @@ cleanup:
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
string_list_clear(&refs_to_delete, 0);
+ string_list_clear(&affected_refnames, 0);
return ret;
}
@@ -4021,6 +4195,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
char *log_file;
int status = 0;
int type;
+ struct strbuf err = STRBUF_INIT;
memset(&cb, 0, sizeof(cb));
cb.flags = flags;
@@ -4032,9 +4207,12 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
* reference itself, plus we might need to update the
* reference if --updateref was specified:
*/
- lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, &type);
- if (!lock)
- return error("cannot lock ref '%s'", refname);
+ lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err);
+ if (!lock) {
+ error("cannot lock ref '%s': %s", refname, err.buf);
+ strbuf_release(&err);
+ return -1;
+ }
if (!reflog_exists(refname)) {
unlock_ref(lock);
return 0;
@@ -4084,9 +4262,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
status |= error("couldn't write %s: %s", log_file,
strerror(errno));
} else if (update &&
- (write_in_full(lock->lock_fd,
+ (write_in_full(lock->lk->fd,
sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
- write_str_in_full(lock->lock_fd, "\n") != 1 ||
+ write_str_in_full(lock->lk->fd, "\n") != 1 ||
close_ref(lock) < 0)) {
status |= error("couldn't write %s",
lock->lk->filename.buf);
diff --git a/refs.h b/refs.h
index cf642e6..6d7d9b4 100644
--- a/refs.h
+++ b/refs.h
@@ -191,7 +191,7 @@ extern int peel_ref(const char *refname, unsigned char *sha1);
/*
* Setup reflog before using. Set errno to something meaningful on failure.
*/
-int log_ref_setup(const char *refname, char *logfile, int bufsize);
+int log_ref_setup(const char *refname, struct strbuf *logfile);
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned int flags,
diff --git a/revision.c b/revision.c
index 6399a04..7ddbaa0 100644
--- a/revision.c
+++ b/revision.c
@@ -345,14 +345,24 @@ static struct commit *handle_commit(struct rev_info *revs,
die("%s is unknown object", name);
}
-static int everybody_uninteresting(struct commit_list *orig)
+static int everybody_uninteresting(struct commit_list *orig,
+ struct commit **interesting_cache)
{
struct commit_list *list = orig;
+
+ if (*interesting_cache) {
+ struct commit *commit = *interesting_cache;
+ if (!(commit->object.flags & UNINTERESTING))
+ return 0;
+ }
+
while (list) {
struct commit *commit = list->item;
list = list->next;
if (commit->object.flags & UNINTERESTING)
continue;
+ if (interesting_cache)
+ *interesting_cache = commit;
return 0;
}
return 1;
@@ -940,7 +950,8 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
/* How many extra uninteresting commits we want to see.. */
#define SLOP 5
-static int still_interesting(struct commit_list *src, unsigned long date, int slop)
+static int still_interesting(struct commit_list *src, unsigned long date, int slop,
+ struct commit **interesting_cache)
{
/*
* No source list at all? We're definitely done..
@@ -959,7 +970,7 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
* Does the source list still have interesting commits in
* it? Definitely not done..
*/
- if (!everybody_uninteresting(src))
+ if (!everybody_uninteresting(src, interesting_cache))
return SLOP;
/* Ok, we're closing in.. */
@@ -1078,6 +1089,7 @@ static int limit_list(struct rev_info *revs)
struct commit_list *newlist = NULL;
struct commit_list **p = &newlist;
struct commit_list *bottom = NULL;
+ struct commit *interesting_cache = NULL;
if (revs->ancestry_path) {
bottom = collect_bottom_commits(list);
@@ -1094,6 +1106,9 @@ static int limit_list(struct rev_info *revs)
list = list->next;
free(entry);
+ if (commit == interesting_cache)
+ interesting_cache = NULL;
+
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
if (add_parents_to_list(revs, commit, &list, NULL) < 0)
@@ -1102,7 +1117,7 @@ static int limit_list(struct rev_info *revs)
mark_parents_uninteresting(commit);
if (revs->show_all)
p = &commit_list_insert(commit, p)->next;
- slop = still_interesting(list, date, slop);
+ slop = still_interesting(list, date, slop, &interesting_cache);
if (slop)
continue;
/* If showing all, add the whole pending list to the end */
diff --git a/run-command.c b/run-command.c
index aad03ab..4d73e90 100644
--- a/run-command.c
+++ b/run-command.c
@@ -795,9 +795,9 @@ int finish_async(struct async *async)
#endif
}
-char *find_hook(const char *name)
+const char *find_hook(const char *name)
{
- char *path = git_path("hooks/%s", name);
+ const char *path = git_path("hooks/%s", name);
if (access(path, X_OK) < 0)
path = NULL;
diff --git a/run-command.h b/run-command.h
index 263b966..1103805 100644
--- a/run-command.h
+++ b/run-command.h
@@ -52,7 +52,7 @@ int start_command(struct child_process *);
int finish_command(struct child_process *);
int run_command(struct child_process *);
-extern char *find_hook(const char *name);
+extern const char *find_hook(const char *name);
LAST_ARG_MUST_BE_NULL
extern int run_hook_le(const char *const *env, const char *name, ...);
extern int run_hook_ve(const char *const *env, const char *name, va_list args);
diff --git a/send-pack.c b/send-pack.c
index 2e07ac3..2a64fec 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -182,7 +182,7 @@ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *c
{
struct strbuf *sb = cb;
if (graft->nr_parent == -1)
- packet_buf_write(sb, "shallow %s\n", sha1_to_hex(graft->sha1));
+ packet_buf_write(sb, "shallow %s\n", oid_to_hex(&graft->oid));
return 0;
}
diff --git a/setup.c b/setup.c
index 979b13f..863ddfd 100644
--- a/setup.c
+++ b/setup.c
@@ -140,7 +140,9 @@ int check_filename(const char *prefix, const char *arg)
if (arg[2] == '\0') /* ":/" is root dir, always exists */
return 1;
name = arg + 2;
- } else if (prefix)
+ } else if (!no_wildcard(arg))
+ return 1;
+ else if (prefix)
name = prefix_filename(prefix, strlen(prefix), arg);
else
name = arg;
@@ -224,6 +226,36 @@ void verify_non_filename(const char *prefix, const char *arg)
"'git <command> [<revision>...] -- [<file>...]'", arg);
}
+int get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+ struct strbuf data = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+ int ret = 0;
+ if (git_common_dir) {
+ strbuf_addstr(sb, git_common_dir);
+ return 1;
+ }
+ strbuf_addf(&path, "%s/commondir", gitdir);
+ if (file_exists(path.buf)) {
+ if (strbuf_read_file(&data, path.buf, 0) <= 0)
+ die_errno(_("failed to read %s"), path.buf);
+ while (data.len && (data.buf[data.len - 1] == '\n' ||
+ data.buf[data.len - 1] == '\r'))
+ data.len--;
+ data.buf[data.len] = '\0';
+ strbuf_reset(&path);
+ if (!is_absolute_path(data.buf))
+ strbuf_addf(&path, "%s/", gitdir);
+ strbuf_addbuf(&path, &data);
+ strbuf_addstr(sb, real_path(path.buf));
+ ret = 1;
+ } else
+ strbuf_addstr(sb, gitdir);
+ strbuf_release(&data);
+ strbuf_release(&path);
+ return ret;
+}
/*
* Test if it looks like we're at a git directory.
@@ -238,31 +270,40 @@ void verify_non_filename(const char *prefix, const char *arg)
*/
int is_git_directory(const char *suspect)
{
- char path[PATH_MAX];
- size_t len = strlen(suspect);
+ struct strbuf path = STRBUF_INIT;
+ int ret = 0;
+ size_t len;
+
+ /* Check worktree-related signatures */
+ strbuf_addf(&path, "%s/HEAD", suspect);
+ if (validate_headref(path.buf))
+ goto done;
- if (PATH_MAX <= len + strlen("/objects"))
- die("Too long path: %.*s", 60, suspect);
- strcpy(path, suspect);
+ strbuf_reset(&path);
+ get_common_dir(&path, suspect);
+ len = path.len;
+
+ /* Check non-worktree-related signatures */
if (getenv(DB_ENVIRONMENT)) {
if (access(getenv(DB_ENVIRONMENT), X_OK))
- return 0;
+ goto done;
}
else {
- strcpy(path + len, "/objects");
- if (access(path, X_OK))
- return 0;
+ strbuf_setlen(&path, len);
+ strbuf_addstr(&path, "/objects");
+ if (access(path.buf, X_OK))
+ goto done;
}
- strcpy(path + len, "/refs");
- if (access(path, X_OK))
- return 0;
+ strbuf_setlen(&path, len);
+ strbuf_addstr(&path, "/refs");
+ if (access(path.buf, X_OK))
+ goto done;
- strcpy(path + len, "/HEAD");
- if (validate_headref(path))
- return 0;
-
- return 1;
+ ret = 1;
+done:
+ strbuf_release(&path);
+ return ret;
}
int is_inside_git_dir(void)
@@ -304,9 +345,28 @@ void setup_work_tree(void)
initialized = 1;
}
+static int check_repo_format(const char *var, const char *value, void *cb)
+{
+ if (strcmp(var, "core.repositoryformatversion") == 0)
+ repository_format_version = git_config_int(var, value);
+ else if (strcmp(var, "core.sharedrepository") == 0)
+ shared_repository = git_config_perm(var, value);
+ return 0;
+}
+
static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
{
- char repo_config[PATH_MAX+1];
+ struct strbuf sb = STRBUF_INIT;
+ const char *repo_config;
+ config_fn_t fn;
+ int ret = 0;
+
+ if (get_common_dir(&sb, gitdir))
+ fn = check_repo_format;
+ else
+ fn = check_repository_format_version;
+ strbuf_addstr(&sb, "/config");
+ repo_config = sb.buf;
/*
* git_config() can't be used here because it calls git_pathdup()
@@ -317,8 +377,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
* Use a gentler version of git_config() to check if this repo
* is a good one.
*/
- snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
- git_config_early(check_repository_format_version, NULL, repo_config);
+ git_config_early(fn, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
die ("Expected git repo version <= %d, found %d",
@@ -327,9 +386,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
GIT_REPO_VERSION, repository_format_version);
warning("Please upgrade Git");
*nongit_ok = -1;
- return -1;
+ ret = -1;
}
- return 0;
+ strbuf_release(&sb);
+ return ret;
+}
+
+static void update_linked_gitdir(const char *gitfile, const char *gitdir)
+{
+ struct strbuf path = STRBUF_INIT;
+ struct stat st;
+
+ strbuf_addf(&path, "%s/gitfile", gitdir);
+ if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
+ write_file(path.buf, 0, "%s\n", gitfile);
+ strbuf_release(&path);
}
/*
@@ -380,6 +451,8 @@ const char *read_gitfile(const char *path)
if (!is_git_directory(dir))
die("Not a git repository: %s", dir);
+
+ update_linked_gitdir(path, dir);
path = real_path(dir);
free(buf);
@@ -799,11 +872,10 @@ int git_config_perm(const char *var, const char *value)
int check_repository_format_version(const char *var, const char *value, void *cb)
{
- if (strcmp(var, "core.repositoryformatversion") == 0)
- repository_format_version = git_config_int(var, value);
- else if (strcmp(var, "core.sharedrepository") == 0)
- shared_repository = git_config_perm(var, value);
- else if (strcmp(var, "core.bare") == 0) {
+ int ret = check_repo_format(var, value, cb);
+ if (ret)
+ return ret;
+ if (strcmp(var, "core.bare") == 0) {
is_bare_repository_cfg = git_config_bool(var, value);
if (is_bare_repository_cfg == 1)
inside_work_tree = -1;
diff --git a/sha1_file.c b/sha1_file.c
index 88f06ba..ccc6dac 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -405,7 +405,7 @@ void add_to_alternates_file(const char *reference)
{
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
- char *alt = mkpath("%s\n", reference);
+ const char *alt = mkpath("%s\n", reference);
write_or_die(fd, alt, strlen(alt));
if (commit_lock_file(lock))
die("could not close alternates file");
@@ -1564,6 +1564,40 @@ int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long ma
return git_inflate(stream, 0);
}
+static int unpack_sha1_header_to_strbuf(git_zstream *stream, unsigned char *map,
+ unsigned long mapsize, void *buffer,
+ unsigned long bufsiz, struct strbuf *header)
+{
+ int status;
+
+ status = unpack_sha1_header(stream, map, mapsize, buffer, bufsiz);
+
+ /*
+ * Check if entire header is unpacked in the first iteration.
+ */
+ if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
+ return 0;
+
+ /*
+ * buffer[0..bufsiz] was not large enough. Copy the partial
+ * result out to header, and then append the result of further
+ * reading the stream.
+ */
+ strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
+ stream->next_out = buffer;
+ stream->avail_out = bufsiz;
+
+ do {
+ status = git_inflate(stream, 0);
+ strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
+ if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
+ return 0;
+ stream->next_out = buffer;
+ stream->avail_out = bufsiz;
+ } while (status != Z_STREAM_END);
+ return -1;
+}
+
static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
{
int bytes = strlen(buffer) + 1;
@@ -1614,27 +1648,38 @@ static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long s
* too permissive for what we want to check. So do an anal
* object header parse by hand.
*/
-int parse_sha1_header(const char *hdr, unsigned long *sizep)
+static int parse_sha1_header_extended(const char *hdr, struct object_info *oi,
+ unsigned int flags)
{
- char type[10];
- int i;
+ const char *type_buf = hdr;
unsigned long size;
+ int type, type_len = 0;
/*
- * The type can be at most ten bytes (including the
- * terminating '\0' that we add), and is followed by
+ * The type can be of any size but is followed by
* a space.
*/
- i = 0;
for (;;) {
char c = *hdr++;
if (c == ' ')
break;
- type[i++] = c;
- if (i >= sizeof(type))
- return -1;
+ type_len++;
}
- type[i] = 0;
+
+ type = type_from_string_gently(type_buf, type_len, 1);
+ if (oi->typename)
+ strbuf_add(oi->typename, type_buf, type_len);
+ /*
+ * Set type to 0 if its an unknown object and
+ * we're obtaining the type using '--allow-unkown-type'
+ * option.
+ */
+ if ((flags & LOOKUP_UNKNOWN_OBJECT) && (type < 0))
+ type = 0;
+ else if (type < 0)
+ die("invalid object type");
+ if (oi->typep)
+ *oi->typep = type;
/*
* The length must follow immediately, and be in canonical
@@ -1652,12 +1697,24 @@ int parse_sha1_header(const char *hdr, unsigned long *sizep)
size = size * 10 + c;
}
}
- *sizep = size;
+
+ if (oi->sizep)
+ *oi->sizep = size;
/*
* The length must be followed by a zero byte
*/
- return *hdr ? -1 : type_from_string(type);
+ return *hdr ? -1 : type;
+}
+
+int parse_sha1_header(const char *hdr, unsigned long *sizep)
+{
+ struct object_info oi;
+
+ oi.sizep = sizep;
+ oi.typename = NULL;
+ oi.typep = NULL;
+ return parse_sha1_header_extended(hdr, &oi, LOOKUP_REPLACE_OBJECT);
}
static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
@@ -2473,10 +2530,8 @@ static int fill_pack_entry(const unsigned char *sha1,
* answer, as it may have been deleted since the index was
* loaded!
*/
- if (!is_pack_valid(p)) {
- warning("packfile %s cannot be accessed", p->pack_name);
+ if (!is_pack_valid(p))
return 0;
- }
e->offset = offset;
e->p = p;
hashcpy(e->sha1, sha1);
@@ -2524,13 +2579,15 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
}
static int sha1_loose_object_info(const unsigned char *sha1,
- struct object_info *oi)
+ struct object_info *oi,
+ int flags)
{
- int status;
- unsigned long mapsize, size;
+ int status = 0;
+ unsigned long mapsize;
void *map;
git_zstream stream;
char hdr[32];
+ struct strbuf hdrbuf = STRBUF_INIT;
if (oi->delta_base_sha1)
hashclr(oi->delta_base_sha1);
@@ -2543,7 +2600,7 @@ static int sha1_loose_object_info(const unsigned char *sha1,
* return value implicitly indicates whether the
* object even exists.
*/
- if (!oi->typep && !oi->sizep) {
+ if (!oi->typep && !oi->typename && !oi->sizep) {
struct stat st;
if (stat_sha1_file(sha1, &st) < 0)
return -1;
@@ -2557,17 +2614,26 @@ static int sha1_loose_object_info(const unsigned char *sha1,
return -1;
if (oi->disk_sizep)
*oi->disk_sizep = mapsize;
- if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
+ if ((flags & LOOKUP_UNKNOWN_OBJECT)) {
+ if (unpack_sha1_header_to_strbuf(&stream, map, mapsize, hdr, sizeof(hdr), &hdrbuf) < 0)
+ status = error("unable to unpack %s header with --allow-unknown-type",
+ sha1_to_hex(sha1));
+ } else if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
status = error("unable to unpack %s header",
sha1_to_hex(sha1));
- else if ((status = parse_sha1_header(hdr, &size)) < 0)
+ if (status < 0)
+ ; /* Do nothing */
+ else if (hdrbuf.len) {
+ if ((status = parse_sha1_header_extended(hdrbuf.buf, oi, flags)) < 0)
+ status = error("unable to parse %s header with --allow-unknown-type",
+ sha1_to_hex(sha1));
+ } else if ((status = parse_sha1_header_extended(hdr, oi, flags)) < 0)
status = error("unable to parse %s header", sha1_to_hex(sha1));
- else if (oi->sizep)
- *oi->sizep = size;
git_inflate_end(&stream);
munmap(map, mapsize);
- if (oi->typep)
+ if (status && oi->typep)
*oi->typep = status;
+ strbuf_release(&hdrbuf);
return 0;
}
@@ -2576,6 +2642,7 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
struct cached_object *co;
struct pack_entry e;
int rtype;
+ enum object_type real_type;
const unsigned char *real = lookup_replace_object_extended(sha1, flags);
co = find_cached_object(real);
@@ -2588,13 +2655,15 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
*(oi->disk_sizep) = 0;
if (oi->delta_base_sha1)
hashclr(oi->delta_base_sha1);
+ if (oi->typename)
+ strbuf_addstr(oi->typename, typename(co->type));
oi->whence = OI_CACHED;
return 0;
}
if (!find_pack_entry(real, &e)) {
/* Most likely it's a loose object. */
- if (!sha1_loose_object_info(real, oi)) {
+ if (!sha1_loose_object_info(real, oi, flags)) {
oi->whence = OI_LOOSE;
return 0;
}
@@ -2605,9 +2674,18 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
return -1;
}
+ /*
+ * packed_object_info() does not follow the delta chain to
+ * find out the real type, unless it is given oi->typep.
+ */
+ if (oi->typename && !oi->typep)
+ oi->typep = &real_type;
+
rtype = packed_object_info(e.p, e.offset, oi);
if (rtype < 0) {
mark_bad_packed_object(e.p, real);
+ if (oi->typep == &real_type)
+ oi->typep = NULL;
return sha1_object_info_extended(real, oi, 0);
} else if (in_delta_base_cache(e.p, e.offset)) {
oi->whence = OI_DBCACHED;
@@ -2618,6 +2696,10 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
rtype == OBJ_OFS_DELTA);
}
+ if (oi->typename)
+ strbuf_addstr(oi->typename, typename(*oi->typep));
+ if (oi->typep == &real_type)
+ oi->typep = NULL;
return 0;
}
@@ -2999,12 +3081,18 @@ static int freshen_loose_object(const unsigned char *sha1)
static int freshen_packed_object(const unsigned char *sha1)
{
struct pack_entry e;
- return find_pack_entry(sha1, &e) && freshen_file(e.p->pack_name);
+ if (!find_pack_entry(sha1, &e))
+ return 0;
+ if (e.p->freshened)
+ return 1;
+ if (!freshen_file(e.p->pack_name))
+ return 0;
+ e.p->freshened = 1;
+ return 1;
}
-int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1)
{
- unsigned char sha1[20];
char hdr[32];
int hdrlen;
@@ -3012,13 +3100,32 @@ int write_sha1_file(const void *buf, unsigned long len, const char *type, unsign
* it out into .git/objects/??/?{38} file.
*/
write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
- if (returnsha1)
- hashcpy(returnsha1, sha1);
- if (freshen_loose_object(sha1) || freshen_packed_object(sha1))
+ if (freshen_packed_object(sha1) || freshen_loose_object(sha1))
return 0;
return write_loose_object(sha1, hdr, hdrlen, buf, len, 0);
}
+int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type,
+ unsigned char *sha1, unsigned flags)
+{
+ char *header;
+ int hdrlen, status = 0;
+
+ /* type string, SP, %lu of the length plus NUL must fit this */
+ header = xmalloc(strlen(type) + 32);
+ write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen);
+
+ if (!(flags & HASH_WRITE_OBJECT))
+ goto cleanup;
+ if (freshen_packed_object(sha1) || freshen_loose_object(sha1))
+ goto cleanup;
+ status = write_loose_object(sha1, header, hdrlen, buf, len, 0);
+
+cleanup:
+ free(header);
+ return status;
+}
+
int force_object_loose(const unsigned char *sha1, time_t mtime)
{
void *buf;
@@ -3418,7 +3525,7 @@ static int loose_from_alt_odb(struct alternate_object_database *alt,
return r;
}
-int for_each_loose_object(each_loose_object_fn cb, void *data)
+int for_each_loose_object(each_loose_object_fn cb, void *data, unsigned flags)
{
struct loose_alt_odb_data alt;
int r;
@@ -3428,6 +3535,9 @@ int for_each_loose_object(each_loose_object_fn cb, void *data)
if (r)
return r;
+ if (flags & FOR_EACH_OBJECT_LOCAL_ONLY)
+ return 0;
+
alt.cb = cb;
alt.data = data;
return foreach_alt_odb(loose_from_alt_odb, &alt);
@@ -3452,13 +3562,15 @@ static int for_each_object_in_pack(struct packed_git *p, each_packed_object_fn c
return r;
}
-int for_each_packed_object(each_packed_object_fn cb, void *data)
+int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)
{
struct packed_git *p;
int r = 0;
prepare_packed_git();
for (p = packed_git; p; p = p->next) {
+ if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
+ continue;
r = for_each_object_in_pack(p, cb, data);
if (r)
break;
diff --git a/shallow.c b/shallow.c
index d8bf40a..d08d264 100644
--- a/shallow.c
+++ b/shallow.c
@@ -31,7 +31,7 @@ int register_shallow(const unsigned char *sha1)
xmalloc(sizeof(struct commit_graft));
struct commit *commit = lookup_commit(sha1);
- hashcpy(graft->sha1, sha1);
+ hashcpy(graft->oid.hash, sha1);
graft->nr_parent = -1;
if (commit && commit->object.parsed)
commit->parents = NULL;
@@ -159,11 +159,11 @@ struct write_shallow_data {
static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
{
struct write_shallow_data *data = cb_data;
- const char *hex = sha1_to_hex(graft->sha1);
+ const char *hex = oid_to_hex(&graft->oid);
if (graft->nr_parent != -1)
return 0;
if (data->flags & SEEN_ONLY) {
- struct commit *c = lookup_commit(graft->sha1);
+ struct commit *c = lookup_commit(graft->oid.hash);
if (!c || !(c->object.flags & SEEN)) {
if (data->flags & VERBOSE)
printf("Removing %s from .git/shallow\n",
@@ -282,7 +282,7 @@ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *c
{
int fd = *(int *)cb;
if (graft->nr_parent == -1)
- packet_write(fd, "shallow %s\n", sha1_to_hex(graft->sha1));
+ packet_write(fd, "shallow %s\n", oid_to_hex(&graft->oid));
return 0;
}
diff --git a/strbuf.c b/strbuf.c
index 88cafd4..0d4f4e5 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -435,6 +435,47 @@ int strbuf_getcwd(struct strbuf *sb)
return -1;
}
+#ifdef HAVE_GETDELIM
+int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
+{
+ ssize_t r;
+
+ if (feof(fp))
+ return EOF;
+
+ strbuf_reset(sb);
+
+ /* Translate slopbuf to NULL, as we cannot call realloc on it */
+ if (!sb->alloc)
+ sb->buf = NULL;
+ r = getdelim(&sb->buf, &sb->alloc, term, fp);
+
+ if (r > 0) {
+ sb->len = r;
+ return 0;
+ }
+ assert(r == -1);
+
+ /*
+ * Normally we would have called xrealloc, which will try to free
+ * memory and recover. But we have no way to tell getdelim() to do so.
+ * Worse, we cannot try to recover ENOMEM ourselves, because we have
+ * no idea how many bytes were read by getdelim.
+ *
+ * Dying here is reasonable. It mirrors what xrealloc would do on
+ * catastrophic memory failure. We skip the opportunity to free pack
+ * memory and retry, but that's unlikely to help for a malloc small
+ * enough to hold a single line of input, anyway.
+ */
+ if (errno == ENOMEM)
+ die("Out of memory, getdelim failed");
+
+ /* Restore slopbuf that we moved out of the way before */
+ if (!sb->buf)
+ strbuf_init(sb, 0);
+ return EOF;
+}
+#else
int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
{
int ch;
@@ -443,18 +484,22 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
return EOF;
strbuf_reset(sb);
- while ((ch = fgetc(fp)) != EOF) {
- strbuf_grow(sb, 1);
+ flockfile(fp);
+ while ((ch = getc_unlocked(fp)) != EOF) {
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = ch;
if (ch == term)
break;
}
+ funlockfile(fp);
if (ch == EOF && sb->len == 0)
return EOF;
sb->buf[sb->len] = '\0';
return 0;
}
+#endif
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
{
diff --git a/strbuf.h b/strbuf.h
index 1883494..01c5c63 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -205,7 +205,8 @@ extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
*/
static inline void strbuf_addch(struct strbuf *sb, int c)
{
- strbuf_grow(sb, 1);
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
diff --git a/submodule.c b/submodule.c
index c0e6c81..d491e6a 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1100,16 +1100,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
struct strbuf file_name = STRBUF_INIT;
struct strbuf rel_path = STRBUF_INIT;
const char *real_work_tree = xstrdup(real_path(work_tree));
- FILE *fp;
/* Update gitfile */
strbuf_addf(&file_name, "%s/.git", work_tree);
- fp = fopen(file_name.buf, "w");
- if (!fp)
- die(_("Could not create git link %s"), file_name.buf);
- fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
- &rel_path));
- fclose(fp);
+ write_file(file_name.buf, 1, "gitdir: %s\n",
+ relative_path(git_dir, real_work_tree, &rel_path));
/* Update core.worktree setting */
strbuf_reset(&file_name);
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh
index 5aa8adc..7548225 100644
--- a/t/lib-git-p4.sh
+++ b/t/lib-git-p4.sh
@@ -69,7 +69,7 @@ start_p4d() {
(
cd "$db" &&
{
- p4d -q -p $P4DPORT &
+ p4d -q -p $P4DPORT "$@" &
echo $! >"$pidfile"
}
) &&
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index e6adf2f..e971446 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -30,6 +30,18 @@
# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
#
+if test -n "$NO_CURL"
+then
+ skip_all='skipping test, git built without http support'
+ test_done
+fi
+
+if test -n "$NO_EXPAT" && test -n "$LIB_HTTPD_DAV"
+then
+ skip_all='skipping test, git built without expat support'
+ test_done
+fi
+
test_tristate GIT_TEST_HTTPD
if test "$GIT_TEST_HTTPD" = false
then
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index 8dc6939..4ef5ed4 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -831,4 +831,14 @@ test_expect_success !MINGW,!CYGWIN 'correct handling of backslashes' '
test_cmp err.expect err
'
+test_expect_success 'info/exclude trumps core.excludesfile' '
+ echo >>global-excludes usually-ignored &&
+ echo >>.git/info/exclude "!usually-ignored" &&
+ >usually-ignored &&
+ echo "?? usually-ignored" >expect &&
+
+ git status --porcelain usually-ignored >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index 452320d..1a56e5e 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -57,28 +57,35 @@ create_gitattributes () {
check_warning () {
case "$1" in
- LF_CRLF) grep "LF will be replaced by CRLF" $2;;
- CRLF_LF) grep "CRLF will be replaced by LF" $2;;
- '')
- >expect
- grep "will be replaced by" $2 >actual
- test_cmp expect actual
- ;;
- *) false ;;
+ LF_CRLF) echo "warning: LF will be replaced by CRLF" >"$2".expect ;;
+ CRLF_LF) echo "warning: CRLF will be replaced by LF" >"$2".expect ;;
+ '') >"$2".expect ;;
+ *) echo >&2 "Illegal 1": "$1" ; return false ;;
esac
+ grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" >"$2".actual
+ test_cmp "$2".expect "$2".actual
}
-create_file_in_repo () {
+commit_check_warn () {
crlf=$1
attr=$2
lfname=$3
crlfname=$4
- lfmixcrlf=$5
- lfmixcr=$6
- crlfnul=$7
- create_gitattributes "$attr" &&
+ repoMIX=$5
+ lfmixcrlf=$6
+ lfmixcr=$7
+ crlfnul=$8
pfx=crlf_${crlf}_attr_${attr}
- for f in LF CRLF LF_mix_CR CRLF_mix_LF CRLF_nul
+ # Special handling for repoMIX: It should already be in the repo
+ # with CRLF
+ f=repoMIX
+ fname=${pfx}_$f.txt
+ echo >.gitattributes &&
+ cp $f $fname &&
+ git -c core.autocrlf=false add $fname 2>"${pfx}_$f.err" &&
+ git commit -m "repoMIX" &&
+ create_gitattributes "$attr" &&
+ for f in LF CRLF repoMIX LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul
do
fname=${pfx}_$f.txt &&
cp $f $fname &&
@@ -109,7 +116,7 @@ check_files_in_repo () {
}
-check_files_in_ws () {
+checkout_files () {
eol=$1
crlf=$2
attr=$3
@@ -122,7 +129,7 @@ check_files_in_ws () {
git config core.autocrlf $crlf &&
pfx=eol_${eol}_crlf_${crlf}_attr_${attr}_ &&
src=crlf_false_attr__ &&
- for f in LF CRLF LF_mix_CR CRLF_mix_LF CRLF_nul
+ for f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul
do
rm $src$f.txt &&
if test -z "$eol"; then
@@ -144,8 +151,8 @@ check_files_in_ws () {
test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF_mix_CR" "
compare_ws_file $pfx $lfmixcr ${src}LF_mix_CR.txt
"
- test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=CRLF_nul" "
- compare_ws_file $pfx $crlfnul ${src}CRLF_nul.txt
+ test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF_nul" "
+ compare_ws_file $pfx $crlfnul ${src}LF_nul.txt
"
}
@@ -157,6 +164,7 @@ test_expect_success 'setup master' '
git commit -m "add .gitattributes" "" &&
printf "line1\nline2\nline3" >LF &&
printf "line1\r\nline2\r\nline3" >CRLF &&
+ printf "line1\r\nline2\nline3" >repoMIX &&
printf "line1\r\nline2\nline3" >CRLF_mix_LF &&
printf "line1\nline2\rline3" >LF_mix_CR &&
printf "line1\r\nline2\rline3" >CRLF_mix_CR &&
@@ -169,40 +177,55 @@ test_expect_success 'setup master' '
warn_LF_CRLF="LF will be replaced by CRLF"
warn_CRLF_LF="CRLF will be replaced by LF"
-test_expect_success 'add files empty attr' '
- create_file_in_repo false "" "" "" "" "" "" &&
- create_file_in_repo true "" "LF_CRLF" "" "LF_CRLF" "" "" &&
- create_file_in_repo input "" "" "CRLF_LF" "CRLF_LF" "" ""
+# WILC stands for "Warn if (this OS) converts LF into CRLF".
+# WICL: Warn if CRLF becomes LF
+# WAMIX: Mixed line endings: either CRLF->LF or LF->CRLF
+if test_have_prereq NATIVE_CRLF
+then
+ WILC=LF_CRLF
+ WICL=
+ WAMIX=LF_CRLF
+else
+ WILC=
+ WICL=CRLF_LF
+ WAMIX=CRLF_LF
+fi
+
+# attr LF CRLF repoMIX CRLFmixLF LFmixCR CRLFNUL
+test_expect_success 'commit files empty attr' '
+ commit_check_warn false "" "" "" "" "" "" "" &&
+ commit_check_warn true "" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" "" &&
+ commit_check_warn input "" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" ""
'
-test_expect_success 'add files attr=auto' '
- create_file_in_repo false "auto" "" "CRLF_LF" "CRLF_LF" "" "" &&
- create_file_in_repo true "auto" "LF_CRLF" "" "LF_CRLF" "" "" &&
- create_file_in_repo input "auto" "" "CRLF_LF" "CRLF_LF" "" ""
+test_expect_success 'commit files attr=auto' '
+ commit_check_warn false "auto" "$WILC" "$WICL" "$WAMIX" "$WAMIX" "" "" &&
+ commit_check_warn true "auto" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" "" &&
+ commit_check_warn input "auto" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" ""
'
-test_expect_success 'add files attr=text' '
- create_file_in_repo false "text" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
- create_file_in_repo true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" &&
- create_file_in_repo input "text" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
+test_expect_success 'commit files attr=text' '
+ commit_check_warn false "text" "$WILC" "$WICL" "$WAMIX" "$WAMIX" "$WILC" "$WICL" &&
+ commit_check_warn true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
+ commit_check_warn input "text" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
'
-test_expect_success 'add files attr=-text' '
- create_file_in_repo false "-text" "" "" "" "" "" &&
- create_file_in_repo true "-text" "" "" "" "" "" &&
- create_file_in_repo input "-text" "" "" "" "" ""
+test_expect_success 'commit files attr=-text' '
+ commit_check_warn false "-text" "" "" "" "" "" "" &&
+ commit_check_warn true "-text" "" "" "" "" "" "" &&
+ commit_check_warn input "-text" "" "" "" "" "" ""
'
-test_expect_success 'add files attr=lf' '
- create_file_in_repo false "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
- create_file_in_repo true "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
- create_file_in_repo input "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
+test_expect_success 'commit files attr=lf' '
+ commit_check_warn false "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
+ commit_check_warn true "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
+ commit_check_warn input "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
'
-test_expect_success 'add files attr=crlf' '
- create_file_in_repo false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" &&
- create_file_in_repo true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" &&
- create_file_in_repo input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" ""
+test_expect_success 'commit files attr=crlf' '
+ commit_check_warn false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
+ commit_check_warn true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
+ commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" ""
'
test_expect_success 'create files cleanup' '
@@ -237,7 +260,7 @@ test_expect_success 'commit -text' '
################################################################################
# Check how files in the repo are changed when they are checked out
# How to read the table below:
-# - check_files_in_ws will check multiple files with a combination of settings
+# - checkout_files will check multiple files with a combination of settings
# and attributes (core.autocrlf=input is forbidden with core.eol=crlf)
# - parameter $1 : core.eol lf | crlf
# - parameter $2 : core.autocrlf false | true | input
@@ -249,87 +272,89 @@ test_expect_success 'commit -text' '
# - parameter $8 : reference for a file with CRLF and a NUL (should be handled as binary when auto)
# What we have in the repo:
-# ----------------- EOL in repo ----------------
-# LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+# ----------------- EOL in repo ----------------
+# LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
# settings with checkout:
# core. core. .gitattr
# eol acrlf
# ----------------------------------------------
# What we want to have in the working tree:
-if test_have_prereq MINGW
+if test_have_prereq NATIVE_CRLF
then
MIX_CRLF_LF=CRLF
MIX_LF_CR=CRLF_mix_CR
NL=CRLF
+LFNUL=CRLF_nul
else
MIX_CRLF_LF=CRLF_mix_LF
MIX_LF_CR=LF_mix_CR
NL=LF
+LFNUL=LF_nul
fi
export CRLF_MIX_LF_CR MIX NL
-check_files_in_ws lf false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws lf input "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws lf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws lf input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-
-check_files_in_ws crlf false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws crlf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws crlf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws crlf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-
-check_files_in_ws "" false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR CRLF_nul
-check_files_in_ws "" true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws "" input "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws "" true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws "" input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-
-check_files_in_ws native false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws native false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR CRLF_nul
-check_files_in_ws native true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws native false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws native true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files lf input "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf input "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf input "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf input "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+
+checkout_files crlf false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf false "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files crlf true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files crlf false "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files crlf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files crlf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files crlf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+
+checkout_files "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul
+checkout_files "" true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files "" input "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL
+checkout_files "" true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files "" input "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" input "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" input "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files "" true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files "" input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+
+checkout_files native false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul
+checkout_files native true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files native false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL
+checkout_files native true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files native false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files native true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
test_done
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index c0143a0..93605f4 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -19,6 +19,14 @@ relative_path() {
"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
}
+test_git_path() {
+ test_expect_success "git-path $1 $2 => $3" "
+ $1 git rev-parse --git-path $2 >actual &&
+ echo $3 >expect &&
+ test_cmp expect actual
+ "
+}
+
# On Windows, we are using MSYS's bash, which mangles the paths.
# Absolute paths are anchored at the MSYS installation directory,
# which means that the path / accounts for this many characters:
@@ -244,4 +252,32 @@ relative_path "<null>" "<empty>" ./
relative_path "<null>" "<null>" ./
relative_path "<null>" /foo/a/b ./
+test_git_path A=B info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=foo info/grafts foo
+test_git_path GIT_GRAFT_FILE=foo info/////grafts foo
+test_git_path GIT_INDEX_FILE=foo index foo
+test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=foo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=bar init'
+test_git_path GIT_COMMON_DIR=bar index .git/index
+test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar objects bar/objects
+test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar
+test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude
+test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts
+test_git_path GIT_COMMON_DIR=bar info/sparse-checkout .git/info/sparse-checkout
+test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar
+test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar
+test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me
+test_git_path GIT_COMMON_DIR=bar config bar/config
+test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
+test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
+
test_done
diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh
index f61b40c..0979df9 100755
--- a/t/t0302-credential-store.sh
+++ b/t/t0302-credential-store.sh
@@ -6,4 +6,118 @@ test_description='credential-store tests'
helper_test store
+test_expect_success 'when xdg file does not exist, xdg file not created' '
+ test_path_is_missing "$HOME/.config/git/credentials" &&
+ test -s "$HOME/.git-credentials"
+'
+
+test_expect_success 'setup xdg file' '
+ rm -f "$HOME/.git-credentials" &&
+ mkdir -p "$HOME/.config/git" &&
+ >"$HOME/.config/git/credentials"
+'
+
+helper_test store
+
+test_expect_success 'when xdg file exists, home file not created' '
+ test -s "$HOME/.config/git/credentials" &&
+ test_path_is_missing "$HOME/.git-credentials"
+'
+
+test_expect_success 'setup custom xdg file' '
+ rm -f "$HOME/.git-credentials" &&
+ rm -f "$HOME/.config/git/credentials" &&
+ mkdir -p "$HOME/xdg/git" &&
+ >"$HOME/xdg/git/credentials"
+'
+
+XDG_CONFIG_HOME="$HOME/xdg"
+export XDG_CONFIG_HOME
+helper_test store
+unset XDG_CONFIG_HOME
+
+test_expect_success 'if custom xdg file exists, home and xdg files not created' '
+ test_when_finished "rm -f $HOME/xdg/git/credentials" &&
+ test -s "$HOME/xdg/git/credentials" &&
+ test_path_is_missing "$HOME/.git-credentials" &&
+ test_path_is_missing "$HOME/.config/git/credentials"
+'
+
+test_expect_success 'get: use home file if both home and xdg files have matches' '
+ echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" &&
+ mkdir -p "$HOME/.config/git" &&
+ echo "https://xdg-user:xdg-pass@example.com" >"$HOME/.config/git/credentials" &&
+ check fill store <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=home-user
+ password=home-pass
+ --
+ EOF
+'
+
+test_expect_success 'get: use xdg file if home file has no matches' '
+ >"$HOME/.git-credentials" &&
+ mkdir -p "$HOME/.config/git" &&
+ echo "https://xdg-user:xdg-pass@example.com" >"$HOME/.config/git/credentials" &&
+ check fill store <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=xdg-user
+ password=xdg-pass
+ --
+ EOF
+'
+
+test_expect_success POSIXPERM 'get: use xdg file if home file is unreadable' '
+ echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" &&
+ chmod -r "$HOME/.git-credentials" &&
+ mkdir -p "$HOME/.config/git" &&
+ echo "https://xdg-user:xdg-pass@example.com" >"$HOME/.config/git/credentials" &&
+ check fill store <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=xdg-user
+ password=xdg-pass
+ --
+ EOF
+'
+
+test_expect_success 'store: if both xdg and home files exist, only store in home file' '
+ >"$HOME/.git-credentials" &&
+ mkdir -p "$HOME/.config/git" &&
+ >"$HOME/.config/git/credentials" &&
+ check approve store <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=store-user
+ password=store-pass
+ EOF
+ echo "https://store-user:store-pass@example.com" >expected &&
+ test_cmp expected "$HOME/.git-credentials" &&
+ test_must_be_empty "$HOME/.config/git/credentials"
+'
+
+
+test_expect_success 'erase: erase matching credentials from both xdg and home files' '
+ echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" &&
+ mkdir -p "$HOME/.config/git" &&
+ echo "https://xdg-user:xdg-pass@example.com" >"$HOME/.config/git/credentials" &&
+ check reject store <<-\EOF &&
+ protocol=https
+ host=example.com
+ EOF
+ test_must_be_empty "$HOME/.git-credentials" &&
+ test_must_be_empty "$HOME/.config/git/credentials"
+'
+
test_done
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index ab36b1e..4f225db 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -47,6 +47,18 @@ $content"
test_cmp expect actual
'
+ test_expect_success "Type of $type is correct using --allow-unknown-type" '
+ echo $type >expect &&
+ git cat-file -t --allow-unknown-type $sha1 >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "Size of $type is correct using --allow-unknown-type" '
+ echo $size >expect &&
+ git cat-file -s --allow-unknown-type $sha1 >actual &&
+ test_cmp expect actual
+ '
+
test -z "$content" ||
test_expect_success "Content of $type is correct" '
maybe_remove_timestamp "$content" $no_ts >expect &&
@@ -296,4 +308,37 @@ test_expect_success '%(deltabase) reports packed delta bases' '
}
'
+bogus_type="bogus"
+bogus_content="bogus"
+bogus_size=$(strlen "$bogus_content")
+bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin)
+
+test_expect_success "Type of broken object is correct" '
+ echo $bogus_type >expect &&
+ git cat-file -t --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "Size of broken object is correct" '
+ echo $bogus_size >expect &&
+ git cat-file -s --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+bogus_type="abcdefghijklmnopqrstuvwxyz1234679"
+bogus_content="bogus"
+bogus_size=$(strlen "$bogus_content")
+bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin)
+
+test_expect_success "Type of broken object is correct when type is large" '
+ echo $bogus_type >expect &&
+ git cat-file -t --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "Size of large broken object is correct when type is large" '
+ echo $bogus_size >expect &&
+ git cat-file -s --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index f83df8e..7d2baa1 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -201,4 +201,23 @@ test_expect_success 'corrupt tag' '
test_must_fail git hash-object -t tag --stdin </dev/null
'
+test_expect_success 'hash-object complains about bogus type name' '
+ test_must_fail git hash-object -t bogus --stdin </dev/null
+'
+
+test_expect_success 'hash-object complains about truncated type name' '
+ test_must_fail git hash-object -t bl --stdin </dev/null
+'
+
+test_expect_success '--literally' '
+ t=1234567890 &&
+ echo example | git hash-object -t $t --literally --stdin
+'
+
+test_expect_success '--literally with extra-long type' '
+ t=12345678901234567890123456789012345678901234567890 &&
+ t="$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t" &&
+ echo example | git hash-object -t $t --literally --stdin
+'
+
test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 6805b9e..ba89f4c 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -519,7 +519,7 @@ test_expect_success 'stdin create ref works with path with space to blob' '
test_expect_success 'stdin update ref fails with wrong old value' '
echo "update $c $m $m~1" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
test_must_fail git rev-parse --verify -q $c
'
@@ -555,7 +555,7 @@ test_expect_success 'stdin update ref works with right old value' '
test_expect_success 'stdin delete ref fails with wrong old value' '
echo "delete $a $m~1" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$a'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
@@ -688,7 +688,7 @@ test_expect_success 'stdin update refs fails with wrong old value' '
update $c ''
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual &&
@@ -883,7 +883,7 @@ test_expect_success 'stdin -z create ref works with path with space to blob' '
test_expect_success 'stdin -z update ref fails with wrong old value' '
printf $F "update $c" "$m" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
test_must_fail git rev-parse --verify -q $c
'
@@ -899,7 +899,7 @@ test_expect_success 'stdin -z create ref fails when ref exists' '
git rev-parse "$c" >expect &&
printf $F "create $c" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse "$c" >actual &&
test_cmp expect actual
'
@@ -930,7 +930,7 @@ test_expect_success 'stdin -z update ref works with right old value' '
test_expect_success 'stdin -z delete ref fails with wrong old value' '
printf $F "delete $a" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$a'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
@@ -1045,7 +1045,7 @@ test_expect_success 'stdin -z update refs fails with wrong old value' '
git update-ref $c $m &&
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual &&
@@ -1065,4 +1065,32 @@ test_expect_success 'stdin -z delete refs works with packed and loose refs' '
test_must_fail git rev-parse --verify -q $c
'
+run_with_limited_open_files () {
+ (ulimit -n 32 && "$@")
+}
+
+test_lazy_prereq ULIMIT_FILE_DESCRIPTORS 'run_with_limited_open_files true'
+
+test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
+(
+ for i in $(test_seq 33)
+ do
+ echo "create refs/heads/$i HEAD"
+ done >large_input &&
+ run_with_limited_open_files git update-ref --stdin <large_input &&
+ git rev-parse --verify -q refs/heads/33
+)
+'
+
+test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' '
+(
+ for i in $(test_seq 33)
+ do
+ echo "delete refs/heads/$i HEAD"
+ done >large_input &&
+ run_with_limited_open_files git update-ref --stdin <large_input &&
+ test_must_fail git rev-parse --verify -q refs/heads/33
+)
+'
+
test_done
diff --git a/t/t1404-update-ref-df-conflicts.sh b/t/t1404-update-ref-df-conflicts.sh
new file mode 100755
index 0000000..66bafb5
--- /dev/null
+++ b/t/t1404-update-ref-df-conflicts.sh
@@ -0,0 +1,107 @@
+#!/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/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index 468e856..16d0b8b 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -68,6 +68,14 @@ test_expect_success 'branch -D cannot delete non-ref in .git dir' '
test_cmp expect .git/my-private-file
'
+test_expect_success 'branch -D cannot delete ref in .git dir' '
+ git rev-parse HEAD >.git/my-private-file &&
+ git rev-parse HEAD >expect &&
+ git branch foo/legit &&
+ test_must_fail git branch -D foo////./././../../../my-private-file &&
+ test_cmp expect .git/my-private-file
+'
+
test_expect_success 'branch -D cannot delete absolute path' '
git branch -f extra &&
test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" &&
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 8f36aa9..cc5b870 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -346,4 +346,81 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
test_cmp expected actual
'
+test_expect_success 'Multi-worktree setup' '
+ mkdir work &&
+ mkdir -p repo.git/repos/foo &&
+ cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
+ test_might_fail cp repo.git/sharedindex.* repo.git/repos/foo &&
+ sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'GIT_DIR set (1)' '
+ echo "gitdir: repo.git/repos/foo" >gitfile &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'GIT_DIR set (2)' '
+ echo "gitdir: repo.git/repos/foo" >gitfile &&
+ echo "$(pwd)/repo.git" >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'Auto discovery' '
+ echo "gitdir: repo.git/repos/foo" >.git &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual &&
+ echo haha >data1 &&
+ git add data1 &&
+ git ls-files --full-name :/ | grep data1 >actual &&
+ echo work/data1 >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '$GIT_DIR/common overrides core.worktree' '
+ mkdir elsewhere &&
+ git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
+ echo "gitdir: repo.git/repos/foo" >.git &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual &&
+ echo haha >data2 &&
+ git add data2 &&
+ git ls-files --full-name :/ | grep data2 >actual &&
+ echo work/data2 >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
+ echo "gitdir: repo.git/repos/foo" >.git &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ echo haha >data3 &&
+ git --git-dir=../.git --work-tree=. add data3 &&
+ git ls-files --full-name -- :/ | grep data3 >actual &&
+ echo data3 >expect &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t1509/prepare-chroot.sh b/t/t1509/prepare-chroot.sh
index 6269117..6d47e2c 100755
--- a/t/t1509/prepare-chroot.sh
+++ b/t/t1509/prepare-chroot.sh
@@ -14,25 +14,45 @@ xmkdir() {
R="$1"
+[ "$(id -u)" -eq 0 ] && die "This script should not be run as root, what if it does rm -rf /?"
[ -n "$R" ] || die "usage: prepare-chroot.sh <root>"
[ -x git ] || die "This script needs to be executed at git source code's top directory"
-[ -x /bin/busybox ] || die "You need busybox"
+if [ -x /bin/busybox ]; then
+ BB=/bin/busybox
+elif [ -x /usr/bin/busybox ]; then
+ BB=/usr/bin/busybox
+else
+ die "You need busybox"
+fi
xmkdir "$R" "$R/bin" "$R/etc" "$R/lib" "$R/dev"
-[ -c "$R/dev/null" ] || die "/dev/null is missing. Do mknod $R/dev/null c 1 3 && chmod 666 $R/dev/null"
+touch "$R/dev/null"
echo "root:x:0:0:root:/:/bin/sh" > "$R/etc/passwd"
echo "$(id -nu):x:$(id -u):$(id -g)::$(pwd)/t:/bin/sh" >> "$R/etc/passwd"
echo "root::0:root" > "$R/etc/group"
echo "$(id -ng)::$(id -g):$(id -nu)" >> "$R/etc/group"
-[ -x "$R/bin/busybox" ] || cp /bin/busybox "$R/bin/busybox"
-[ -x "$R/bin/sh" ] || ln -s /bin/busybox "$R/bin/sh"
-[ -x "$R/bin/su" ] || ln -s /bin/busybox "$R/bin/su"
+[ -x "$R$BB" ] || cp $BB "$R/bin/busybox"
+for cmd in sh su ls expr tr basename rm mkdir mv id uname dirname cat true sed diff; do
+ ln -f -s /bin/busybox "$R/bin/$cmd"
+done
mkdir -p "$R$(pwd)"
rsync --exclude-from t/t1509/excludes -Ha . "$R$(pwd)"
-ldd git | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do
- mkdir -p "$R$(dirname $i)"
- cp "$i" "$R/$i"
+# Fake perl to reduce dependency, t1509 does not use perl, but some
+# env might slip through, see test-lib.sh, unset.*PERL_PATH
+sed 's|^PERL_PATH=.*|PERL_PATH=/bin/true|' GIT-BUILD-OPTIONS > "$R$(pwd)/GIT-BUILD-OPTIONS"
+for cmd in git $BB;do
+ ldd $cmd | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do
+ mkdir -p "$R$(dirname $i)"
+ cp "$i" "$R/$i"
+ done
done
-echo "Execute this in root: 'chroot $R /bin/su - $(id -nu)'"
+cat <<EOF
+All is set up in $R, execute t1509 with the following commands:
+
+sudo chroot $R /bin/su - $(id -nu)
+IKNOWWHATIAMDOING=YES ./t1509-root-worktree.sh -v -i
+
+When you are done, simply delete $R to clean up
+EOF
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index e1b2a99..33c1a58 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -106,6 +106,7 @@ setup_env () {
expect () {
cat >"$1/expected" <<-EOF
setup: git_dir: $2
+ setup: git_common_dir: $2
setup: worktree: $3
setup: cwd: $4
setup: prefix: $5
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
new file mode 100755
index 0000000..f8e4df4
--- /dev/null
+++ b/t/t2025-checkout-to.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='test git checkout --to'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit init
+'
+
+test_expect_success 'checkout --to not updating paths' '
+ test_must_fail git checkout --to -- init.t
+'
+
+test_expect_success 'checkout --to an existing worktree' '
+ mkdir -p existing/subtree &&
+ test_must_fail git checkout --detach --to existing master
+'
+
+test_expect_success 'checkout --to an existing empty worktree' '
+ mkdir existing_empty &&
+ git checkout --detach --to existing_empty master
+'
+
+test_expect_success 'checkout --to refuses to checkout locked branch' '
+ test_must_fail git checkout --to zere master &&
+ ! test -d zere &&
+ ! test -d .git/worktrees/zere
+'
+
+test_expect_success 'checkout --to a new worktree' '
+ git rev-parse HEAD >expect &&
+ git checkout --detach --to here master &&
+ (
+ cd here &&
+ test_cmp ../init.t init.t &&
+ test_must_fail git symbolic-ref HEAD &&
+ git rev-parse HEAD >actual &&
+ test_cmp ../expect actual &&
+ git fsck
+ )
+'
+
+test_expect_success 'checkout --to a new worktree from a subdir' '
+ (
+ mkdir sub &&
+ cd sub &&
+ git checkout --detach --to here master &&
+ cd here &&
+ test_cmp ../../init.t init.t
+ )
+'
+
+test_expect_success 'checkout --to from a linked checkout' '
+ (
+ cd here &&
+ git checkout --detach --to nested-here master &&
+ cd nested-here &&
+ git fsck
+ )
+'
+
+test_expect_success 'checkout --to a new worktree creating new branch' '
+ git checkout --to there -b newmaster master &&
+ (
+ cd there &&
+ test_cmp ../init.t init.t &&
+ git symbolic-ref HEAD >actual &&
+ echo refs/heads/newmaster >expect &&
+ test_cmp expect actual &&
+ git fsck
+ )
+'
+
+test_expect_success 'die the same branch is already checked out' '
+ (
+ cd here &&
+ test_must_fail git checkout newmaster
+ )
+'
+
+test_expect_success 'not die the same branch is already checked out' '
+ (
+ cd here &&
+ git checkout --ignore-other-worktrees --to anothernewmaster newmaster
+ )
+'
+
+test_expect_success 'not die on re-checking out current branch' '
+ (
+ cd there &&
+ git checkout newmaster
+ )
+'
+
+test_expect_success 'checkout --to from a bare repo' '
+ (
+ git clone --bare . bare &&
+ cd bare &&
+ git checkout --to ../there2 -b bare-master master
+ )
+'
+
+test_expect_success 'checkout from a bare repo without --to' '
+ (
+ cd bare &&
+ test_must_fail git checkout master
+ )
+'
+
+test_expect_success 'checkout with grafts' '
+ test_when_finished rm .git/info/grafts &&
+ test_commit abc &&
+ SHA1=`git rev-parse HEAD` &&
+ test_commit def &&
+ test_commit xyz &&
+ echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts &&
+ cat >expected <<-\EOF &&
+ xyz
+ abc
+ EOF
+ git log --format=%s -2 >actual &&
+ test_cmp expected actual &&
+ git checkout --detach --to grafted master &&
+ git --git-dir=grafted/.git log --format=%s -2 >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh
new file mode 100755
index 0000000..1821a48
--- /dev/null
+++ b/t/t2026-prune-linked-checkouts.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+test_description='prune $GIT_DIR/worktrees'
+
+. ./test-lib.sh
+
+test_expect_success initialize '
+ git commit --allow-empty -m init
+'
+
+test_expect_success 'prune --worktrees on normal repo' '
+ git prune --worktrees &&
+ test_must_fail git prune --worktrees abc
+'
+
+test_expect_success 'prune files inside $GIT_DIR/worktrees' '
+ mkdir .git/worktrees &&
+ : >.git/worktrees/abc &&
+ git prune --worktrees --verbose >actual &&
+ cat >expect <<EOF &&
+Removing worktrees/abc: not a valid directory
+EOF
+ test_i18ncmp expect actual &&
+ ! test -f .git/worktrees/abc &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories without gitdir' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ cat >expect <<EOF &&
+Removing worktrees/def: gitdir file does not exist
+EOF
+ git prune --worktrees --verbose >actual &&
+ test_i18ncmp expect actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success SANITY 'prune directories with unreadable gitdir' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ : >.git/worktrees/def/gitdir &&
+ chmod u-r .git/worktrees/def/gitdir &&
+ git prune --worktrees --verbose >actual &&
+ test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with invalid gitdir' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ : >.git/worktrees/def/gitdir &&
+ git prune --worktrees --verbose >actual &&
+ test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with gitdir pointing to nowhere' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
+ git prune --worktrees --verbose >actual &&
+ test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'not prune locked checkout' '
+ test_when_finished rm -r .git/worktrees &&
+ mkdir -p .git/worktrees/ghi &&
+ : >.git/worktrees/ghi/locked &&
+ git prune --worktrees &&
+ test -d .git/worktrees/ghi
+'
+
+test_expect_success 'not prune recent checkouts' '
+ test_when_finished rm -r .git/worktrees &&
+ mkdir zz &&
+ mkdir -p .git/worktrees/jlm &&
+ echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
+ rmdir zz &&
+ git prune --worktrees --verbose --expire=2.days.ago &&
+ test -d .git/worktrees/jlm
+'
+
+test_expect_success 'not prune proper checkouts' '
+ test_when_finished rm -r .git/worktrees &&
+ git checkout "--to=$PWD/nop" --detach master &&
+ git prune --worktrees &&
+ test -d .git/worktrees/nop
+'
+
+test_done
diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh
index 2a4a749..7c641bf 100755
--- a/t/t2203-add-intent.sh
+++ b/t/t2203-add-intent.sh
@@ -5,10 +5,24 @@ test_description='Intent to add'
. ./test-lib.sh
test_expect_success 'intent to add' '
+ test_commit 1 &&
+ git rm 1.t &&
+ echo hello >1.t &&
echo hello >file &&
echo hello >elif &&
git add -N file &&
- git add elif
+ git add elif &&
+ git add -N 1.t
+'
+
+test_expect_success 'git status' '
+ git status --porcelain | grep -v actual >actual &&
+ cat >expect <<-\EOF &&
+ DA 1.t
+ A elif
+ A file
+ EOF
+ test_cmp expect actual
'
test_expect_success 'check result of "add -N"' '
@@ -43,7 +57,8 @@ test_expect_success 'i-t-a entry is simply ignored' '
git add -N nitfol &&
git commit -m second &&
test $(git ls-tree HEAD -- nitfol | wc -l) = 0 &&
- test $(git diff --name-only HEAD -- nitfol | wc -l) = 1
+ test $(git diff --name-only HEAD -- nitfol | wc -l) = 0 &&
+ test $(git diff --name-only -- nitfol | wc -l) = 1
'
test_expect_success 'can commit with an unrelated i-t-a entry in index' '
@@ -72,13 +87,13 @@ test_expect_success 'cache-tree invalidates i-t-a paths' '
: >dir/bar &&
git add -N dir/bar &&
git diff --cached --name-only >actual &&
- echo dir/bar >expect &&
+ >expect &&
test_cmp expect actual &&
git write-tree >/dev/null &&
git diff --cached --name-only >actual &&
- echo dir/bar >expect &&
+ >expect &&
test_cmp expect actual
'
diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh
new file mode 100755
index 0000000..46aadc4
--- /dev/null
+++ b/t/t3033-merge-toplevel.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+
+test_description='"git merge" top-level frontend'
+
+. ./test-lib.sh
+
+t3033_reset () {
+ git checkout -B master two &&
+ git branch -f left three &&
+ git branch -f right four
+}
+
+test_expect_success setup '
+ test_commit one &&
+ git branch left &&
+ git branch right &&
+ test_commit two &&
+ git checkout left &&
+ test_commit three &&
+ git checkout right &&
+ test_commit four &&
+ git checkout master
+'
+
+# Local branches
+
+test_expect_success 'merge an octopus into void' '
+ t3033_reset &&
+ git checkout --orphan test &&
+ git rm -fr . &&
+ test_must_fail git merge left right &&
+ test_must_fail git rev-parse --verify HEAD &&
+ git diff --quiet &&
+ test_must_fail git rev-parse HEAD
+'
+
+test_expect_success 'merge an octopus, fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git merge left right &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^3 &&
+ git rev-parse HEAD^1 HEAD^2 | sort >actual &&
+ git rev-parse three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge octopus, non-fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git merge --no-ff left right &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse one three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge octopus, fast-forward (does not ff)' '
+ t3033_reset &&
+ git merge left right &&
+ # two (master) is not an ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge octopus, non-fast-forward' '
+ t3033_reset &&
+ git merge --no-ff left right &&
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+# The same set with FETCH_HEAD
+
+test_expect_success 'merge FETCH_HEAD octopus into void' '
+ t3033_reset &&
+ git checkout --orphan test &&
+ git rm -fr . &&
+ git fetch . left right &&
+ test_must_fail git merge FETCH_HEAD &&
+ test_must_fail git rev-parse --verify HEAD &&
+ git diff --quiet &&
+ test_must_fail git rev-parse HEAD
+'
+
+test_expect_success 'merge FETCH_HEAD octopus fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git fetch . left right &&
+ git merge FETCH_HEAD &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^3 &&
+ git rev-parse HEAD^1 HEAD^2 | sort >actual &&
+ git rev-parse three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git fetch . left right &&
+ git merge --no-ff FETCH_HEAD &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse one three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
+ t3033_reset &&
+ git fetch . left right &&
+ git merge FETCH_HEAD &&
+ # two (master) is not an ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' '
+ t3033_reset &&
+ git fetch . left right &&
+ git merge --no-ff FETCH_HEAD &&
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index aa9eb3a..8aae98d 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -187,4 +187,21 @@ test_expect_success 'notice d/f conflict with existing ref' '
test_must_fail git branch foo/bar/baz/lots/of/extra/components
'
+test_expect_success 'timeout if packed-refs.lock exists' '
+ LOCK=.git/packed-refs.lock &&
+ >"$LOCK" &&
+ test_when_finished "rm -f $LOCK" &&
+ test_must_fail git pack-refs --all --prune
+'
+
+test_expect_success 'retry acquiring packed-refs.lock' '
+ LOCK=.git/packed-refs.lock &&
+ >"$LOCK" &&
+ test_when_finished "wait; rm -f $LOCK" &&
+ {
+ ( sleep 1 ; rm -f $LOCK ) &
+ } &&
+ git -c core.packedrefstimeout=3000 pack-refs --all --prune
+'
+
test_done
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index 5a27ec9..8f64505 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -47,7 +47,7 @@ test_expect_success setup '
'
test_expect_success 'reference merge' '
- git merge -s recursive "reference merge" HEAD master
+ git merge -s recursive -m "reference merge" master
'
PRE_REBASE=$(git rev-parse test-rebase)
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index eed76cc..ac429a0 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1055,4 +1055,51 @@ test_expect_success 'todo count' '
grep "^# Rebase ..* onto ..* ([0-9]" actual
'
+test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '
+ git checkout --force branch2 &&
+ git clean -f &&
+ set_fake_editor &&
+ FAKE_LINES="edit 1 2" git rebase -i A &&
+ test_cmp_rev HEAD F &&
+ test_path_is_missing file6 &&
+ >file6 &&
+ test_must_fail git rebase --continue &&
+ test_cmp_rev HEAD F &&
+ rm file6 &&
+ git rebase --continue &&
+ test_cmp_rev HEAD I
+'
+
+test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '
+ git checkout --force branch2 &&
+ git clean -f &&
+ git tag original-branch2 &&
+ set_fake_editor &&
+ FAKE_LINES="edit 1 squash 2" git rebase -i A &&
+ test_cmp_rev HEAD F &&
+ test_path_is_missing file6 &&
+ >file6 &&
+ test_must_fail git rebase --continue &&
+ test_cmp_rev HEAD F &&
+ rm file6 &&
+ git rebase --continue &&
+ test $(git cat-file commit HEAD | sed -ne \$p) = I &&
+ git reset --hard original-branch2
+'
+
+test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '
+ git checkout --force branch2 &&
+ git clean -f &&
+ set_fake_editor &&
+ FAKE_LINES="edit 1 2" git rebase -i --no-ff A &&
+ test $(git cat-file commit HEAD | sed -ne \$p) = F &&
+ test_path_is_missing file6 &&
+ >file6 &&
+ test_must_fail git rebase --continue &&
+ test $(git cat-file commit HEAD | sed -ne \$p) = F &&
+ rm file6 &&
+ git rebase --continue &&
+ test $(git cat-file commit HEAD | sed -ne \$p) = I
+'
+
test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 24ddd8a..deae948 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -326,15 +326,34 @@ test_expect_success 'split hunk "add -p (edit)"' '
# 2. Correct version applies the (not)edited version, and asks
# about the next hunk, against which we say q and program
# exits.
- for a in s e q n q q
- do
- echo $a
- done |
+ printf "%s\n" s e q n q q |
EDITOR=: git add -p &&
git diff >actual &&
! grep "^+15" actual
'
+test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
+ cat >test <<-\EOF &&
+ 5
+ 10
+ 20
+ 21
+ 30
+ 31
+ 40
+ 50
+ 60
+ EOF
+ git reset &&
+ # test sequence is s(plit), n(o), y(es), e(dit)
+ # q n q q is there to make sure we exit at the end.
+ printf "%s\n" s n y e q n q q |
+ EDITOR=: git add -p 2>error &&
+ test_must_be_empty error &&
+ git diff >actual &&
+ ! grep "^+31" actual
+'
+
test_expect_success 'patch mode ignores unmerged entries' '
git reset --hard &&
test_commit conflict &&
diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh
index 4ee47cc..3cb74ca 100755
--- a/t/t3702-add-edit.sh
+++ b/t/t3702-add-edit.sh
@@ -118,4 +118,11 @@ test_expect_success 'add -e' '
'
+test_expect_success 'add -e notices editor failure' '
+ git reset --hard &&
+ echo change >>file &&
+ test_must_fail env GIT_EDITOR=false git add -e &&
+ test_expect_code 1 git diff --exit-code
+'
+
test_done
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 1e29962..0746eee 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -10,6 +10,8 @@ test_description='Test git stash'
test_expect_success 'stash some dirty working directory' '
echo 1 > file &&
git add file &&
+ echo unrelated >other-file &&
+ git add other-file &&
test_tick &&
git commit -m initial &&
echo 2 > file &&
@@ -43,10 +45,15 @@ test_expect_success 'applying bogus stash does nothing' '
test_cmp expect file
'
+test_expect_success 'apply requires a clean index' '
+ test_when_finished "git reset --hard" &&
+ echo changed >other-file &&
+ git add other-file &&
+ test_must_fail git stash apply
+'
+
test_expect_success 'apply does not need clean working directory' '
echo 4 >other-file &&
- git add other-file &&
- echo 5 >other-file &&
git stash apply &&
echo 3 >expect &&
test_cmp expect file
@@ -695,8 +702,8 @@ test_expect_success 'setup stash with index and worktree changes' '
'
test_expect_success 'stash list implies --first-parent -m' '
- cat >expect <<-\EOF &&
- stash@{0}: WIP on master: b27a2bc subdir
+ cat >expect <<-EOF &&
+ stash@{0}
diff --git a/file b/file
index 257cc56..d26b33d 100644
@@ -706,13 +713,13 @@ test_expect_success 'stash list implies --first-parent -m' '
-foo
+working
EOF
- git stash list -p >actual &&
+ git stash list --format=%gd -p >actual &&
test_cmp expect actual
'
test_expect_success 'stash list --cc shows combined diff' '
cat >expect <<-\EOF &&
- stash@{0}: WIP on master: b27a2bc subdir
+ stash@{0}
diff --cc file
index 257cc56,9015a7a..d26b33d
@@ -723,7 +730,7 @@ test_expect_success 'stash list --cc shows combined diff' '
-index
++working
EOF
- git stash list -p --cc >actual &&
+ git stash list --format=%gd -p --cc >actual &&
test_cmp expect actual
'
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index 70655c1..38e7300 100755
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
@@ -1,9 +1,15 @@
#!/bin/sh
-test_description='git checkout --patch'
+test_description='stash -p'
. ./lib-patch-mode.sh
-test_expect_success PERL 'setup' '
+if ! test_have_prereq PERL
+then
+ skip_all='skipping stash -p tests, perl not available'
+ test_done
+fi
+
+test_expect_success 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
@@ -20,7 +26,7 @@ test_expect_success PERL 'setup' '
# note: order of files with unstaged changes: HEAD bar dir/foo
-test_expect_success PERL 'saying "n" does nothing' '
+test_expect_success 'saying "n" does nothing' '
set_state HEAD HEADfile_work HEADfile_index &&
set_state dir/foo work index &&
(echo n; echo n; echo n) | test_must_fail git stash save -p &&
@@ -29,7 +35,7 @@ test_expect_success PERL 'saying "n" does nothing' '
verify_state dir/foo work index
'
-test_expect_success PERL 'git stash -p' '
+test_expect_success 'git stash -p' '
(echo y; echo n; echo y) | git stash save -p &&
verify_state HEAD committed HEADfile_index &&
verify_saved_state bar &&
@@ -41,7 +47,7 @@ test_expect_success PERL 'git stash -p' '
verify_state dir/foo work head
'
-test_expect_success PERL 'git stash -p --no-keep-index' '
+test_expect_success 'git stash -p --no-keep-index' '
set_state HEAD HEADfile_work HEADfile_index &&
set_state bar bar_work bar_index &&
set_state dir/foo work index &&
@@ -56,7 +62,7 @@ test_expect_success PERL 'git stash -p --no-keep-index' '
verify_state dir/foo work index
'
-test_expect_success PERL 'git stash --no-keep-index -p' '
+test_expect_success 'git stash --no-keep-index -p' '
set_state HEAD HEADfile_work HEADfile_index &&
set_state bar bar_work bar_index &&
set_state dir/foo work index &&
@@ -71,8 +77,31 @@ test_expect_success PERL 'git stash --no-keep-index -p' '
verify_state dir/foo work index
'
-test_expect_success PERL 'none of this moved HEAD' '
+test_expect_success 'none of this moved HEAD' '
verify_saved_head
'
+test_expect_failure 'stash -p with split hunk' '
+ git reset --hard &&
+ cat >test <<-\EOF &&
+ aaa
+ bbb
+ ccc
+ EOF
+ git add test &&
+ git commit -m "initial" &&
+ cat >test <<-\EOF &&
+ aaa
+ added line 1
+ bbb
+ added line 2
+ ccc
+ EOF
+ printf "%s\n" s n y q |
+ test_might_fail git stash -p 2>error &&
+ ! test_must_be_empty error &&
+ grep "added line 1" test &&
+ ! grep "added line 2" test
+'
+
test_done
diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh
index 13e7f62..7452fce 100755
--- a/t/t4011-diff-symlink.sh
+++ b/t/t4011-diff-symlink.sh
@@ -139,11 +139,13 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' '
test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' '
cat >expect <<-\EOF &&
diff --git a/file.bin b/file.bin
- index e69de29..d95f3ad 100644
- Binary files a/file.bin and b/file.bin differ
+ new file mode 100644
+ index 0000000..d95f3ad
+ Binary files /dev/null and b/file.bin differ
diff --git a/link.bin b/link.bin
- index e69de29..dce41ec 120000
- --- a/link.bin
+ new file mode 120000
+ index 0000000..dce41ec
+ --- /dev/null
+++ b/link.bin
@@ -0,0 +1 @@
+file.bin
diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all
index 44d4525..b345b2e 100644
--- a/t/t4013/diff.log_--decorate=full_--all
+++ b/t/t4013/diff.log_--decorate=full_--all
@@ -5,7 +5,7 @@ Date: Mon Jun 26 00:06:00 2006 +0000
Rearranged lines in dir/sub
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master)
Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 075ece6..6eb8321 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -55,4 +55,38 @@ test_expect_success 'git diff --no-index executed outside repo gives correct err
)
'
+test_expect_success 'diff D F and diff F D' '
+ (
+ cd repo &&
+ echo in-repo >a &&
+ echo non-repo >../non/git/a &&
+ mkdir sub &&
+ echo sub-repo >sub/a &&
+
+ test_must_fail git diff --no-index sub/a ../non/git/a >expect &&
+ test_must_fail git diff --no-index sub/a ../non/git/ >actual &&
+ test_cmp expect actual &&
+
+ test_must_fail git diff --no-index a ../non/git/a >expect &&
+ test_must_fail git diff --no-index a ../non/git/ >actual &&
+ test_cmp expect actual &&
+
+ test_must_fail git diff --no-index ../non/git/a a >expect &&
+ test_must_fail git diff --no-index ../non/git a >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'turning a file into a directory' '
+ (
+ cd non/git &&
+ mkdir d e e/sub &&
+ echo 1 >d/sub &&
+ echo 2 >e/sub/file &&
+ printf "D\td/sub\nA\te/sub/file\n" >expect &&
+ test_must_fail git diff --no-index --name-status d e >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index 0901b30..4451127 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh
@@ -54,14 +54,14 @@ canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
test_bad_opts "-L" "switch.*requires a value"
-test_bad_opts "-L b.c" "argument.*not of the form"
-test_bad_opts "-L 1:" "argument.*not of the form"
+test_bad_opts "-L b.c" "argument not .start,end:file"
+test_bad_opts "-L 1:" "argument not .start,end:file"
test_bad_opts "-L 1:nonexistent" "There is no path"
test_bad_opts "-L 1:simple" "There is no path"
-test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
+test_bad_opts "-L '/foo:b.c'" "argument not .start,end:file"
test_bad_opts "-L 1000:b.c" "has only.*lines"
test_bad_opts "-L 1,1000:b.c" "has only.*lines"
-test_bad_opts "-L :b.c" "argument.*not of the form"
+test_bad_opts "-L :b.c" "argument not .start,end:file"
test_bad_opts "-L :foo:b.c" "no match"
test_expect_success '-L X (X == nlines)' '
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 227d293..7efd45b 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -9,36 +9,27 @@ modify () {
mv "$2.x" "$2"
}
-D=`pwd`
-
test_expect_success setup '
-
echo file >file &&
git add file &&
git commit -a -m original
-
'
test_expect_success 'pulling into void' '
- mkdir cloned &&
- cd cloned &&
- git init &&
- git pull ..
-'
-
-cd "$D"
-
-test_expect_success 'checking the results' '
+ git init cloned &&
+ (
+ cd cloned &&
+ git pull ..
+ ) &&
test -f file &&
test -f cloned/file &&
test_cmp file cloned/file
'
test_expect_success 'pulling into void using master:master' '
- mkdir cloned-uho &&
+ git init cloned-uho &&
(
cd cloned-uho &&
- git init &&
git pull .. master:master
) &&
test -f file &&
@@ -71,7 +62,6 @@ test_expect_success 'pulling into void does not overwrite staged files' '
)
'
-
test_expect_success 'pulling into void does not remove new staged files' '
git init cloned-staged-new &&
(
@@ -86,6 +76,15 @@ test_expect_success 'pulling into void does not remove new staged files' '
)
'
+test_expect_success 'pulling into void must not create an octopus' '
+ git init cloned-octopus &&
+ (
+ cd cloned-octopus &&
+ test_must_fail git pull .. master master &&
+ ! test -f file
+ )
+'
+
test_expect_success 'test . as a remote' '
git branch copy master &&
diff --git a/t/t5539-fetch-http-shallow.sh b/t/t5539-fetch-http-shallow.sh
index b461188..37a4335 100755
--- a/t/t5539-fetch-http-shallow.sh
+++ b/t/t5539-fetch-http-shallow.sh
@@ -3,12 +3,6 @@
test_description='fetch/clone from a shallow clone over http'
. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
- skip_all='skipping test, git built without http support'
- test_done
-fi
-
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 9cf27e8..fd7d06b 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -6,11 +6,6 @@
test_description='test smart pushing over http via http-backend'
. ./test-lib.sh
-if test -n "$NO_CURL"; then
- skip_all='skipping test, git built without http support'
- test_done
-fi
-
ROOT_PATH="$PWD"
. "$TEST_DIRECTORY"/lib-gpg.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
diff --git a/t/t5542-push-http-shallow.sh b/t/t5542-push-http-shallow.sh
index 2a691e0..5165833 100755
--- a/t/t5542-push-http-shallow.sh
+++ b/t/t5542-push-http-shallow.sh
@@ -3,12 +3,6 @@
test_description='push from/to a shallow clone over http'
. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
- say 'skipping test, git built without http support'
- test_done
-fi
-
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index 3d11b7a..87a7aa0 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -2,12 +2,6 @@
test_description='test dumb fetching over http via static file'
. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
- skip_all='skipping test, git built without http support'
- test_done
-fi
-
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 66439e5..2b29311 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -2,12 +2,6 @@
test_description='test smart fetching over http via http-backend'
. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
- skip_all='skipping test, git built without http support'
- test_done
-fi
-
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh
index d23fb02..19afe96 100755
--- a/t/t5561-http-backend.sh
+++ b/t/t5561-http-backend.sh
@@ -2,12 +2,6 @@
test_description='test git-http-backend'
. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
- skip_all='skipping test, git built without http support'
- test_done
-fi
-
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 1befc45..bfdaf75 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -296,6 +296,12 @@ setup_ssh_wrapper () {
'
}
+copy_ssh_wrapper_as () {
+ cp "$TRASH_DIRECTORY/ssh-wrapper" "$1" &&
+ GIT_SSH="$1" &&
+ export GIT_SSH
+}
+
expect_ssh () {
test_when_finished '
(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)
@@ -332,9 +338,36 @@ test_expect_success !MINGW,!CYGWIN 'clone local path foo:bar' '
test_expect_success 'bracketed hostnames are still ssh' '
git clone "[myhost:123]:src" ssh-bracket-clone &&
- expect_ssh myhost '-p 123' src
+ expect_ssh "-p 123" myhost src
+'
+
+test_expect_success 'uplink is not treated as putty' '
+ copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" &&
+ git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+ expect_ssh "-p 123" myhost src
+'
+
+test_expect_success 'plink is treated specially (as putty)' '
+ copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
+ git clone "[myhost:123]:src" ssh-bracket-clone-plink-0 &&
+ expect_ssh "-P 123" myhost src
'
+test_expect_success 'plink.exe is treated specially (as putty)' '
+ copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" &&
+ git clone "[myhost:123]:src" ssh-bracket-clone-plink-1 &&
+ expect_ssh "-P 123" myhost src
+'
+
+test_expect_success 'tortoiseplink is like putty, with extra arguments' '
+ copy_ssh_wrapper_as "$TRASH_DIRECTORY/tortoiseplink" &&
+ git clone "[myhost:123]:src" ssh-bracket-clone-plink-2 &&
+ expect_ssh "-batch -P 123" myhost src
+'
+
+# Reset the GIT_SSH environment variable for clone tests.
+setup_ssh_wrapper
+
counter=0
# $1 url
# $2 none|host
diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh
index 27c3d73..2af1bee 100755
--- a/t/t6020-merge-df.sh
+++ b/t/t6020-merge-df.sh
@@ -24,7 +24,7 @@ test_expect_success 'prepare repository' '
'
test_expect_success 'Merge with d/f conflicts' '
- test_expect_code 1 git merge "merge msg" B master
+ test_expect_code 1 git merge -m "merge msg" master
'
test_expect_success 'F/D conflict' '
diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh
index d15b313..213deec 100755
--- a/t/t6021-merge-criss-cross.sh
+++ b/t/t6021-merge-criss-cross.sh
@@ -48,7 +48,7 @@ echo "1
" > file &&
git commit -m "C3" file &&
git branch C3 &&
-git merge "pre E3 merge" B A &&
+git merge -m "pre E3 merge" A &&
echo "1
2
3 changed in E3, branch B. New file size
@@ -61,7 +61,7 @@ echo "1
" > file &&
git commit -m "E3" file &&
git checkout A &&
-git merge "pre D8 merge" A C3 &&
+git merge -m "pre D8 merge" C3 &&
echo "1
2
3 changed in C3, branch B
@@ -73,7 +73,7 @@ echo "1
9" > file &&
git commit -m D8 file'
-test_expect_success 'Criss-cross merge' 'git merge "final merge" A B'
+test_expect_success 'Criss-cross merge' 'git merge -m "final merge" B'
cat > file-expect <<EOF
1
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 66643e4..855afda 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -394,4 +394,14 @@ test_expect_success 'replace submodule revision' '
test $orig_head != `git show-ref --hash --head HEAD`
'
+test_expect_success 'filter commit message without trailing newline' '
+ git reset --hard original &&
+ commit=$(printf "no newline" | git commit-tree HEAD^{tree}) &&
+ git update-ref refs/heads/no-newline $commit &&
+ git filter-branch -f refs/heads/no-newline &&
+ echo $commit >expect &&
+ git rev-parse refs/heads/no-newline >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index fa207f3..d1ff5c9 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1491,10 +1491,10 @@ run_with_limited_stack () {
(ulimit -s 128 && "$@")
}
-test_lazy_prereq ULIMIT 'run_with_limited_stack true'
+test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true'
# we require ulimit, this excludes Windows
-test_expect_success ULIMIT '--contains works in a deep repo' '
+test_expect_success ULIMIT_STACK_SIZE '--contains works in a deep repo' '
>expect &&
i=1 &&
while test $i -lt 8000
diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh
index 460789b..cdc0747 100755
--- a/t/t7061-wtstatus-ignore.sh
+++ b/t/t7061-wtstatus-ignore.sh
@@ -20,6 +20,15 @@ test_expect_success 'status untracked directory with --ignored' '
test_cmp expected actual
'
+test_expect_success 'same with gitignore starting with BOM' '
+ printf "\357\273\277ignored\n" >.gitignore &&
+ mkdir -p untracked &&
+ : >untracked/ignored &&
+ : >untracked/uncommitted &&
+ git status --porcelain --ignored >actual &&
+ test_cmp expected actual
+'
+
cat >expected <<\EOF
?? .gitignore
?? actual
diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh
new file mode 100755
index 0000000..8f30aed
--- /dev/null
+++ b/t/t7410-submodule-checkout-to.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='Combination of submodules and multiple workdirs'
+
+. ./test-lib.sh
+
+base_path=$(pwd -P)
+
+test_expect_success 'setup: make origin' \
+ 'mkdir -p origin/sub && ( cd origin/sub && git init &&
+ echo file1 >file1 &&
+ git add file1 &&
+ git commit -m file1 ) &&
+ mkdir -p origin/main && ( cd origin/main && git init &&
+ git submodule add ../sub &&
+ git commit -m "add sub" ) &&
+ ( cd origin/sub &&
+ echo file1updated >file1 &&
+ git add file1 &&
+ git commit -m "file1 updated" ) &&
+ ( cd origin/main/sub && git pull ) &&
+ ( cd origin/main &&
+ git add sub &&
+ git commit -m "sub updated" )'
+
+test_expect_success 'setup: clone' \
+ 'mkdir clone && ( cd clone &&
+ git clone --recursive "$base_path/origin/main")'
+
+rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
+rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
+
+test_expect_success 'checkout main' \
+ 'mkdir default_checkout &&
+ (cd clone/main &&
+ git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")'
+
+test_expect_failure 'can see submodule diffs just after checkout' \
+ '(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")'
+
+test_expect_success 'checkout main and initialize independed clones' \
+ 'mkdir fully_cloned_submodule &&
+ (cd clone/main &&
+ git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
+ (cd fully_cloned_submodule/main && git submodule update)'
+
+test_expect_success 'can see submodule diffs after independed cloning' \
+ '(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")'
+
+test_done
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index 051489e..2e0d557 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -370,7 +370,7 @@ exit 0
EOF
test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
- >.git/result
+ >.git/result &&
>expect &&
echo >>negative &&
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
index 32895e5..16f1442 100755
--- a/t/t8003-blame-corner-cases.sh
+++ b/t/t8003-blame-corner-cases.sh
@@ -191,12 +191,24 @@ test_expect_success 'indent of line numbers, ten lines' '
test $(grep -c " " actual) = 9
'
-test_expect_success 'blaming files with CRLF newlines' '
+test_expect_success 'setup file with CRLF newlines' '
git config core.autocrlf false &&
- printf "testcase\r\n" >crlffile &&
+ printf "testcase\n" >crlffile &&
git add crlffile &&
git commit -m testcase &&
- git -c core.autocrlf=input blame crlffile >actual &&
+ printf "testcase\r\n" >crlffile
+'
+
+test_expect_success 'blame file with CRLF core.autocrlf true' '
+ git config core.autocrlf true &&
+ git blame crlffile >actual &&
+ grep "A U Thor" actual
+'
+
+test_expect_success 'blame file with CRLF attributes text' '
+ git config core.autocrlf false &&
+ echo "crlffile text" >.gitattributes &&
+ git blame crlffile >actual &&
grep "A U Thor" actual
'
diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh
index 1e266ef..d00df08 100755
--- a/t/t9402-git-cvsserver-refs.sh
+++ b/t/t9402-git-cvsserver-refs.sh
@@ -496,7 +496,7 @@ test_expect_success 'check [cvswork3] diff' '
'
test_expect_success 'merge early [cvswork3] b3 with b1' '
- ( cd gitwork3 && git merge "message" HEAD b1 ) &&
+ ( cd gitwork3 && git merge -m "message" b1 ) &&
git fetch gitwork3 b3:b3 &&
git tag v3merged b3 &&
git push --tags gitcvs.git b3:b3
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
index 2bf142d..0aafd03 100755
--- a/t/t9801-git-p4-branch.sh
+++ b/t/t9801-git-p4-branch.sh
@@ -504,6 +504,112 @@ test_expect_success 'use-client-spec detect-branches skips files in branches' '
)
'
+test_expect_success 'restart p4d' '
+ kill_p4d &&
+ start_p4d
+'
+
+#
+# 1: //depot/branch1/base/file1
+# //depot/branch1/base/file2
+# //depot/branch1/base/dir/sub_file1
+# 2: integrate //depot/branch1/base/... -> //depot/branch2/base/...
+# 3: //depot/branch1/base/file3
+# 4: //depot/branch1/base/file2 (edit)
+# 5: integrate //depot/branch1/base/... -> //depot/branch3/base/...
+#
+# Note: the client view removes the "base" folder from the workspace
+# and moves sub_file1 one level up.
+test_expect_success 'add simple p4 branches with common base folder on each branch' '
+ (
+ cd "$cli" &&
+ client_view "//depot/branch1/base/... //client/branch1/..." \
+ "//depot/branch1/base/dir/sub_file1 //client/branch1/sub_file1" \
+ "//depot/branch2/base/... //client/branch2/..." \
+ "//depot/branch3/base/... //client/branch3/..." &&
+ mkdir -p branch1 &&
+ cd branch1 &&
+ echo file1 >file1 &&
+ echo file2 >file2 &&
+ mkdir dir &&
+ echo sub_file1 >sub_file1 &&
+ p4 add file1 file2 sub_file1 &&
+ p4 submit -d "Create branch1" &&
+ p4 integrate //depot/branch1/base/... //depot/branch2/base/... &&
+ p4 submit -d "Integrate branch2 from branch1" &&
+ echo file3 >file3 &&
+ p4 add file3 &&
+ p4 submit -d "add file3 in branch1" &&
+ p4 open file2 &&
+ echo update >>file2 &&
+ p4 submit -d "update file2 in branch1" &&
+ p4 integrate //depot/branch1/base/... //depot/branch3/base/... &&
+ p4 submit -d "Integrate branch3 from branch1"
+ )
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git p4.
+# git p4 is expected to use the client view to also not include the common
+# "base" folder in the imported directory structure.
+test_expect_success 'git p4 clone simple branches with base folder on server side' '
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList branch1:branch2 &&
+ git config --add git-p4.branchList branch1:branch3 &&
+ git p4 clone --dest=. --use-client-spec --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch1 &&
+ test -f file1 &&
+ test -f file2 &&
+ test -f file3 &&
+ test -f sub_file1 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch2 &&
+ test -f file1 &&
+ test -f file2 &&
+ test ! -f file3 &&
+ test -f sub_file1 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch3 &&
+ test -f file1 &&
+ test -f file2 &&
+ test -f file3 &&
+ test -f sub_file1 &&
+ grep update file2 &&
+ cd "$cli" &&
+ cd branch1 &&
+ p4 edit file2 &&
+ echo file2_ >>file2 &&
+ p4 submit -d "update file2 in branch1" &&
+ cd "$git" &&
+ git reset --hard p4/depot/branch1 &&
+ git p4 rebase &&
+ grep file2_ file2
+ )
+'
+
+# Now update a file in one of the branches in git and submit to P4
+test_expect_success 'Update a file in git side and submit to P4 using client view' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git reset --hard p4/depot/branch1 &&
+ echo "client spec" >> file1 &&
+ git add -u . &&
+ git commit -m "update file1 in branch1" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit --verbose &&
+ cd "$cli" &&
+ p4 sync ... &&
+ cd branch1 &&
+ grep "client spec" file1
+ )
+'
+
test_expect_success 'kill p4d' '
kill_p4d
'
diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh
index 99bb71b..c89992c 100755
--- a/t/t9814-git-p4-rename.sh
+++ b/t/t9814-git-p4-rename.sh
@@ -226,14 +226,9 @@ test_expect_success 'detect copies' '
# See if configurables can be set, and in particular if the run.move.allow
# variable exists, which allows admins to disable the "p4 move" command.
-test_expect_success 'p4 configure command and run.move.allow are available' '
- p4 configure show run.move.allow >out ; retval=$? &&
- test $retval = 0 &&
- {
- egrep ^run.move.allow: out &&
- test_set_prereq P4D_HAVE_CONFIGURABLE_RUN_MOVE_ALLOW ||
- true
- } || true
+test_lazy_prereq P4D_HAVE_CONFIGURABLE_RUN_MOVE_ALLOW '
+ p4 configure show run.move.allow >out &&
+ egrep ^run.move.allow: out
'
# If move can be disabled, turn it off and test p4 move handling
diff --git a/t/t9816-git-p4-locked.sh b/t/t9816-git-p4-locked.sh
index e71e543..d048bd3 100755
--- a/t/t9816-git-p4-locked.sh
+++ b/t/t9816-git-p4-locked.sh
@@ -35,13 +35,13 @@ test_expect_success 'edit with lock not taken' '
)
'
-test_expect_failure 'add with lock not taken' '
+test_expect_success 'add with lock not taken' '
test_when_finished cleanup_git &&
git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
echo line1 >>add-lock-not-taken &&
- git add file2 &&
+ git add add-lock-not-taken &&
git commit -m "add add-lock-not-taken" &&
git config git-p4.skipSubmitEdit true &&
git p4 submit --verbose
@@ -107,7 +107,7 @@ test_expect_failure 'chmod with lock taken' '
)
'
-test_expect_failure 'copy with lock taken' '
+test_expect_success 'copy with lock taken' '
lock_in_another_client &&
test_when_finished cleanup_git &&
test_when_finished "cd \"$cli\" && p4 revert file2 && rm -f file2" &&
@@ -130,8 +130,8 @@ test_expect_failure 'move with lock taken' '
git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
- git mv file1 file2 &&
- git commit -m "mv file1 to file2" &&
+ git mv file1 file3 &&
+ git commit -m "mv file1 to file3" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.detectRenames true &&
git p4 submit --verbose
diff --git a/t/t9818-git-p4-block.sh b/t/t9818-git-p4-block.sh
new file mode 100755
index 0000000..153b20a
--- /dev/null
+++ b/t/t9818-git-p4-block.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='git p4 fetching changes in multiple blocks'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'Create a repo with ~100 changes' '
+ (
+ cd "$cli" &&
+ >file.txt &&
+ p4 add file.txt &&
+ p4 submit -d "Add file.txt" &&
+ for i in $(test_seq 0 9)
+ do
+ >outer$i.txt &&
+ p4 add outer$i.txt &&
+ p4 submit -d "Adding outer$i.txt" &&
+ for j in $(test_seq 0 9)
+ do
+ p4 edit file.txt &&
+ echo $i$j >file.txt &&
+ p4 submit -d "Commit $i$j" || exit
+ done || exit
+ done
+ )
+'
+
+test_expect_success 'Clone the repo' '
+ git p4 clone --dest="$git" --changes-block-size=10 --verbose //depot@all
+'
+
+test_expect_success 'All files are present' '
+ echo file.txt >expected &&
+ test_write_lines outer0.txt outer1.txt outer2.txt outer3.txt outer4.txt >>expected &&
+ test_write_lines outer5.txt outer6.txt outer7.txt outer8.txt outer9.txt >>expected &&
+ ls "$git" >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'file.txt is correct' '
+ echo 99 >expected &&
+ test_cmp expected "$git/file.txt"
+'
+
+test_expect_success 'Correct number of commits' '
+ (cd "$git" && git log --oneline) >log &&
+ test_line_count = 111 log
+'
+
+test_expect_success 'Previous version of file.txt is correct' '
+ (cd "$git" && git checkout HEAD^^) &&
+ echo 97 >expected &&
+ test_cmp expected "$git/file.txt"
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9819-git-p4-case-folding.sh b/t/t9819-git-p4-case-folding.sh
new file mode 100755
index 0000000..78f1d0f
--- /dev/null
+++ b/t/t9819-git-p4-case-folding.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+test_description='interaction with P4 case-folding'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d with case folding enabled' '
+ start_p4d -C1
+'
+
+test_expect_success 'Create a repo, name is lowercase' '
+ (
+ client_view "//depot/... //client/..." &&
+ cd "$cli" &&
+ mkdir -p lc UC &&
+ >lc/file.txt && >UC/file.txt &&
+ p4 add lc/file.txt UC/file.txt &&
+ p4 submit -d "Add initial lc and UC repos"
+ )
+'
+
+test_expect_success 'Check p4 is in case-folding mode' '
+ (
+ cd "$cli" &&
+ >lc/FILE.TXT &&
+ p4 add lc/FILE.TXT &&
+ test_must_fail p4 submit -d "Cannot add file differing only in case" lc/FILE.TXT
+ )
+'
+
+# Check we created the repo properly
+test_expect_success 'Clone lc repo using lc name' '
+ git p4 clone //depot/lc/... &&
+ test_path_is_file lc/file.txt &&
+ git p4 clone //depot/UC/... &&
+ test_path_is_file UC/file.txt
+'
+
+# The clone should fail, since there is no repo called LC, but because
+# we have case-insensitive p4d enabled, it appears to go ahead and work,
+# but leaves an empty git repo in place.
+test_expect_failure 'Clone lc repo using uc name' '
+ test_must_fail git p4 clone //depot/LC/...
+'
+
+test_expect_failure 'Clone UC repo with lc name' '
+ test_must_fail git p4 clone //depot/uc/...
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 4a14a58..2ba62fb 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -370,6 +370,40 @@ test_expect_success '__git_remotes - list remotes from $GIT_DIR/remotes and from
test_cmp expect actual
'
+test_expect_success '__git_get_config_variables' '
+ cat >expect <<-EOF &&
+ name-1
+ name-2
+ EOF
+ test_config interesting.name-1 good &&
+ test_config interesting.name-2 good &&
+ test_config subsection.interesting.name-3 bad &&
+ __git_get_config_variables interesting >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '__git_pretty_aliases' '
+ cat >expect <<-EOF &&
+ author
+ hash
+ EOF
+ test_config pretty.author "%an %ae" &&
+ test_config pretty.hash %H &&
+ __git_pretty_aliases >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '__git_aliases' '
+ cat >expect <<-EOF &&
+ ci
+ co
+ EOF
+ test_config alias.ci commit &&
+ test_config alias.co checkout &&
+ __git_aliases >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'basic' '
run_completion "git " &&
# built-in
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 0698ce7..e8d3c0f 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -348,11 +348,18 @@ test_declared_prereq () {
return 1
}
+test_verify_prereq () {
+ test -z "$test_prereq" ||
+ expr >/dev/null "$test_prereq" : '[A-Z0-9_,!]*$' ||
+ error "bug in the test script: '$test_prereq' does not look like a prereq"
+}
+
test_expect_failure () {
test_start_
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
+ test_verify_prereq
export test_prereq
if ! test_skip "$@"
then
@@ -372,6 +379,7 @@ test_expect_success () {
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ test_verify_prereq
export test_prereq
if ! test_skip "$@"
then
@@ -400,6 +408,7 @@ test_external () {
error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
descr="$1"
shift
+ test_verify_prereq
export test_prereq
if ! test_skip "$descr" "$@"
then
@@ -478,7 +487,7 @@ test_external_without_stderr () {
test_path_is_file () {
if ! test -f "$1"
then
- echo "File $1 doesn't exist. $*"
+ echo "File $1 doesn't exist. $2"
false
fi
}
@@ -486,7 +495,7 @@ test_path_is_file () {
test_path_is_dir () {
if ! test -d "$1"
then
- echo "Directory $1 doesn't exist. $*"
+ echo "Directory $1 doesn't exist. $2"
false
fi
}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 4ea99a2..39da9c2 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -529,7 +529,7 @@ test_run_ () {
test_cleanup=:
expecting_failure=$2
- if test "${GIT_TEST_CHAIN_LINT:-0}" != 0; then
+ if test "${GIT_TEST_CHAIN_LINT:-1}" != 0; then
# 117 is magic because it is unlikely to match the exit
# code of other programs
test_eval_ "(exit 117) && $1"
diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample
index 8b2a2fe..a5d7b84 100755
--- a/templates/hooks--applypatch-msg.sample
+++ b/templates/hooks--applypatch-msg.sample
@@ -10,6 +10,6 @@
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
- exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample
index b1f187c..4142082 100755
--- a/templates/hooks--pre-applypatch.sample
+++ b/templates/hooks--pre-applypatch.sample
@@ -9,6 +9,6 @@
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
- exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit="$(git rev-parse --git-path hooks/pre-commit)"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
diff --git a/trace.c b/trace.c
index 1dc5c7c..3c3bd8f 100644
--- a/trace.c
+++ b/trace.c
@@ -310,6 +310,7 @@ void trace_repo_setup(const char *prefix)
prefix = "(null)";
trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+ trace_printf_key(&key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd));
trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix));
diff --git a/transport.c b/transport.c
index eca9b8c..f080e93 100644
--- a/transport.c
+++ b/transport.c
@@ -283,7 +283,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
{
struct strbuf *buf = data;
int len = buf->len;
- FILE *f;
/* when called via for_each_ref(), flags is non-zero */
if (flags && !starts_with(name, "refs/heads/") &&
@@ -292,10 +291,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
strbuf_addstr(buf, name);
if (safe_create_leading_directories(buf->buf) ||
- !(f = fopen(buf->buf, "w")) ||
- fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
- fclose(f))
- return error("problems writing temporary file %s", buf->buf);
+ write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1)))
+ return error("problems writing temporary file %s: %s",
+ buf->buf, strerror(errno));
strbuf_setlen(buf, len);
return 0;
}
diff --git a/tree-diff.c b/tree-diff.c
index e7b378c..290a1da 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -64,7 +64,7 @@ static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_
{
struct combine_diff_parent *p0 = &p->parent[0];
if (p->mode && p0->mode) {
- opt->change(opt, p0->mode, p->mode, p0->sha1, p->sha1,
+ opt->change(opt, p0->mode, p->mode, p0->oid.hash, p->oid.hash,
1, 1, p->path, 0, 0);
}
else {
@@ -74,11 +74,11 @@ static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_
if (p->mode) {
addremove = '+';
- sha1 = p->sha1;
+ sha1 = p->oid.hash;
mode = p->mode;
} else {
addremove = '-';
- sha1 = p0->sha1;
+ sha1 = p0->oid.hash;
mode = p0->mode;
}
@@ -151,7 +151,7 @@ static struct combine_diff_path *path_appendnew(struct combine_diff_path *last,
memcpy(p->path + base->len, path, pathlen);
p->path[len] = 0;
p->mode = mode;
- hashcpy(p->sha1, sha1 ? sha1 : null_sha1);
+ hashcpy(p->oid.hash, sha1 ? sha1 : null_sha1);
return p;
}
@@ -238,7 +238,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p,
}
p->parent[i].mode = mode_i;
- hashcpy(p->parent[i].sha1, sha1_i ? sha1_i : null_sha1);
+ hashcpy(p->parent[i].oid.hash, sha1_i ? sha1_i : null_sha1);
}
keep = 1;
diff --git a/upload-pack.c b/upload-pack.c
index aa84576..745fda8 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -74,7 +74,7 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
{
FILE *fp = cb_data;
if (graft->nr_parent == -1)
- fprintf(fp, "--shallow %s\n", sha1_to_hex(graft->sha1));
+ fprintf(fp, "--shallow %s\n", oid_to_hex(&graft->oid));
return 0;
}
diff --git a/utf8.c b/utf8.c
index 520fbb4..28e6d76 100644
--- a/utf8.c
+++ b/utf8.c
@@ -633,3 +633,14 @@ int is_hfs_dotgit(const char *path)
return 1;
}
+
+const char utf8_bom[] = "\357\273\277";
+
+int skip_utf8_bom(char **text, size_t len)
+{
+ if (len < strlen(utf8_bom) ||
+ memcmp(*text, utf8_bom, strlen(utf8_bom)))
+ return 0;
+ *text += strlen(utf8_bom);
+ return 1;
+}
diff --git a/utf8.h b/utf8.h
index e4d9183..e7b2aa4 100644
--- a/utf8.h
+++ b/utf8.h
@@ -13,6 +13,9 @@ int same_encoding(const char *, const char *);
__attribute__((format (printf, 2, 3)))
int utf8_fprintf(FILE *, const char *, ...);
+extern const char utf8_bom[];
+extern int skip_utf8_bom(char **, size_t);
+
void strbuf_add_wrapped_text(struct strbuf *buf,
const char *text, int indent, int indent2, int width);
void strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len,
diff --git a/wrapper.c b/wrapper.c
index d5a6cef..c1a663f 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -564,3 +564,34 @@ char *xgetcwd(void)
die_errno(_("unable to get current working directory"));
return strbuf_detach(&sb, NULL);
}
+
+int write_file(const char *path, int fatal, const char *fmt, ...)
+{
+ struct strbuf sb = STRBUF_INIT;
+ va_list params;
+ int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0) {
+ if (fatal)
+ die_errno(_("could not open %s for writing"), path);
+ return -1;
+ }
+ va_start(params, fmt);
+ strbuf_vaddf(&sb, fmt, params);
+ va_end(params);
+ if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
+ int err = errno;
+ close(fd);
+ strbuf_release(&sb);
+ errno = err;
+ if (fatal)
+ die_errno(_("could not write to %s"), path);
+ return -1;
+ }
+ strbuf_release(&sb);
+ if (close(fd)) {
+ if (fatal)
+ die_errno(_("could not close %s"), path);
+ return -1;
+ }
+ return 0;
+}