summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format18
-rw-r--r--Documentation/Makefile1
-rw-r--r--Documentation/RelNotes/2.10.5.txt17
-rw-r--r--Documentation/RelNotes/2.11.4.txt17
-rw-r--r--Documentation/RelNotes/2.12.5.txt17
-rw-r--r--Documentation/RelNotes/2.13.6.txt17
-rw-r--r--Documentation/RelNotes/2.14.2.txt11
-rw-r--r--Documentation/RelNotes/2.15.0.txt146
-rw-r--r--Documentation/config.txt47
-rw-r--r--Documentation/git-add.txt3
-rw-r--r--Documentation/git-branch.txt18
-rw-r--r--Documentation/git-describe.txt24
-rw-r--r--Documentation/git-filter-branch.txt8
-rw-r--r--Documentation/git-for-each-ref.txt13
-rw-r--r--Documentation/git-grep.txt6
-rw-r--r--Documentation/git-read-tree.txt2
-rw-r--r--Documentation/git-rebase.txt16
-rw-r--r--Documentation/git-shell.txt16
-rw-r--r--Documentation/git-status.txt2
-rw-r--r--Documentation/git-tag.txt7
-rw-r--r--Documentation/git.txt14
-rw-r--r--Documentation/githooks.txt9
-rw-r--r--Documentation/glossary-content.txt2
-rw-r--r--Documentation/merge-strategies.txt3
-rw-r--r--Documentation/technical/hash-function-transition.txt797
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--Makefile7
-rw-r--r--abspath.c4
-rw-r--r--apply.c3
-rw-r--r--bisect.c3
-rw-r--r--branch.c3
-rw-r--r--builtin/branch.c75
-rw-r--r--builtin/cat-file.c5
-rw-r--r--builtin/checkout.c14
-rw-r--r--builtin/commit.c23
-rw-r--r--builtin/describe.c36
-rw-r--r--builtin/diff-index.c1
-rw-r--r--builtin/diff.c3
-rw-r--r--builtin/fast-export.c49
-rw-r--r--builtin/for-each-ref.c1
-rw-r--r--builtin/fsck.c7
-rw-r--r--builtin/get-tar-commit-id.c6
-rw-r--r--builtin/log.c3
-rw-r--r--builtin/name-rev.c1
-rw-r--r--builtin/pack-objects.c4
-rw-r--r--builtin/rebase--helper.c29
-rw-r--r--builtin/receive-pack.c6
-rw-r--r--builtin/reflog.c6
-rw-r--r--builtin/remote-ext.c2
-rw-r--r--builtin/remote.c3
-rw-r--r--builtin/rev-parse.c18
-rw-r--r--builtin/submodule--helper.c7
-rw-r--r--builtin/symbolic-ref.c3
-rw-r--r--builtin/tag.c10
-rw-r--r--builtin/worktree.c24
-rw-r--r--bulk-checkin.c5
-rw-r--r--bundle.c9
-rw-r--r--cache.h10
-rw-r--r--color.c2
-rw-r--r--commit-slab.h6
-rw-r--r--commit.c1
-rw-r--r--compat/poll/poll.c4
-rw-r--r--compat/win32/lazyload.h57
-rw-r--r--config.c117
-rw-r--r--config.h2
-rw-r--r--config.mak.uname3
-rw-r--r--contrib/coccinelle/array.cocci10
-rw-r--r--convert.c3
-rw-r--r--csum-file.c2
-rw-r--r--diff-delta.c27
-rw-r--r--diff-lib.c3
-rw-r--r--diff.c3
-rw-r--r--dir.c47
-rw-r--r--environment.c5
-rw-r--r--fast-import.c6
-rw-r--r--fetch-pack.c2
-rw-r--r--fsck.c1
-rwxr-xr-xgit-archimport.perl4
-rw-r--r--git-compat-util.h6
-rwxr-xr-xgit-cvsimport.perl1
-rwxr-xr-xgit-cvsserver.perl77
-rwxr-xr-xgit-filter-branch.sh94
-rw-r--r--git-rebase--interactive.sh373
-rwxr-xr-xgit-rebase.sh3
-rwxr-xr-xgit-request-pull.sh2
-rw-r--r--git.c4
-rw-r--r--graph.c10
-rw-r--r--http-backend.c3
-rw-r--r--http-push.c3
-rw-r--r--http.c7
-rw-r--r--line-log.c18
-rw-r--r--line-log.h2
-rw-r--r--log-tree.c3
-rw-r--r--mailinfo.c12
-rw-r--r--notes-merge.c2
-rw-r--r--object.c13
-rw-r--r--object.h8
-rw-r--r--oidmap.c51
-rw-r--r--oidmap.h68
-rw-r--r--oidset.c36
-rw-r--r--oidset.h6
-rw-r--r--pack-bitmap-write.c4
-rw-r--r--pack-bitmap.c10
-rw-r--r--pack-write.c7
-rw-r--r--packfile.c11
-rw-r--r--parse-options.c12
-rw-r--r--path.c34
-rw-r--r--pkt-line.c2
-rw-r--r--pretty.c33
-rw-r--r--quote.c1
-rw-r--r--read-cache.c1
-rw-r--r--ref-filter.c56
-rw-r--r--reflog-walk.c6
-rw-r--r--refs.c42
-rw-r--r--refs.h18
-rw-r--r--refs/files-backend.c109
-rw-r--r--refs/iterator.c47
-rw-r--r--refs/packed-backend.c991
-rw-r--r--refs/ref-cache.c44
-rw-r--r--refs/ref-cache.h35
-rw-r--r--refs/refs-internal.h30
-rw-r--r--remote.c12
-rw-r--r--repository.c27
-rw-r--r--revision.c5
-rw-r--r--revision.h11
-rw-r--r--run-command.c2
-rw-r--r--send-pack.c2
-rw-r--r--sequencer.c533
-rw-r--r--sequencer.h8
-rw-r--r--setup.c5
-rw-r--r--sha1_file.c11
-rw-r--r--shallow.c2
-rw-r--r--shell.c14
-rw-r--r--strbuf.h11
-rw-r--r--sub-process.c3
-rw-r--r--submodule.c10
-rw-r--r--submodule.h2
-rw-r--r--t/helper/test-line-buffer.c32
-rw-r--r--t/helper/test-parse-options.c2
-rw-r--r--t/helper/test-string-list.c2
-rwxr-xr-xt/t0040-parse-options.sh2
-rwxr-xr-xt/t1502-rev-parse-parseopt.sh112
-rwxr-xr-xt/t3200-branch.sh256
-rwxr-xr-xt/t3203-branch-output.sh8
-rwxr-xr-xt/t3205-branch-color.sh5
-rwxr-xr-xt/t3404-rebase-interactive.sh22
-rwxr-xr-xt/t3415-rebase-autosquash.sh28
-rwxr-xr-xt/t3701-add-interactive.sh18
-rwxr-xr-xt/t4013-diff-various.sh12
-rw-r--r--t/t4013/diff.diff-tree_--stat_initial_mode4
-rw-r--r--t/t4013/diff.diff-tree_--summary_initial_mode3
-rw-r--r--t/t4013/diff.diff-tree_initial_mode3
-rw-r--r--t/t4013/diff.log_--decorate=full_--all6
-rw-r--r--t/t4013/diff.log_--decorate_--all6
-rwxr-xr-xt/t4015-diff-whitespace.sh53
-rwxr-xr-xt/t4059-diff-submodule-not-initialized.sh2
-rwxr-xr-xt/t4202-log.sh2
-rwxr-xr-xt/t4205-log-pretty-formats.sh6
-rwxr-xr-xt/t5150-request-pull.sh4
-rwxr-xr-xt/t6006-rev-list-format.sh23
-rwxr-xr-xt/t6120-describe.sh33
-rwxr-xr-xt/t6132-pathspec-exclude.sh13
-rwxr-xr-xt/t6300-for-each-ref.sh98
-rwxr-xr-xt/t7004-tag.sh6
-rwxr-xr-xt/t7006-pager.sh6
-rwxr-xr-xt/t7301-clean-interactive.sh5
-rwxr-xr-xt/t7406-submodule-update.sh8
-rwxr-xr-xt/t7502-commit.sh4
-rwxr-xr-xt/t7508-status.sh51
-rwxr-xr-xt/t8010-cat-file-filters.sh5
-rwxr-xr-xt/t9300-fast-import.sh142
-rwxr-xr-xt/t9350-fast-export.sh20
-rwxr-xr-xt/t9400-git-cvsserver-server.sh48
-rwxr-xr-xt/test-terminal.perl1
-rw-r--r--tag.c8
-rw-r--r--transport.c6
-rw-r--r--tree-walk.c3
-rw-r--r--upload-pack.c5
-rw-r--r--userdiff.c2
-rw-r--r--worktree.c3
-rw-r--r--wt-status.c4
181 files changed, 4590 insertions, 1363 deletions
diff --git a/.clang-format b/.clang-format
index 3ede2628d2..611ab4750b 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,4 +1,8 @@
-# Defaults
+# This file is an example configuration for clang-format 5.0.
+#
+# Note that this style definition should only be understood as a hint
+# for writing new code. The rules are still work-in-progress and does
+# not yet exactly match the style we have in the existing code.
# Use tabs whenever we need to fill whitespace that spans at least from one tab
# stop to the next one.
@@ -153,13 +157,13 @@ KeepEmptyLinesAtTheStartOfBlocks: false
# Penalties
# This decides what order things should be done if a line is too long
-PenaltyBreakAssignment: 100
-PenaltyBreakBeforeFirstCallParameter: 100
-PenaltyBreakComment: 100
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
-PenaltyBreakString: 100
-PenaltyExcessCharacter: 5
-PenaltyReturnTypeOnItsOwnLine: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 5
# Don't sort #include's
SortIncludes: false
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 2415e0d657..471bb29725 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -67,6 +67,7 @@ SP_ARTICLES += howto/maintain-git
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
SP_ARTICLES += $(API_DOCS)
+TECH_DOCS += technical/hash-function-transition
TECH_DOCS += technical/http-protocol
TECH_DOCS += technical/index-format
TECH_DOCS += technical/pack-format
diff --git a/Documentation/RelNotes/2.10.5.txt b/Documentation/RelNotes/2.10.5.txt
new file mode 100644
index 0000000000..a498fd6fdc
--- /dev/null
+++ b/Documentation/RelNotes/2.10.5.txt
@@ -0,0 +1,17 @@
+Git v2.10.5 Release Notes
+=========================
+
+Fixes since v2.10.4
+-------------------
+
+ * "git cvsserver" no longer is invoked by "git daemon" by default,
+ as it is old and largely unmaintained.
+
+ * Various Perl scripts did not use safe_pipe_capture() instead of
+ backticks, leaving them susceptible to end-user input. They have
+ been corrected.
+
+Credits go to joernchen <joernchen@phenoelit.de> for finding the
+unsafe constructs in "git cvsserver", and to Jeff King at GitHub for
+finding and fixing instances of the same issue in other scripts.
+
diff --git a/Documentation/RelNotes/2.11.4.txt b/Documentation/RelNotes/2.11.4.txt
new file mode 100644
index 0000000000..ad4da8eb09
--- /dev/null
+++ b/Documentation/RelNotes/2.11.4.txt
@@ -0,0 +1,17 @@
+Git v2.11.4 Release Notes
+=========================
+
+Fixes since v2.11.3
+-------------------
+
+ * "git cvsserver" no longer is invoked by "git daemon" by default,
+ as it is old and largely unmaintained.
+
+ * Various Perl scripts did not use safe_pipe_capture() instead of
+ backticks, leaving them susceptible to end-user input. They have
+ been corrected.
+
+Credits go to joernchen <joernchen@phenoelit.de> for finding the
+unsafe constructs in "git cvsserver", and to Jeff King at GitHub for
+finding and fixing instances of the same issue in other scripts.
+
diff --git a/Documentation/RelNotes/2.12.5.txt b/Documentation/RelNotes/2.12.5.txt
new file mode 100644
index 0000000000..8fa73cfce7
--- /dev/null
+++ b/Documentation/RelNotes/2.12.5.txt
@@ -0,0 +1,17 @@
+Git v2.12.5 Release Notes
+=========================
+
+Fixes since v2.12.4
+-------------------
+
+ * "git cvsserver" no longer is invoked by "git daemon" by default,
+ as it is old and largely unmaintained.
+
+ * Various Perl scripts did not use safe_pipe_capture() instead of
+ backticks, leaving them susceptible to end-user input. They have
+ been corrected.
+
+Credits go to joernchen <joernchen@phenoelit.de> for finding the
+unsafe constructs in "git cvsserver", and to Jeff King at GitHub for
+finding and fixing instances of the same issue in other scripts.
+
diff --git a/Documentation/RelNotes/2.13.6.txt b/Documentation/RelNotes/2.13.6.txt
new file mode 100644
index 0000000000..afcae9c808
--- /dev/null
+++ b/Documentation/RelNotes/2.13.6.txt
@@ -0,0 +1,17 @@
+Git v2.13.6 Release Notes
+=========================
+
+Fixes since v2.13.5
+-------------------
+
+ * "git cvsserver" no longer is invoked by "git daemon" by default,
+ as it is old and largely unmaintained.
+
+ * Various Perl scripts did not use safe_pipe_capture() instead of
+ backticks, leaving them susceptible to end-user input. They have
+ been corrected.
+
+Credits go to joernchen <joernchen@phenoelit.de> for finding the
+unsafe constructs in "git cvsserver", and to Jeff King at GitHub for
+finding and fixing instances of the same issue in other scripts.
+
diff --git a/Documentation/RelNotes/2.14.2.txt b/Documentation/RelNotes/2.14.2.txt
index bcfe78f59d..bec9186ade 100644
--- a/Documentation/RelNotes/2.14.2.txt
+++ b/Documentation/RelNotes/2.14.2.txt
@@ -91,4 +91,15 @@ Fixes since v2.14.1
* "git archive" did not work well with pathspecs and the
export-ignore attribute.
+ * "git cvsserver" no longer is invoked by "git daemon" by default,
+ as it is old and largely unmaintained.
+
+ * Various Perl scripts did not use safe_pipe_capture() instead of
+ backticks, leaving them susceptible to end-user input. They have
+ been corrected.
+
Also contains various documentation updates and code clean-ups.
+
+Credits go to joernchen <joernchen@phenoelit.de> for finding the
+unsafe constructs in "git cvsserver", and to Jeff King at GitHub for
+finding and fixing instances of the same issue in other scripts.
diff --git a/Documentation/RelNotes/2.15.0.txt b/Documentation/RelNotes/2.15.0.txt
index 290cad5287..7b8eeb52b6 100644
--- a/Documentation/RelNotes/2.15.0.txt
+++ b/Documentation/RelNotes/2.15.0.txt
@@ -8,12 +8,12 @@ Backward compatibility notes and other notable changes.
more explicit '.' for that instead. The hope is that existing
users will not mind this change, and eventually the warning can be
turned into a hard error, upgrading the deprecation into removal of
- this (mis)feature. That is now scheduled to happen in the upcoming
- release.
+ this (mis)feature. That is now scheduled to happen in Git v2.16,
+ the next major release after this one.
* Git now avoids blindly falling back to ".git" when the setup
sequence said we are _not_ in Git repository. A corner case that
- happens to work right now may be broken by a call to die("BUG").
+ happens to work right now may be broken by a call to BUG().
We've tried hard to locate such cases and fixed them, but there
might still be cases that need to be addressed--bug reports are
greatly appreciated.
@@ -61,6 +61,10 @@ UI, Workflows & Features
other options to make it easier for scripts to grab existing
trailer lines from a commit log message.
+ * The "--format=%(trailers)" option "git log" and its friends take
+ learned to take the 'unfold' and 'only' modifiers to normalize its
+ output, e.g. "git log --format=%(trailers:only,unfold)".
+
* "gitweb" shows a link to visit the 'raw' contents of blbos in the
history overview page.
@@ -84,6 +88,17 @@ UI, Workflows & Features
used in a way similar to existing "--is-bare-repository" and
friends.
+ * "git describe --match <pattern>" has been taught to play well with
+ the "--all" option.
+
+ * "git branch" learned "-c/-C" to create a new branch by copying an
+ existing one.
+
+ * Some commands (most notably "git status") makes an opportunistic
+ update when performing a read-only operation to help optimize later
+ operations in the same repository. The new "--no-optional-locks"
+ option can be passed to Git to disable them.
+
Performance, Internal Implementation, Development Support etc.
@@ -189,6 +204,32 @@ Performance, Internal Implementation, Development Support etc.
expect failures under a limited stack situation. This has been
fixed.
+ * Many codepaths have been updated to squelch -Wimplicit-fallthrough
+ warnings from Gcc 7 (which is a good code hygiene).
+
+ * Add a helper for DLL loading in anticipation for its need in a
+ future topic RSN.
+
+ * "git status --ignored", when noticing that a directory without any
+ tracked path is ignored, still enumerated all the ignored paths in
+ the directory, which is unnecessary. The codepath has been
+ optimized to avoid this overhead.
+
+ * The final batch to "git rebase -i" updates to move more code from
+ the shell script to C has been merged.
+
+ * Operations that do not touch (majority of) packed refs have been
+ optimized by making accesses to packed-refs file lazy; we no longer
+ pre-parse everything, and an access to a single ref in the
+ packed-refs does not touch majority of irrelevant refs, either.
+
+ * Add comment to clarify that the style file is meant to be used with
+ clang-5 and the rules are still work in progress.
+
+ * Many variables that points at a region of memory that will live
+ throughout the life of the program have been marked with UNLEAK
+ marker to help the leak checkers concentrate on real leaks..
+
Also contains various documentation updates and code clean-ups.
@@ -359,6 +400,91 @@ Fixes since v2.14
alternate object stores overrun the end of the string.
(merge f0f7bebef7 jk/info-alternates-fix later to maint).
+ * "git describe --match" learned to take multiple patterns in v2.13
+ series, but the feature ignored the patterns after the first one
+ and did not work at all. This has been fixed.
+ (merge da769d2986 jk/describe-omit-some-refs later to maint).
+
+ * "git filter-branch" cannot reproduce a history with a tag without
+ the tagger field, which only ancient versions of Git allowed to be
+ created. This has been corrected.
+ (merge b2c1ca6b4b ic/fix-filter-branch-to-handle-tag-without-tagger later to maint).
+
+ * "git cat-file --textconv" started segfaulting recently, which
+ has been corrected.
+ (merge cc0ea7c9e5 jk/diff-blob later to maint).
+
+ * The built-in pattern to detect the "function header" for HTML did
+ not match <H1>..<H6> elements without any attributes, which has
+ been fixed.
+ (merge 9c03caca2c ik/userdiff-html-h-element-fix later to maint).
+
+ * "git mailinfo" was loose in decoding quoted printable and produced
+ garbage when the two letters after the equal sign are not
+ hexadecimal. This has been fixed.
+ (merge c8cf423eab rs/mailinfo-qp-decode-fix later to maint).
+
+ * The machinery to create xdelta used in pack files received the
+ sizes of the data in size_t, but lost the higher bits of them by
+ storing them in "unsigned int" during the computation, which is
+ fixed.
+
+ * The delta format used in the packfile cannot reference data at
+ offset larger than what can be expressed in 4-byte, but the
+ generator for the data failed to make sure the offset does not
+ overflow. This has been corrected.
+
+ * The documentation for '-X<option>' for merges was misleadingly
+ written to suggest that "-s theirs" exists, which is not the case.
+ (merge c25d98b2a7 jc/merge-x-theirs-docfix later to maint).
+
+ * "git fast-export" with -M/-C option issued "copy" instruction on a
+ path that is simultaneously modified, which was incorrect.
+ (merge b3e8ca89cf jt/fast-export-copy-modify-fix later to maint).
+
+ * Many codepaths have been updated to squelch -Wsign-compare
+ warnings.
+ (merge 071bcaab64 rj/no-sign-compare later to maint).
+
+ * Memory leaks in various codepaths have been plugged.
+ (merge 4d01a7fa65 ma/leakplugs later to maint).
+
+ * Recent versions of "git rev-parse --parseopt" did not parse the
+ option specification that does not have the optional flags (*=?!)
+ correctly, which has been corrected.
+ (merge a6304fa4c2 bc/rev-parse-parseopt-fix later to maint).
+
+ * The checkpoint command "git fast-import" did not flush updates to
+ refs and marks unless at least one object was created since the
+ last checkpoint, which has been corrected, as these things can
+ happen without any new object getting created.
+ (merge 30e215a65c er/fast-import-dump-refs-on-checkpoint later to maint).
+
+ * Spell the name of our system as "Git" in the output from
+ request-pull script.
+ (merge e66d7c37a5 ar/request-pull-phrasofix later to maint).
+
+ * Fixes for a handful memory access issues identified by valgrind.
+ (merge 2944a94c6b tg/memfixes later to maint).
+
+ * Backports a moral equivalent of 2015 fix to the poll() emulation
+ from the upstream gnulib to fix occasional breakages on HPE NonStop.
+ (merge 61b2a1acaa rb/compat-poll-fix later to maint).
+
+ * Users with "color.ui = always" in their configuration were broken
+ by a recent change that made plumbing commands to pay attention to
+ them as the patch created internally by "git add -p" were colored
+ (heh) and made unusable. Fix this regression by redefining
+ 'always' to mean the same thing as 'auto'.
+ (merge 6be4595edb jk/ui-color-always-to-auto-maint later to maint).
+
+ * In the "--format=..." option of the "git for-each-ref" command (and
+ its friends, i.e. the listing mode of "git branch/tag"), "%(atom:)"
+ (e.g. "%(refname:)", "%(body:)" used to error out. Instead, treat
+ them as if the colon and an empty string that follows it were not
+ there.
+ (merge bea4dbeafd tb/ref-filter-empty-modifier later to maint).
+
* Other minor doc, test and build updates and code cleanups.
(merge f094b89a4d ma/parse-maybe-bool later to maint).
(merge 39b00fa4d4 jk/drop-sha1-entry-pos later to maint).
@@ -376,3 +502,17 @@ Fixes since v2.14
(merge f7a32dd97f kd/doc-for-each-ref later to maint).
(merge be94568bc7 ez/doc-duplicated-words-fix later to maint).
(merge 01e4be6c3d ks/test-readme-phrasofix later to maint).
+ (merge 217bb56d4f hn/typofix later to maint).
+ (merge c08fd6388c jk/doc-read-tree-table-asciidoctor-fix later to maint).
+ (merge c3342b362e ks/doc-use-camelcase-for-config-name later to maint).
+ (merge 0bca165fdb jk/validate-headref-fix later to maint).
+ (merge 93dbefb389 mr/doc-negative-pathspec later to maint).
+ (merge 5e633326e4 ad/doc-markup-fix later to maint).
+ (merge 9ca356fa8b rs/cocci-de-paren-call-params later to maint).
+ (merge 7099153e8d rs/tag-null-pointer-arith-fix later to maint).
+ (merge 0e187d758c rs/run-command-use-alloc-array later to maint).
+ (merge e0222159fa jn/strbuf-doc-re-reuse later to maint).
+ (merge 97487ea11a rs/qsort-s later to maint).
+ (merge a9155c50bd sb/branch-avoid-repeated-strbuf-release later to maint).
+ (merge f777623514 ks/branch-tweak-error-message-for-extra-args later to maint).
+ (merge 33f3c683ec ks/verify-filename-non-option-error-message-tweak later to maint).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index dc4e3f58a2..b53c994d0a 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1058,10 +1058,10 @@ clean.requireForce::
color.branch::
A boolean to enable/disable color in the output of
- linkgit:git-branch[1]. May be set to `always`,
- `false` (or `never`) or `auto` (or `true`), in which case colors are used
- only when the output is to a terminal. If unset, then the
- value of `color.ui` is used (`auto` by default).
+ linkgit:git-branch[1]. May be set to `false` (or `never`) to
+ disable color entirely, `auto` (or `true` or `always`) in which
+ case colors are used only when the output is to a terminal. If
+ unset, then the value of `color.ui` is used (`auto` by default).
color.branch.<slot>::
Use customized color for branch coloration. `<slot>` is one of
@@ -1072,12 +1072,11 @@ color.branch.<slot>::
color.diff::
Whether to use ANSI escape sequences to add color to patches.
- If this is set to `always`, linkgit:git-diff[1],
+ If this is set to `true` or `auto`, linkgit:git-diff[1],
linkgit:git-log[1], and linkgit:git-show[1] will use color
- for all patches. If it is set to `true` or `auto`, those
- commands will only use color when output is to the terminal.
- If unset, then the value of `color.ui` is used (`auto` by
- default).
+ when output is to the terminal. The value `always` is a
+ historical synonym for `auto`. If unset, then the value of
+ `color.ui` is used (`auto` by default).
+
This does not affect linkgit:git-format-patch[1] or the
'git-diff-{asterisk}' plumbing commands. Can be overridden on the
@@ -1141,12 +1140,12 @@ color.grep.<slot>::
--
color.interactive::
- When set to `always`, always use colors for interactive prompts
+ When set to `true` or `auto`, use colors for interactive prompts
and displays (such as those used by "git-add --interactive" and
- "git-clean --interactive"). When false (or `never`), never.
- When set to `true` or `auto`, use colors only when the output is
- to the terminal. If unset, then the value of `color.ui` is
- used (`auto` by default).
+ "git-clean --interactive") when the output is to the terminal.
+ When false (or `never`), never show colors. The value `always`
+ is a historical synonym for `auto`. If unset, then the value of
+ `color.ui` is used (`auto` by default).
color.interactive.<slot>::
Use customized color for 'git add --interactive' and 'git clean
@@ -1193,10 +1192,10 @@ color.ui::
configuration to set a default for the `--color` option. Set it
to `false` or `never` if you prefer Git commands not to use
color unless enabled explicitly with some other configuration
- or the `--color` option. Set it to `always` if you want all
- output not intended for machine consumption to use color, to
- `true` or `auto` (this is the default since Git 1.8.4) if you
- want such output to use color when written to the terminal.
+ or the `--color` option. Set it to `true` or `auto` to enable
+ color when output is written to the terminal (this is also the
+ default since Git 1.8.4). The value `always` is a historical
+ synonym for `auto`.
column.ui::
Specify whether supported commands should output in columns.
@@ -3085,10 +3084,14 @@ submodule.<name>.url::
See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
submodule.<name>.update::
- The default update procedure for a submodule. This variable
- is populated by `git submodule init` from the
- linkgit:gitmodules[5] file. See description of 'update'
- command in linkgit:git-submodule[1].
+ The method by which a submodule is updated by 'git submodule update',
+ which is the only affected command, others such as
+ 'git checkout --recurse-submodules' are unaffected. It exists for
+ historical reasons, when 'git submodule' was the only command to
+ interact with submodules; settings like `submodule.active`
+ and `pull.rebase` are more specific. It is populated by
+ `git submodule init` from the linkgit:gitmodules[5] file.
+ See description of 'update' command in linkgit:git-submodule[1].
submodule.<name>.branch::
The remote branch name for a submodule, used by `git submodule
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index f4169fb1ec..b700beaff5 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -61,6 +61,9 @@ OPTIONS
the working tree. Note that older versions of Git used
to ignore removed files; use `--no-all` option if you want
to add modified or new files but ignore removed ones.
++
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
-n::
--dry-run::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index e292737b9c..fe029ac6fc 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -18,6 +18,7 @@ SYNOPSIS
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
'git branch' --unset-upstream [<branchname>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
+'git branch' (-c | -C) [<oldbranch>] <newbranch>
'git branch' (-d | -D) [-r] <branchname>...
'git branch' --edit-description [<branchname>]
@@ -64,6 +65,10 @@ If <oldbranch> had a corresponding reflog, it is renamed to match
renaming. If <newbranch> exists, -M must be used to force the rename
to happen.
+The `-c` and `-C` options have the exact same semantics as `-m` and
+`-M`, except instead of the branch being renamed it along with its
+config and reflog will be copied to a new name.
+
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
specify more than one branch for deletion. If the branch currently
has a reflog then the reflog will also be deleted.
@@ -92,10 +97,10 @@ OPTIONS
all changes made to the branch ref, enabling use of date
based sha1 expressions such as "<branchname>@\{yesterday}".
Note that in non-bare repositories, reflogs are usually
- enabled by default by the `core.logallrefupdates` config option.
+ enabled by default by the `core.logAllRefUpdates` config option.
The negated form `--no-create-reflog` only overrides an earlier
`--create-reflog`, but currently does not negate the setting of
- `core.logallrefupdates`.
+ `core.logAllRefUpdates`.
-f::
--force::
@@ -104,7 +109,7 @@ OPTIONS
In combination with `-d` (or `--delete`), allow deleting the
branch irrespective of its merged status. In combination with
`-m` (or `--move`), allow renaming the branch even if the new
- branch name already exists.
+ branch name already exists, the same applies for `-c` (or `--copy`).
-m::
--move::
@@ -113,6 +118,13 @@ OPTIONS
-M::
Shortcut for `--move --force`.
+-c::
+--copy::
+ Copy a branch and the corresponding reflog.
+
+-C::
+ Shortcut for `--copy --force`.
+
--color[=<when>]::
Color branches to highlight current, local, and
remote-tracking branches.
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index 26f19d3b07..c924c945ba 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -87,19 +87,23 @@ OPTIONS
--match <pattern>::
Only consider tags matching the given `glob(7)` pattern,
- excluding the "refs/tags/" prefix. This can be used to avoid
- leaking private tags from the repository. If given multiple times, a
- list of patterns will be accumulated, and tags matching any of the
- patterns will be considered. Use `--no-match` to clear and reset the
- list of patterns.
+ excluding the "refs/tags/" prefix. If used with `--all`, it also
+ considers local branches and remote-tracking references matching the
+ pattern, excluding respectively "refs/heads/" and "refs/remotes/"
+ prefix; references of other types are never considered. If given
+ multiple times, a list of patterns will be accumulated, and tags
+ matching any of the patterns will be considered. Use `--no-match` to
+ clear and reset the list of patterns.
--exclude <pattern>::
Do not consider tags matching the given `glob(7)` pattern, excluding
- the "refs/tags/" prefix. This can be used to narrow the tag space and
- find only tags matching some meaningful criteria. If given multiple
- times, a list of patterns will be accumulated and tags matching any
- of the patterns will be excluded. When combined with --match a tag will
- be considered when it matches at least one --match pattern and does not
+ the "refs/tags/" prefix. If used with `--all`, it also does not consider
+ local branches and remote-tracking references matching the pattern,
+ excluding respectively "refs/heads/" and "refs/remotes/" prefix;
+ references of other types are never considered. If given multiple times,
+ a list of patterns will be accumulated and tags matching any of the
+ patterns will be excluded. When combined with --match a tag will be
+ considered when it matches at least one --match pattern and does not
match any of the --exclude patterns. Use `--no-exclude` to clear and
reset the list of patterns.
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 9e5169aa64..bebdcdec5a 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -14,7 +14,7 @@ SYNOPSIS
[--commit-filter <command>] [--tag-name-filter <command>]
[--subdirectory-filter <directory>] [--prune-empty]
[--original <namespace>] [-d <directory>] [-f | --force]
- [--] [<rev-list options>...]
+ [--state-branch <branch>] [--] [<rev-list options>...]
DESCRIPTION
-----------
@@ -198,6 +198,12 @@ to other tags will be rewritten to point to the underlying commit.
directory or when there are already refs starting with
'refs/original/', unless forced.
+--state-branch <branch>::
+ This option will cause the mapping from old to new objects to
+ be loaded from named branch upon startup and saved as a new
+ commit to that branch upon exit, enabling incremental of large
+ trees. If '<branch>' does not exist it will be created.
+
<rev-list options>...::
Arguments for 'git rev-list'. All positive refs included by
these options are rewritten. You may also specify options
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 66b4e0a405..1d420e4cde 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -57,6 +57,11 @@ OPTIONS
`xx`; for example `%00` interpolates to `\0` (NUL),
`%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+--color[=<when>]:
+ Respect any colors specified in the `--format` option. The
+ `<when>` field must be one of `always`, `never`, or `auto` (if
+ `<when>` is absent, behave as if `always` was given).
+
--shell::
--perl::
--python::
@@ -213,11 +218,15 @@ and `date` to extract the named component.
The complete message in a commit and tag object is `contents`.
Its first line is `contents:subject`, where subject is the concatenation
of all lines of the commit message up to the first blank line. The next
-line is 'contents:body', where body is all of the lines after the first
+line is `contents:body`, where body is all of the lines after the first
blank line. The optional GPG signature is `contents:signature`. The
first `N` lines of the message is obtained using `contents:lines=N`.
Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1]
-are obtained as 'contents:trailers'.
+are obtained as `trailers` (or by using the historical alias
+`contents:trailers`). Non-trailer lines from the trailer block can be omitted
+with `trailers:only`. Whitespace-continuations can be removed from trailers so
+that each trailer appears on a line by itself with its full content with
+`trailers:unfold`. Both can be used together as `trailers:unfold,only`.
For sorting purposes, fields with numeric values sort in numeric order
(`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`).
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 720c7850e2..18b494731f 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -289,6 +289,9 @@ providing this option will cause it to die.
<pathspec>...::
If given, limit the search to paths matching at least one pattern.
Both leading paths match and glob(7) patterns are supported.
++
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
Examples
--------
@@ -305,6 +308,9 @@ Examples
Looks for a line that has `NODE` or `Unexpected` in
files that have lines that match both.
+`git grep solution -- :^Documentation`::
+ Looks for `solution`, excluding files in `Documentation`.
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 02576d8c0a..72bd809fb8 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -179,6 +179,7 @@ Here are the "carry forward" rules, where "I" denotes the index,
"clean" means that index and work tree coincide, and "exists"/"nothing"
refer to the presence of a path in the specified commit:
+....
I H M Result
-------------------------------------------------------
0 nothing nothing nothing (does not happen)
@@ -217,6 +218,7 @@ refer to the presence of a path in the specified commit:
19 no no yes exists exists keep index
20 yes yes no exists exists use M
21 no yes no exists exists fail
+....
In all "keep index" cases, the index entry stays as in the
original index file. If the entry is not up to date,
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 6805a74aec..3cedfb0fd2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -430,13 +430,15 @@ without an explicit `--interactive`.
--autosquash::
--no-autosquash::
When the commit log message begins with "squash! ..." (or
- "fixup! ..."), and there is a commit whose title begins with
- the same ..., automatically modify the todo list of rebase -i
- so that the commit marked for squashing comes right after the
- commit to be modified, and change the action of the moved
- commit from `pick` to `squash` (or `fixup`). Ignores subsequent
- "fixup! " or "squash! " after the first, in case you referred to an
- earlier fixup/squash with `git commit --fixup/--squash`.
+ "fixup! ..."), and there is already a commit in the todo list that
+ matches the same `...`, automatically modify the todo list of rebase
+ -i so that the commit marked for squashing comes right after the
+ commit to be modified, and change the action of the moved commit
+ from `pick` to `squash` (or `fixup`). A commit matches the `...` if
+ the commit subject matches, or if the `...` refers to the commit's
+ hash. As a fall-back, partial matches of the commit subject work,
+ too. The recommended way to create fixup/squash commits is by using
+ the `--fixup`/`--squash` options of linkgit:git-commit[1].
+
This option is only valid when the `--interactive` option is used.
+
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
index 2e30a3e42d..54cf2560be 100644
--- a/Documentation/git-shell.txt
+++ b/Documentation/git-shell.txt
@@ -79,6 +79,22 @@ EOF
$ chmod +x $HOME/git-shell-commands/no-interactive-login
----------------
+To enable git-cvsserver access (which should generally have the
+`no-interactive-login` example above as a prerequisite, as creating
+the git-shell-commands directory allows interactive logins):
+
+----------------
+$ cat >$HOME/git-shell-commands/cvs <<\EOF
+if ! test $# = 1 && test "$1" = "server"
+then
+ echo >&2 "git-cvsserver only handles \"server\""
+ exit 1
+fi
+exec git cvsserver server
+EOF
+$ chmod +x $HOME/git-shell-commands/cvs
+----------------
+
SEE ALSO
--------
ssh(1),
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index d47f198f15..9f3a78a36c 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -111,6 +111,8 @@ configuration variable documented in linkgit:git-config[1].
without options are equivalent to 'always' and 'never'
respectively.
+<pathspec>...::
+ See the 'pathspec' entry in linkgit:gitglossary[7].
OUTPUT
------
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 543fb425ee..956fc019f9 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -115,6 +115,11 @@ options for details.
variable if it exists, or lexicographic order otherwise. See
linkgit:git-config[1].
+--color[=<when>]:
+ Respect any colors specified in the `--format` option. The
+ `<when>` field must be one of `always`, `never`, or `auto` (if
+ `<when>` is absent, behave as if `always` was given).
+
-i::
--ignore-case::
Sorting and filtering tags are case insensitive.
@@ -174,7 +179,7 @@ This option is only applicable when listing tags without annotation lines.
`core.logAllRefUpdates` in linkgit:git-config[1].
The negated form `--no-create-reflog` only overrides an earlier
`--create-reflog`, but currently does not negate the setting of
- `core.logallrefupdates`.
+ `core.logAllRefUpdates`.
<tagname>::
The name of the tag to create, delete, or describe.
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 6e3a6767e5..7a1d629ca0 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -75,7 +75,7 @@ example the following invocations are equivalent:
Note that omitting the `=` in `git -c foo.bar ...` is allowed and sets
`foo.bar` to the boolean true value (just like `[foo]bar` would in a
config file). Including the equals but with an empty value (like `git -c
-foo.bar= ...`) sets `foo.bar` to the empty string which ` git config
+foo.bar= ...`) sets `foo.bar` to the empty string which `git config
--bool` will convert to `false`.
--exec-path[=<path>]::
@@ -159,6 +159,10 @@ foo.bar= ...`) sets `foo.bar` to the empty string which ` git config
Add "icase" magic to all pathspec. This is equivalent to setting
the `GIT_ICASE_PATHSPECS` environment variable to `1`.
+--no-optional-locks::
+ Do not perform optional operations that require locks. This is
+ equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
+
GIT COMMANDS
------------
@@ -697,6 +701,14 @@ of clones and fetches.
which feed potentially-untrusted URLS to git commands. See
linkgit:git-config[1] for more details.
+`GIT_OPTIONAL_LOCKS`::
+ If set to `0`, Git will complete any requested operation without
+ performing any optional sub-operations that require taking a lock.
+ For example, this will prevent `git status` from refreshing the
+ index as a side effect. This is useful for processes running in
+ the background which do not want to cause lock contention with
+ other operations on the repository. Defaults to `1`.
+
Discussion[[Discussion]]
------------------------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 1bb4f92d4d..5d3f45560e 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -127,11 +127,10 @@ help message found in the commented portion of the commit template.
commit-msg
~~~~~~~~~~
-This hook is invoked by 'git commit', and can be bypassed
-with the `--no-verify` option. It takes a single parameter, the
-name of the file that holds the proposed commit log message.
-Exiting with a non-zero status causes the 'git commit' to
-abort.
+This hook is invoked by 'git commit' and 'git merge', and can be
+bypassed with the `--no-verify` option. It takes a single parameter,
+the name of the file that holds the proposed commit log message.
+Exiting with a non-zero status causes the command to abort.
The hook is allowed to edit the message file in place, and can be used
to normalize the message into some project standard format. It
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index b71b943b12..6b8888d123 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -407,7 +407,7 @@ these forms:
exclude;;
After a path matches any non-exclude pathspec, it will be run
- through all exclude pathspec (magic signature: `!` or its
+ through all exclude pathspecs (magic signature: `!` or its
synonym `^`). If it matches, the path is ignored. When there
is no non-exclude pathspec, the exclusion is applied to the
result set as if invoked without any pathspec.
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 2eb92b9327..a09d597463 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -39,7 +39,8 @@ even look at what the other tree contains at all. It discards everything
the other tree did, declaring 'our' history contains all that happened in it.
theirs;;
- This is the opposite of 'ours'.
+ This is the opposite of 'ours'; note that, unlike 'ours', there is
+ no 'theirs' merge stragegy to confuse this merge option with.
patience;;
With this option, 'merge-recursive' spends a little extra time
diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt
new file mode 100644
index 0000000000..417ba491d0
--- /dev/null
+++ b/Documentation/technical/hash-function-transition.txt
@@ -0,0 +1,797 @@
+Git hash function transition
+============================
+
+Objective
+---------
+Migrate Git from SHA-1 to a stronger hash function.
+
+Background
+----------
+At its core, the Git version control system is a content addressable
+filesystem. It uses the SHA-1 hash function to name content. For
+example, files, directories, and revisions are referred to by hash
+values unlike in other traditional version control systems where files
+or versions are referred to via sequential numbers. The use of a hash
+function to address its content delivers a few advantages:
+
+* Integrity checking is easy. Bit flips, for example, are easily
+ detected, as the hash of corrupted content does not match its name.
+* Lookup of objects is fast.
+
+Using a cryptographically secure hash function brings additional
+advantages:
+
+* Object names can be signed and third parties can trust the hash to
+ address the signed object and all objects it references.
+* Communication using Git protocol and out of band communication
+ methods have a short reliable string that can be used to reliably
+ address stored content.
+
+Over time some flaws in SHA-1 have been discovered by security
+researchers. https://shattered.io demonstrated a practical SHA-1 hash
+collision. As a result, SHA-1 cannot be considered cryptographically
+secure any more. This impacts the communication of hash values because
+we cannot trust that a given hash value represents the known good
+version of content that the speaker intended.
+
+SHA-1 still possesses the other properties such as fast object lookup
+and safe error checking, but other hash functions are equally suitable
+that are believed to be cryptographically secure.
+
+Goals
+-----
+Where NewHash is a strong 256-bit hash function to replace SHA-1 (see
+"Selection of a New Hash", below):
+
+1. The transition to NewHash can be done one local repository at a time.
+ a. Requiring no action by any other party.
+ b. A NewHash repository can communicate with SHA-1 Git servers
+ (push/fetch).
+ c. Users can use SHA-1 and NewHash identifiers for objects
+ interchangeably (see "Object names on the command line", below).
+ d. New signed objects make use of a stronger hash function than
+ SHA-1 for their security guarantees.
+2. Allow a complete transition away from SHA-1.
+ a. Local metadata for SHA-1 compatibility can be removed from a
+ repository if compatibility with SHA-1 is no longer needed.
+3. Maintainability throughout the process.
+ a. The object format is kept simple and consistent.
+ b. Creation of a generalized repository conversion tool.
+
+Non-Goals
+---------
+1. Add NewHash support to Git protocol. This is valuable and the
+ logical next step but it is out of scope for this initial design.
+2. Transparently improving the security of existing SHA-1 signed
+ objects.
+3. Intermixing objects using multiple hash functions in a single
+ repository.
+4. Taking the opportunity to fix other bugs in Git's formats and
+ protocols.
+5. Shallow clones and fetches into a NewHash repository. (This will
+ change when we add NewHash support to Git protocol.)
+6. Skip fetching some submodules of a project into a NewHash
+ repository. (This also depends on NewHash support in Git
+ protocol.)
+
+Overview
+--------
+We introduce a new repository format extension. Repositories with this
+extension enabled use NewHash instead of SHA-1 to name their objects.
+This affects both object names and object content --- both the names
+of objects and all references to other objects within an object are
+switched to the new hash function.
+
+NewHash repositories cannot be read by older versions of Git.
+
+Alongside the packfile, a NewHash repository stores a bidirectional
+mapping between NewHash and SHA-1 object names. The mapping is generated
+locally and can be verified using "git fsck". Object lookups use this
+mapping to allow naming objects using either their SHA-1 and NewHash names
+interchangeably.
+
+"git cat-file" and "git hash-object" gain options to display an object
+in its sha1 form and write an object given its sha1 form. This
+requires all objects referenced by that object to be present in the
+object database so that they can be named using the appropriate name
+(using the bidirectional hash mapping).
+
+Fetches from a SHA-1 based server convert the fetched objects into
+NewHash form and record the mapping in the bidirectional mapping table
+(see below for details). Pushes to a SHA-1 based server convert the
+objects being pushed into sha1 form so the server does not have to be
+aware of the hash function the client is using.
+
+Detailed Design
+---------------
+Repository format extension
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A NewHash repository uses repository format version `1` (see
+Documentation/technical/repository-version.txt) with extensions
+`objectFormat` and `compatObjectFormat`:
+
+ [core]
+ repositoryFormatVersion = 1
+ [extensions]
+ objectFormat = newhash
+ compatObjectFormat = sha1
+
+Specifying a repository format extension ensures that versions of Git
+not aware of NewHash do not try to operate on these repositories,
+instead producing an error message:
+
+ $ git status
+ fatal: unknown repository extensions found:
+ objectformat
+ compatobjectformat
+
+See the "Transition plan" section below for more details on these
+repository extensions.
+
+Object names
+~~~~~~~~~~~~
+Objects can be named by their 40 hexadecimal digit sha1-name or 64
+hexadecimal digit newhash-name, plus names derived from those (see
+gitrevisions(7)).
+
+The sha1-name of an object is the SHA-1 of the concatenation of its
+type, length, a nul byte, and the object's sha1-content. This is the
+traditional <sha1> used in Git to name objects.
+
+The newhash-name of an object is the NewHash of the concatenation of its
+type, length, a nul byte, and the object's newhash-content.
+
+Object format
+~~~~~~~~~~~~~
+The content as a byte sequence of a tag, commit, or tree object named
+by sha1 and newhash differ because an object named by newhash-name refers to
+other objects by their newhash-names and an object named by sha1-name
+refers to other objects by their sha1-names.
+
+The newhash-content of an object is the same as its sha1-content, except
+that objects referenced by the object are named using their newhash-names
+instead of sha1-names. Because a blob object does not refer to any
+other object, its sha1-content and newhash-content are the same.
+
+The format allows round-trip conversion between newhash-content and
+sha1-content.
+
+Object storage
+~~~~~~~~~~~~~~
+Loose objects use zlib compression and packed objects use the packed
+format described in Documentation/technical/pack-format.txt, just like
+today. The content that is compressed and stored uses newhash-content
+instead of sha1-content.
+
+Pack index
+~~~~~~~~~~
+Pack index (.idx) files use a new v3 format that supports multiple
+hash functions. They have the following format (all integers are in
+network byte order):
+
+- A header appears at the beginning and consists of the following:
+ - The 4-byte pack index signature: '\377t0c'
+ - 4-byte version number: 3
+ - 4-byte length of the header section, including the signature and
+ version number
+ - 4-byte number of objects contained in the pack
+ - 4-byte number of object formats in this pack index: 2
+ - For each object format:
+ - 4-byte format identifier (e.g., 'sha1' for SHA-1)
+ - 4-byte length in bytes of shortened object names. This is the
+ shortest possible length needed to make names in the shortened
+ object name table unambiguous.
+ - 4-byte integer, recording where tables relating to this format
+ are stored in this index file, as an offset from the beginning.
+ - 4-byte offset to the trailer from the beginning of this file.
+ - Zero or more additional key/value pairs (4-byte key, 4-byte
+ value). Only one key is supported: 'PSRC'. See the "Loose objects
+ and unreachable objects" section for supported values and how this
+ is used. All other keys are reserved. Readers must ignore
+ unrecognized keys.
+- Zero or more NUL bytes. This can optionally be used to improve the
+ alignment of the full object name table below.
+- Tables for the first object format:
+ - A sorted table of shortened object names. These are prefixes of
+ the names of all objects in this pack file, packed together
+ without offset values to reduce the cache footprint of the binary
+ search for a specific object name.
+
+ - A table of full object names in pack order. This allows resolving
+ a reference to "the nth object in the pack file" (from a
+ reachability bitmap or from the next table of another object
+ format) to its object name.
+
+ - A table of 4-byte values mapping object name order to pack order.
+ For an object in the table of sorted shortened object names, the
+ value at the corresponding index in this table is the index in the
+ previous table for that same object.
+
+ This can be used to look up the object in reachability bitmaps or
+ to look up its name in another object format.
+
+ - A table of 4-byte CRC32 values of the packed object data, in the
+ order that the objects appear in the pack file. This is to allow
+ compressed data to be copied directly from pack to pack during
+ repacking without undetected data corruption.
+
+ - A table of 4-byte offset values. For an object in the table of
+ sorted shortened object names, the value at the corresponding
+ index in this table indicates where that object can be found in
+ the pack file. These are usually 31-bit pack file offsets, but
+ large offsets are encoded as an index into the next table with the
+ most significant bit set.
+
+ - A table of 8-byte offset entries (empty for pack files less than
+ 2 GiB). Pack files are organized with heavily used objects toward
+ the front, so most object references should not need to refer to
+ this table.
+- Zero or more NUL bytes.
+- Tables for the second object format, with the same layout as above,
+ up to and not including the table of CRC32 values.
+- Zero or more NUL bytes.
+- The trailer consists of the following:
+ - A copy of the 20-byte NewHash checksum at the end of the
+ corresponding packfile.
+
+ - 20-byte NewHash checksum of all of the above.
+
+Loose object index
+~~~~~~~~~~~~~~~~~~
+A new file $GIT_OBJECT_DIR/loose-object-idx contains information about
+all loose objects. Its format is
+
+ # loose-object-idx
+ (newhash-name SP sha1-name LF)*
+
+where the object names are in hexadecimal format. The file is not
+sorted.
+
+The loose object index is protected against concurrent writes by a
+lock file $GIT_OBJECT_DIR/loose-object-idx.lock. To add a new loose
+object:
+
+1. Write the loose object to a temporary file, like today.
+2. Open loose-object-idx.lock with O_CREAT | O_EXCL to acquire the lock.
+3. Rename the loose object into place.
+4. Open loose-object-idx with O_APPEND and write the new object
+5. Unlink loose-object-idx.lock to release the lock.
+
+To remove entries (e.g. in "git pack-refs" or "git-prune"):
+
+1. Open loose-object-idx.lock with O_CREAT | O_EXCL to acquire the
+ lock.
+2. Write the new content to loose-object-idx.lock.
+3. Unlink any loose objects being removed.
+4. Rename to replace loose-object-idx, releasing the lock.
+
+Translation table
+~~~~~~~~~~~~~~~~~
+The index files support a bidirectional mapping between sha1-names
+and newhash-names. The lookup proceeds similarly to ordinary object
+lookups. For example, to convert a sha1-name to a newhash-name:
+
+ 1. Look for the object in idx files. If a match is present in the
+ idx's sorted list of truncated sha1-names, then:
+ a. Read the corresponding entry in the sha1-name order to pack
+ name order mapping.
+ b. Read the corresponding entry in the full sha1-name table to
+ verify we found the right object. If it is, then
+ c. Read the corresponding entry in the full newhash-name table.
+ That is the object's newhash-name.
+ 2. Check for a loose object. Read lines from loose-object-idx until
+ we find a match.
+
+Step (1) takes the same amount of time as an ordinary object lookup:
+O(number of packs * log(objects per pack)). Step (2) takes O(number of
+loose objects) time. To maintain good performance it will be necessary
+to keep the number of loose objects low. See the "Loose objects and
+unreachable objects" section below for more details.
+
+Since all operations that make new objects (e.g., "git commit") add
+the new objects to the corresponding index, this mapping is possible
+for all objects in the object store.
+
+Reading an object's sha1-content
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The sha1-content of an object can be read by converting all newhash-names
+its newhash-content references to sha1-names using the translation table.
+
+Fetch
+~~~~~
+Fetching from a SHA-1 based server requires translating between SHA-1
+and NewHash based representations on the fly.
+
+SHA-1s named in the ref advertisement that are present on the client
+can be translated to NewHash and looked up as local objects using the
+translation table.
+
+Negotiation proceeds as today. Any "have"s generated locally are
+converted to SHA-1 before being sent to the server, and SHA-1s
+mentioned by the server are converted to NewHash when looking them up
+locally.
+
+After negotiation, the server sends a packfile containing the
+requested objects. We convert the packfile to NewHash format using
+the following steps:
+
+1. index-pack: inflate each object in the packfile and compute its
+ SHA-1. Objects can contain deltas in OBJ_REF_DELTA format against
+ objects the client has locally. These objects can be looked up
+ using the translation table and their sha1-content read as
+ described above to resolve the deltas.
+2. topological sort: starting at the "want"s from the negotiation
+ phase, walk through objects in the pack and emit a list of them,
+ excluding blobs, in reverse topologically sorted order, with each
+ object coming later in the list than all objects it references.
+ (This list only contains objects reachable from the "wants". If the
+ pack from the server contained additional extraneous objects, then
+ they will be discarded.)
+3. convert to newhash: open a new (newhash) packfile. Read the topologically
+ sorted list just generated. For each object, inflate its
+ sha1-content, convert to newhash-content, and write it to the newhash
+ pack. Record the new sha1<->newhash mapping entry for use in the idx.
+4. sort: reorder entries in the new pack to match the order of objects
+ in the pack the server generated and include blobs. Write a newhash idx
+ file
+5. clean up: remove the SHA-1 based pack file, index, and
+ topologically sorted list obtained from the server in steps 1
+ and 2.
+
+Step 3 requires every object referenced by the new object to be in the
+translation table. This is why the topological sort step is necessary.
+
+As an optimization, step 1 could write a file describing what non-blob
+objects each object it has inflated from the packfile references. This
+makes the topological sort in step 2 possible without inflating the
+objects in the packfile for a second time. The objects need to be
+inflated again in step 3, for a total of two inflations.
+
+Step 4 is probably necessary for good read-time performance. "git
+pack-objects" on the server optimizes the pack file for good data
+locality (see Documentation/technical/pack-heuristics.txt).
+
+Details of this process are likely to change. It will take some
+experimenting to get this to perform well.
+
+Push
+~~~~
+Push is simpler than fetch because the objects referenced by the
+pushed objects are already in the translation table. The sha1-content
+of each object being pushed can be read as described in the "Reading
+an object's sha1-content" section to generate the pack written by git
+send-pack.
+
+Signed Commits
+~~~~~~~~~~~~~~
+We add a new field "gpgsig-newhash" to the commit object format to allow
+signing commits without relying on SHA-1. It is similar to the
+existing "gpgsig" field. Its signed payload is the newhash-content of the
+commit object with any "gpgsig" and "gpgsig-newhash" fields removed.
+
+This means commits can be signed
+1. using SHA-1 only, as in existing signed commit objects
+2. using both SHA-1 and NewHash, by using both gpgsig-newhash and gpgsig
+ fields.
+3. using only NewHash, by only using the gpgsig-newhash field.
+
+Old versions of "git verify-commit" can verify the gpgsig signature in
+cases (1) and (2) without modifications and view case (3) as an
+ordinary unsigned commit.
+
+Signed Tags
+~~~~~~~~~~~
+We add a new field "gpgsig-newhash" to the tag object format to allow
+signing tags without relying on SHA-1. Its signed payload is the
+newhash-content of the tag with its gpgsig-newhash field and "-----BEGIN PGP
+SIGNATURE-----" delimited in-body signature removed.
+
+This means tags can be signed
+1. using SHA-1 only, as in existing signed tag objects
+2. using both SHA-1 and NewHash, by using gpgsig-newhash and an in-body
+ signature.
+3. using only NewHash, by only using the gpgsig-newhash field.
+
+Mergetag embedding
+~~~~~~~~~~~~~~~~~~
+The mergetag field in the sha1-content of a commit contains the
+sha1-content of a tag that was merged by that commit.
+
+The mergetag field in the newhash-content of the same commit contains the
+newhash-content of the same tag.
+
+Submodules
+~~~~~~~~~~
+To convert recorded submodule pointers, you need to have the converted
+submodule repository in place. The translation table of the submodule
+can be used to look up the new hash.
+
+Loose objects and unreachable objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Fast lookups in the loose-object-idx require that the number of loose
+objects not grow too high.
+
+"git gc --auto" currently waits for there to be 6700 loose objects
+present before consolidating them into a packfile. We will need to
+measure to find a more appropriate threshold for it to use.
+
+"git gc --auto" currently waits for there to be 50 packs present
+before combining packfiles. Packing loose objects more aggressively
+may cause the number of pack files to grow too quickly. This can be
+mitigated by using a strategy similar to Martin Fick's exponential
+rolling garbage collection script:
+https://gerrit-review.googlesource.com/c/gerrit/+/35215
+
+"git gc" currently expels any unreachable objects it encounters in
+pack files to loose objects in an attempt to prevent a race when
+pruning them (in case another process is simultaneously writing a new
+object that refers to the about-to-be-deleted object). This leads to
+an explosion in the number of loose objects present and disk space
+usage due to the objects in delta form being replaced with independent
+loose objects. Worse, the race is still present for loose objects.
+
+Instead, "git gc" will need to move unreachable objects to a new
+packfile marked as UNREACHABLE_GARBAGE (using the PSRC field; see
+below). To avoid the race when writing new objects referring to an
+about-to-be-deleted object, code paths that write new objects will
+need to copy any objects from UNREACHABLE_GARBAGE packs that they
+refer to to new, non-UNREACHABLE_GARBAGE packs (or loose objects).
+UNREACHABLE_GARBAGE are then safe to delete if their creation time (as
+indicated by the file's mtime) is long enough ago.
+
+To avoid a proliferation of UNREACHABLE_GARBAGE packs, they can be
+combined under certain circumstances. If "gc.garbageTtl" is set to
+greater than one day, then packs created within a single calendar day,
+UTC, can be coalesced together. The resulting packfile would have an
+mtime before midnight on that day, so this makes the effective maximum
+ttl the garbageTtl + 1 day. If "gc.garbageTtl" is less than one day,
+then we divide the calendar day into intervals one-third of that ttl
+in duration. Packs created within the same interval can be coalesced
+together. The resulting packfile would have an mtime before the end of
+the interval, so this makes the effective maximum ttl equal to the
+garbageTtl * 4/3.
+
+This rule comes from Thirumala Reddy Mutchukota's JGit change
+https://git.eclipse.org/r/90465.
+
+The UNREACHABLE_GARBAGE setting goes in the PSRC field of the pack
+index. More generally, that field indicates where a pack came from:
+
+ - 1 (PACK_SOURCE_RECEIVE) for a pack received over the network
+ - 2 (PACK_SOURCE_AUTO) for a pack created by a lightweight
+ "gc --auto" operation
+ - 3 (PACK_SOURCE_GC) for a pack created by a full gc
+ - 4 (PACK_SOURCE_UNREACHABLE_GARBAGE) for potential garbage
+ discovered by gc
+ - 5 (PACK_SOURCE_INSERT) for locally created objects that were
+ written directly to a pack file, e.g. from "git add ."
+
+This information can be useful for debugging and for "gc --auto" to
+make appropriate choices about which packs to coalesce.
+
+Caveats
+-------
+Invalid objects
+~~~~~~~~~~~~~~~
+The conversion from sha1-content to newhash-content retains any
+brokenness in the original object (e.g., tree entry modes encoded with
+leading 0, tree objects whose paths are not sorted correctly, and
+commit objects without an author or committer). This is a deliberate
+feature of the design to allow the conversion to round-trip.
+
+More profoundly broken objects (e.g., a commit with a truncated "tree"
+header line) cannot be converted but were not usable by current Git
+anyway.
+
+Shallow clone and submodules
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Because it requires all referenced objects to be available in the
+locally generated translation table, this design does not support
+shallow clone or unfetched submodules. Protocol improvements might
+allow lifting this restriction.
+
+Alternates
+~~~~~~~~~~
+For the same reason, a newhash repository cannot borrow objects from a
+sha1 repository using objects/info/alternates or
+$GIT_ALTERNATE_OBJECT_REPOSITORIES.
+
+git notes
+~~~~~~~~~
+The "git notes" tool annotates objects using their sha1-name as key.
+This design does not describe a way to migrate notes trees to use
+newhash-names. That migration is expected to happen separately (for
+example using a file at the root of the notes tree to describe which
+hash it uses).
+
+Server-side cost
+~~~~~~~~~~~~~~~~
+Until Git protocol gains NewHash support, using NewHash based storage
+on public-facing Git servers is strongly discouraged. Once Git
+protocol gains NewHash support, NewHash based servers are likely not
+to support SHA-1 compatibility, to avoid what may be a very expensive
+hash reencode during clone and to encourage peers to modernize.
+
+The design described here allows fetches by SHA-1 clients of a
+personal NewHash repository because it's not much more difficult than
+allowing pushes from that repository. This support needs to be guarded
+by a configuration option --- servers like git.kernel.org that serve a
+large number of clients would not be expected to bear that cost.
+
+Meaning of signatures
+~~~~~~~~~~~~~~~~~~~~~
+The signed payload for signed commits and tags does not explicitly
+name the hash used to identify objects. If some day Git adopts a new
+hash function with the same length as the current SHA-1 (40
+hexadecimal digit) or NewHash (64 hexadecimal digit) objects then the
+intent behind the PGP signed payload in an object signature is
+unclear:
+
+ object e7e07d5a4fcc2a203d9873968ad3e6bd4d7419d7
+ type commit
+ tag v2.12.0
+ tagger Junio C Hamano <gitster@pobox.com> 1487962205 -0800
+
+ Git 2.12
+
+Does this mean Git v2.12.0 is the commit with sha1-name
+e7e07d5a4fcc2a203d9873968ad3e6bd4d7419d7 or the commit with
+new-40-digit-hash-name e7e07d5a4fcc2a203d9873968ad3e6bd4d7419d7?
+
+Fortunately NewHash and SHA-1 have different lengths. If Git starts
+using another hash with the same length to name objects, then it will
+need to change the format of signed payloads using that hash to
+address this issue.
+
+Object names on the command line
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To support the transition (see Transition plan below), this design
+supports four different modes of operation:
+
+ 1. ("dark launch") Treat object names input by the user as SHA-1 and
+ convert any object names written to output to SHA-1, but store
+ objects using NewHash. This allows users to test the code with no
+ visible behavior change except for performance. This allows
+ allows running even tests that assume the SHA-1 hash function, to
+ sanity-check the behavior of the new mode.
+
+ 2. ("early transition") Allow both SHA-1 and NewHash object names in
+ input. Any object names written to output use SHA-1. This allows
+ users to continue to make use of SHA-1 to communicate with peers
+ (e.g. by email) that have not migrated yet and prepares for mode 3.
+
+ 3. ("late transition") Allow both SHA-1 and NewHash object names in
+ input. Any object names written to output use NewHash. In this
+ mode, users are using a more secure object naming method by
+ default. The disruption is minimal as long as most of their peers
+ are in mode 2 or mode 3.
+
+ 4. ("post-transition") Treat object names input by the user as
+ NewHash and write output using NewHash. This is safer than mode 3
+ because there is less risk that input is incorrectly interpreted
+ using the wrong hash function.
+
+The mode is specified in configuration.
+
+The user can also explicitly specify which format to use for a
+particular revision specifier and for output, overriding the mode. For
+example:
+
+git --output-format=sha1 log abac87a^{sha1}..f787cac^{newhash}
+
+Selection of a New Hash
+-----------------------
+In early 2005, around the time that Git was written, Xiaoyun Wang,
+Yiqun Lisa Yin, and Hongbo Yu announced an attack finding SHA-1
+collisions in 2^69 operations. In August they published details.
+Luckily, no practical demonstrations of a collision in full SHA-1 were
+published until 10 years later, in 2017.
+
+The hash function NewHash to replace SHA-1 should be stronger than
+SHA-1 was: we would like it to be trustworthy and useful in practice
+for at least 10 years.
+
+Some other relevant properties:
+
+1. A 256-bit hash (long enough to match common security practice; not
+ excessively long to hurt performance and disk usage).
+
+2. High quality implementations should be widely available (e.g. in
+ OpenSSL).
+
+3. The hash function's properties should match Git's needs (e.g. Git
+ requires collision and 2nd preimage resistance and does not require
+ length extension resistance).
+
+4. As a tiebreaker, the hash should be fast to compute (fortunately
+ many contenders are faster than SHA-1).
+
+Some hashes under consideration are SHA-256, SHA-512/256, SHA-256x16,
+K12, and BLAKE2bp-256.
+
+Transition plan
+---------------
+Some initial steps can be implemented independently of one another:
+- adding a hash function API (vtable)
+- teaching fsck to tolerate the gpgsig-newhash field
+- excluding gpgsig-* from the fields copied by "git commit --amend"
+- annotating tests that depend on SHA-1 values with a SHA1 test
+ prerequisite
+- using "struct object_id", GIT_MAX_RAWSZ, and GIT_MAX_HEXSZ
+ consistently instead of "unsigned char *" and the hardcoded
+ constants 20 and 40.
+- introducing index v3
+- adding support for the PSRC field and safer object pruning
+
+
+The first user-visible change is the introduction of the objectFormat
+extension (without compatObjectFormat). This requires:
+- implementing the loose-object-idx
+- teaching fsck about this mode of operation
+- using the hash function API (vtable) when computing object names
+- signing objects and verifying signatures
+- rejecting attempts to fetch from or push to an incompatible
+ repository
+
+Next comes introduction of compatObjectFormat:
+- translating object names between object formats
+- translating object content between object formats
+- generating and verifying signatures in the compat format
+- adding appropriate index entries when adding a new object to the
+ object store
+- --output-format option
+- ^{sha1} and ^{newhash} revision notation
+- configuration to specify default input and output format (see
+ "Object names on the command line" above)
+
+The next step is supporting fetches and pushes to SHA-1 repositories:
+- allow pushes to a repository using the compat format
+- generate a topologically sorted list of the SHA-1 names of fetched
+ objects
+- convert the fetched packfile to newhash format and generate an idx
+ file
+- re-sort to match the order of objects in the fetched packfile
+
+The infrastructure supporting fetch also allows converting an existing
+repository. In converted repositories and new clones, end users can
+gain support for the new hash function without any visible change in
+behavior (see "dark launch" in the "Object names on the command line"
+section). In particular this allows users to verify NewHash signatures
+on objects in the repository, and it should ensure the transition code
+is stable in production in preparation for using it more widely.
+
+Over time projects would encourage their users to adopt the "early
+transition" and then "late transition" modes to take advantage of the
+new, more futureproof NewHash object names.
+
+When objectFormat and compatObjectFormat are both set, commands
+generating signatures would generate both SHA-1 and NewHash signatures
+by default to support both new and old users.
+
+In projects using NewHash heavily, users could be encouraged to adopt
+the "post-transition" mode to avoid accidentally making implicit use
+of SHA-1 object names.
+
+Once a critical mass of users have upgraded to a version of Git that
+can verify NewHash signatures and have converted their existing
+repositories to support verifying them, we can add support for a
+setting to generate only NewHash signatures. This is expected to be at
+least a year later.
+
+That is also a good moment to advertise the ability to convert
+repositories to use NewHash only, stripping out all SHA-1 related
+metadata. This improves performance by eliminating translation
+overhead and security by avoiding the possibility of accidentally
+relying on the safety of SHA-1.
+
+Updating Git's protocols to allow a server to specify which hash
+functions it supports is also an important part of this transition. It
+is not discussed in detail in this document but this transition plan
+assumes it happens. :)
+
+Alternatives considered
+-----------------------
+Upgrading everyone working on a particular project on a flag day
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Projects like the Linux kernel are large and complex enough that
+flipping the switch for all projects based on the repository at once
+is infeasible.
+
+Not only would all developers and server operators supporting
+developers have to switch on the same flag day, but supporting tooling
+(continuous integration, code review, bug trackers, etc) would have to
+be adapted as well. This also makes it difficult to get early feedback
+from some project participants testing before it is time for mass
+adoption.
+
+Using hash functions in parallel
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+(e.g. https://public-inbox.org/git/22708.8913.864049.452252@chiark.greenend.org.uk/ )
+Objects newly created would be addressed by the new hash, but inside
+such an object (e.g. commit) it is still possible to address objects
+using the old hash function.
+* You cannot trust its history (needed for bisectability) in the
+ future without further work
+* Maintenance burden as the number of supported hash functions grows
+ (they will never go away, so they accumulate). In this proposal, by
+ comparison, converted objects lose all references to SHA-1.
+
+Signed objects with multiple hashes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Instead of introducing the gpgsig-newhash field in commit and tag objects
+for newhash-content based signatures, an earlier version of this design
+added "hash newhash <newhash-name>" fields to strengthen the existing
+sha1-content based signatures.
+
+In other words, a single signature was used to attest to the object
+content using both hash functions. This had some advantages:
+* Using one signature instead of two speeds up the signing process.
+* Having one signed payload with both hashes allows the signer to
+ attest to the sha1-name and newhash-name referring to the same object.
+* All users consume the same signature. Broken signatures are likely
+ to be detected quickly using current versions of git.
+
+However, it also came with disadvantages:
+* Verifying a signed object requires access to the sha1-names of all
+ objects it references, even after the transition is complete and
+ translation table is no longer needed for anything else. To support
+ this, the design added fields such as "hash sha1 tree <sha1-name>"
+ and "hash sha1 parent <sha1-name>" to the newhash-content of a signed
+ commit, complicating the conversion process.
+* Allowing signed objects without a sha1 (for after the transition is
+ complete) complicated the design further, requiring a "nohash sha1"
+ field to suppress including "hash sha1" fields in the newhash-content
+ and signed payload.
+
+Lazily populated translation table
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Some of the work of building the translation table could be deferred to
+push time, but that would significantly complicate and slow down pushes.
+Calculating the sha1-name at object creation time at the same time it is
+being streamed to disk and having its newhash-name calculated should be
+an acceptable cost.
+
+Document History
+----------------
+
+2017-03-03
+bmwill@google.com, jonathantanmy@google.com, jrnieder@gmail.com,
+sbeller@google.com
+
+Initial version sent to
+http://public-inbox.org/git/20170304011251.GA26789@aiede.mtv.corp.google.com
+
+2017-03-03 jrnieder@gmail.com
+Incorporated suggestions from jonathantanmy and sbeller:
+* describe purpose of signed objects with each hash type
+* redefine signed object verification using object content under the
+ first hash function
+
+2017-03-06 jrnieder@gmail.com
+* Use SHA3-256 instead of SHA2 (thanks, Linus and brian m. carlson).[1][2]
+* Make sha3-based signatures a separate field, avoiding the need for
+ "hash" and "nohash" fields (thanks to peff[3]).
+* Add a sorting phase to fetch (thanks to Junio for noticing the need
+ for this).
+* Omit blobs from the topological sort during fetch (thanks to peff).
+* Discuss alternates, git notes, and git servers in the caveats
+ section (thanks to Junio Hamano, brian m. carlson[4], and Shawn
+ Pearce).
+* Clarify language throughout (thanks to various commenters,
+ especially Junio).
+
+2017-09-27 jrnieder@gmail.com, sbeller@google.com
+* use placeholder NewHash instead of SHA3-256
+* describe criteria for picking a hash function.
+* include a transition plan (thanks especially to Brandon Williams
+ for fleshing these ideas out)
+* define the translation table (thanks, Shawn Pearce[5], Jonathan
+ Tan, and Masaya Suzuki)
+* avoid loose object overhead by packing more aggressively in
+ "git gc --auto"
+
+[1] http://public-inbox.org/git/CA+55aFzJtejiCjV0e43+9oR3QuJK2PiFiLQemytoLpyJWe6P9w@mail.gmail.com/
+[2] http://public-inbox.org/git/CA+55aFz+gkAsDZ24zmePQuEs1XPS9BP_s8O7Q4wQ7LV7X5-oDA@mail.gmail.com/
+[3] http://public-inbox.org/git/20170306084353.nrns455dvkdsfgo5@sigill.intra.peff.net/
+[4] http://public-inbox.org/git/20170304224936.rqqtkdvfjgyezsht@genre.crustytoothpaste.net
+[5] https://public-inbox.org/git/CAJo=hJtoX9=AyLHHpUJS7fueV9ciZ_MNpnEPHUz8Whui6g9F0A@mail.gmail.com/
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 75beb2e775..ab04c977be 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.14.GIT
+DEF_VER=v2.15.0-rc0
LF='
'
diff --git a/Makefile b/Makefile
index ed4ca438bd..cd75985991 100644
--- a/Makefile
+++ b/Makefile
@@ -205,6 +205,9 @@ all::
#
# Define NO_MMAP if you want to avoid mmap.
#
+# Define MMAP_PREVENTS_DELETE if a file that is currently mmapped cannot be
+# deleted or cannot be replaced using rename().
+#
# Define NO_SYS_POLL_H if you don't have sys/poll.h.
#
# Define NO_POLL if you do not have or don't want to use poll().
@@ -821,6 +824,7 @@ LIB_OBJS += notes-cache.o
LIB_OBJS += notes-merge.o
LIB_OBJS += notes-utils.o
LIB_OBJS += object.o
+LIB_OBJS += oidmap.o
LIB_OBJS += oidset.o
LIB_OBJS += packfile.o
LIB_OBJS += pack-bitmap.o
@@ -1391,6 +1395,9 @@ else
COMPAT_OBJS += compat/win32mmap.o
endif
endif
+ifdef MMAP_PREVENTS_DELETE
+ BASIC_CFLAGS += -DMMAP_PREVENTS_DELETE
+endif
ifdef OBJECT_CREATION_USES_RENAMES
COMPAT_CFLAGS += -DOBJECT_CREATION_MODE=1
endif
diff --git a/abspath.c b/abspath.c
index 708aff8d42..9857985329 100644
--- a/abspath.c
+++ b/abspath.c
@@ -202,6 +202,10 @@ error_out:
return retval;
}
+/*
+ * Resolve `path` into an absolute, cleaned-up path. The return value
+ * comes from a shared buffer.
+ */
const char *real_path(const char *path)
{
static struct strbuf realpath = STRBUF_INIT;
diff --git a/apply.c b/apply.c
index 71cbbd141c..c022af53a2 100644
--- a/apply.c
+++ b/apply.c
@@ -2920,6 +2920,7 @@ static int apply_one_fragment(struct apply_state *state,
if (plen && (ws_rule & WS_BLANK_AT_EOF) &&
ws_blank_line(patch + 1, plen, ws_rule))
is_blank_context = 1;
+ /* fallthrough */
case '-':
memcpy(old, patch + 1, plen);
add_line_info(&preimage, old, plen,
@@ -2927,7 +2928,7 @@ static int apply_one_fragment(struct apply_state *state,
old += plen;
if (first == '-')
break;
- /* Fall-through for ' ' */
+ /* fallthrough */
case '+':
/* --no-add does not add new lines */
if (first == '+' && state->no_add)
diff --git a/bisect.c b/bisect.c
index 2549eaf7b1..96beeb5d13 100644
--- a/bisect.c
+++ b/bisect.c
@@ -826,7 +826,8 @@ static int check_ancestors(const char *prefix)
/* Clean up objects used, as they will be reused. */
clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS);
- free(pending_copy.objects);
+
+ object_array_clear(&pending_copy);
return res;
}
diff --git a/branch.c b/branch.c
index 703ded69ca..4377ce2fb1 100644
--- a/branch.c
+++ b/branch.c
@@ -191,9 +191,8 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
if (!attr_only) {
const char *head;
- struct object_id oid;
- head = resolve_ref_unsafe("HEAD", 0, oid.hash, NULL);
+ head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
if (!is_bare_repository() && head && !strcmp(head, ref->buf))
die(_("Cannot force update the current branch."));
}
diff --git a/builtin/branch.c b/builtin/branch.c
index 355f9ef5da..b67593288c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -28,6 +28,7 @@ static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
+ N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
N_("git branch [<options>] [-r | -a] [--points-at]"),
N_("git branch [<options>] [-r | -a] [--format]"),
NULL
@@ -216,7 +217,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
if (!head_rev)
die(_("Couldn't look up commit object for HEAD"));
}
- for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+ for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
char *target = NULL;
int flags = 0;
@@ -281,8 +282,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
}
free(name);
+ strbuf_release(&bname);
- return(ret);
+ return ret;
}
static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
@@ -352,7 +354,7 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev);
strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
- strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
+ strbuf_addstr(&local, branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&local, " %s ", obname.buf);
if (filter->verbose > 1)
@@ -456,15 +458,19 @@ static void reject_rebase_or_bisect_branch(const char *target)
free_worktrees(worktrees);
}
-static void rename_branch(const char *oldname, const char *newname, int force)
+static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
int recovery = 0;
int clobber_head_ok;
- if (!oldname)
- die(_("cannot rename the current branch while not on any."));
+ if (!oldname) {
+ if (copy)
+ die(_("cannot copy the current branch while not on any."));
+ else
+ die(_("cannot rename the current branch while not on any."));
+ }
if (strbuf_check_branch_ref(&oldref, oldname)) {
/*
@@ -487,16 +493,29 @@ static void rename_branch(const char *oldname, const char *newname, int force)
reject_rebase_or_bisect_branch(oldref.buf);
- strbuf_addf(&logmsg, "Branch: renamed %s to %s",
- oldref.buf, newref.buf);
+ if (copy)
+ strbuf_addf(&logmsg, "Branch: copied %s to %s",
+ oldref.buf, newref.buf);
+ else
+ strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+ oldref.buf, newref.buf);
- if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
+ if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch rename failed"));
+ if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
+ die(_("Branch copy failed"));
- if (recovery)
- warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
+ if (recovery) {
+ if (copy)
+ warning(_("Copied a misnamed branch '%s' away"),
+ oldref.buf + 11);
+ else
+ warning(_("Renamed a misnamed branch '%s' away"),
+ oldref.buf + 11);
+ }
- if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
+ if (!copy &&
+ replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
strbuf_release(&logmsg);
@@ -505,8 +524,10 @@ static void rename_branch(const char *oldname, const char *newname, int force)
strbuf_release(&oldref);
strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
strbuf_release(&newref);
- if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
+ if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
die(_("Branch is renamed, but update of config-file failed"));
+ if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
+ die(_("Branch is copied, but update of config-file failed"));
strbuf_release(&oldsection);
strbuf_release(&newsection);
}
@@ -544,7 +565,7 @@ static int edit_branch_description(const char *branch_name)
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, force = 0, list = 0;
+ int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
int reflog = 0, edit_description = 0;
int quiet = 0, unset_upstream = 0;
const char *new_upstream = NULL;
@@ -581,6 +602,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
+ OPT_BIT('c', "copy", &copy, N_("copy a branch and its reflog"), 1),
+ OPT_BIT('C', NULL, &copy, N_("copy a branch, even if target exists"), 2),
OPT_BOOL(0, "list", &list, N_("list branch names")),
OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
OPT_BOOL(0, "edit-description", &edit_description,
@@ -624,14 +647,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
- if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
+ if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0)
list = 1;
if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
filter.no_commit)
list = 1;
- if (!!delete + !!rename + !!new_upstream +
+ if (!!delete + !!rename + !!copy + !!new_upstream +
list + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
@@ -649,6 +672,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (force) {
delete *= 2;
rename *= 2;
+ copy *= 2;
}
if (delete) {
@@ -703,20 +727,29 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (edit_branch_description(branch_name))
return 1;
+ } else if (copy) {
+ if (!argc)
+ die(_("branch name required"));
+ else if (argc == 1)
+ copy_or_rename_branch(head, argv[0], 1, copy > 1);
+ else if (argc == 2)
+ copy_or_rename_branch(argv[0], argv[1], 1, copy > 1);
+ else
+ die(_("too many branches for a copy operation"));
} else if (rename) {
if (!argc)
die(_("branch name required"));
else if (argc == 1)
- rename_branch(head, argv[0], rename > 1);
+ copy_or_rename_branch(head, argv[0], 0, rename > 1);
else if (argc == 2)
- rename_branch(argv[0], argv[1], rename > 1);
+ copy_or_rename_branch(argv[0], argv[1], 0, rename > 1);
else
- die(_("too many branches for a rename operation"));
+ die(_("too many arguments for a rename operation"));
} else if (new_upstream) {
struct branch *branch = branch_get(argv[0]);
if (argc > 1)
- die(_("too many branches to set new upstream"));
+ die(_("too many arguments to set new upstream"));
if (!branch) {
if (!argc || !strcmp(argv[0], "HEAD"))
@@ -739,7 +772,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
struct strbuf buf = STRBUF_INIT;
if (argc > 1)
- die(_("too many branches to unset upstream"));
+ die(_("too many arguments to unset upstream"));
if (!branch) {
if (!argc || !strcmp(argv[0], "HEAD"))
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 4ccbfaac31..f5fa4fd75a 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -97,7 +97,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
return !has_object_file(&oid);
case 'w':
- if (!path[0])
+ if (!path)
die("git cat-file --filters %s: <object> must be "
"<sha1:path>", obj_name);
@@ -107,12 +107,13 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
break;
case 'c':
- if (!path[0])
+ if (!path)
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);
if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size))
break;
+ /* else fallthrough */
case 'p':
type = sha1_object_info(oid.hash, NULL);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5c202b7af5..fc4f8fd2ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -436,6 +436,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
* update paths in the work tree, and we cannot revert
* them.
*/
+ /* fallthrough */
case 0:
return 0;
default:
@@ -796,9 +797,14 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new)
for_each_ref(add_pending_uninteresting_ref, &revs);
add_pending_oid(&revs, "HEAD", &new->object.oid, UNINTERESTING);
+ /* Save pending objects, so they can be cleaned up later. */
refs = revs.pending;
revs.leak_pending = 1;
+ /*
+ * prepare_revision_walk (together with .leak_pending = 1) makes us
+ * the sole owner of the list of pending objects.
+ */
if (prepare_revision_walk(&revs))
die(_("internal error in revision walk"));
if (!(old->object.flags & UNINTERESTING))
@@ -806,8 +812,10 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new)
else
describe_detached_head(_("Previous HEAD position was"), old);
+ /* Clean up objects used, as they will be reused. */
clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
- free(refs.objects);
+
+ object_array_clear(&refs);
}
static int switch_branches(const struct checkout_opts *opts,
@@ -1116,9 +1124,8 @@ static int checkout_branch(struct checkout_opts *opts,
if (new->path && !opts->force_detach && !opts->new_branch &&
!opts->ignore_other_worktrees) {
- struct object_id oid;
int flag;
- char *head_ref = resolve_refdup("HEAD", 0, oid.hash, &flag);
+ char *head_ref = resolve_refdup("HEAD", 0, NULL, &flag);
if (head_ref &&
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
die_if_checked_out(new->path, 1);
@@ -1290,6 +1297,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}
+ UNLEAK(opts);
if (opts.patch_mode || opts.pathspec.nr)
return checkout_paths(&opts, new.name);
else
diff --git a/builtin/commit.c b/builtin/commit.c
index 58f9747c2f..d75b3805ea 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -335,7 +335,7 @@ static void refresh_cache_or_die(int refresh_flags)
static const char *prepare_index(int argc, const char **argv, const char *prefix,
const struct commit *current_head, int is_status)
{
- struct string_list partial;
+ struct string_list partial = STRING_LIST_INIT_DUP;
struct pathspec pathspec;
int refresh_flags = REFRESH_QUIET;
const char *ret;
@@ -380,7 +380,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
warning(_("Failed to update main cache tree"));
commit_style = COMMIT_NORMAL;
- return get_lock_file_path(&index_lock);
+ ret = get_lock_file_path(&index_lock);
+ goto out;
}
/*
@@ -403,7 +404,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
die(_("unable to write new_index file"));
commit_style = COMMIT_NORMAL;
- return get_lock_file_path(&index_lock);
+ ret = get_lock_file_path(&index_lock);
+ goto out;
}
/*
@@ -429,7 +431,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
rollback_lock_file(&index_lock);
}
commit_style = COMMIT_AS_IS;
- return get_index_file();
+ ret = get_index_file();
+ goto out;
}
/*
@@ -460,7 +463,6 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
die(_("cannot do a partial commit during a cherry-pick."));
}
- string_list_init(&partial, 1);
if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
@@ -490,6 +492,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
discard_cache();
ret = get_lock_file_path(&false_lock);
read_cache_from(ret);
+out:
+ string_list_clear(&partial, 0);
+ clear_pathspec(&pathspec);
return ret;
}
@@ -1387,7 +1392,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
read_cache_preload(&s.pathspec);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
- fd = hold_locked_index(&index_lock, 0);
+ if (use_optional_locks())
+ fd = hold_locked_index(&index_lock, 0);
+ else
+ fd = -1;
s.is_initial = get_oid(s.reference, &oid) ? 1 : 0;
if (!s.is_initial)
@@ -1431,7 +1439,6 @@ static void print_summary(const char *prefix, const struct object_id *oid,
struct rev_info rev;
struct commit *commit;
struct strbuf format = STRBUF_INIT;
- struct object_id junk_oid;
const char *head;
struct pretty_print_context pctx = {0};
struct strbuf author_ident = STRBUF_INIT;
@@ -1484,7 +1491,7 @@ static void print_summary(const char *prefix, const struct object_id *oid,
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL);
+ head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
if (!strcmp(head, "HEAD"))
head = _("detached HEAD");
else
diff --git a/builtin/describe.c b/builtin/describe.c
index e77163e909..29075dbd0f 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -129,13 +129,24 @@ static void add_to_known_names(const char *path,
static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data)
{
- int is_tag = starts_with(path, "refs/tags/");
+ int is_tag = 0;
struct object_id peeled;
int is_annotated, prio;
-
- /* Reject anything outside refs/tags/ unless --all */
- if (!all && !is_tag)
+ const char *path_to_match = NULL;
+
+ if (skip_prefix(path, "refs/tags/", &path_to_match)) {
+ is_tag = 1;
+ } else if (all) {
+ if ((exclude_patterns.nr || patterns.nr) &&
+ !skip_prefix(path, "refs/heads/", &path_to_match) &&
+ !skip_prefix(path, "refs/remotes/", &path_to_match)) {
+ /* Only accept reference of known type if there are match/exclude patterns */
+ return 0;
+ }
+ } else {
+ /* Reject anything outside refs/tags/ unless --all */
return 0;
+ }
/*
* If we're given exclude patterns, first exclude any tag which match
@@ -144,11 +155,8 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
if (exclude_patterns.nr) {
struct string_list_item *item;
- if (!is_tag)
- return 0;
-
for_each_string_list_item(item, &exclude_patterns) {
- if (!wildmatch(item->string, path + 10, 0))
+ if (!wildmatch(item->string, path_to_match, 0))
return 0;
}
}
@@ -158,18 +166,18 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
* pattern.
*/
if (patterns.nr) {
+ int found = 0;
struct string_list_item *item;
- if (!is_tag)
- return 0;
-
for_each_string_list_item(item, &patterns) {
- if (!wildmatch(item->string, path + 10, 0))
+ if (!wildmatch(item->string, path_to_match, 0)) {
+ found = 1;
break;
+ }
+ }
- /* If we get here, no pattern matched. */
+ if (!found)
return 0;
- }
}
/* Is it annotated? */
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 9d772f8f27..522f4fdffd 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -56,5 +56,6 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
return -1;
}
result = run_diff_index(&rev, cached);
+ UNLEAK(rev);
return diff_result_code(&rev.diffopt, result);
}
diff --git a/builtin/diff.c b/builtin/diff.c
index 7e3ebcea38..f5bbd4d757 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -464,5 +464,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
+ UNLEAK(rev);
+ UNLEAK(ent);
+ UNLEAK(blob);
return result;
}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index d412c0a8f3..2fb60d6d48 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -344,6 +344,7 @@ static void show_filemodify(struct diff_queue_struct *q,
struct diff_options *options, void *data)
{
int i;
+ struct string_list *changed = data;
/*
* Handle files below a directory first, in case they are all deleted
@@ -359,20 +360,31 @@ static void show_filemodify(struct diff_queue_struct *q,
case DIFF_STATUS_DELETED:
printf("D ");
print_path(spec->path);
+ string_list_insert(changed, spec->path);
putchar('\n');
break;
case DIFF_STATUS_COPIED:
case DIFF_STATUS_RENAMED:
- printf("%c ", q->queue[i]->status);
- print_path(ospec->path);
- putchar(' ');
- print_path(spec->path);
- putchar('\n');
-
- if (!oidcmp(&ospec->oid, &spec->oid) &&
- ospec->mode == spec->mode)
- break;
+ /*
+ * If a change in the file corresponding to ospec->path
+ * has been observed, we cannot trust its contents
+ * because the diff is calculated based on the prior
+ * contents, not the current contents. So, declare a
+ * copy or rename only if there was no change observed.
+ */
+ if (!string_list_has_string(changed, ospec->path)) {
+ printf("%c ", q->queue[i]->status);
+ print_path(ospec->path);
+ putchar(' ');
+ print_path(spec->path);
+ string_list_insert(changed, spec->path);
+ putchar('\n');
+
+ if (!oidcmp(&ospec->oid, &spec->oid) &&
+ ospec->mode == spec->mode)
+ break;
+ }
/* fallthrough */
case DIFF_STATUS_TYPE_CHANGED:
@@ -393,6 +405,7 @@ static void show_filemodify(struct diff_queue_struct *q,
get_object_mark(object));
}
print_path(spec->path);
+ string_list_insert(changed, spec->path);
putchar('\n');
break;
@@ -528,7 +541,8 @@ static void anonymize_ident_line(const char **beg, const char **end)
*end = out->buf + out->len;
}
-static void handle_commit(struct commit *commit, struct rev_info *rev)
+static void handle_commit(struct commit *commit, struct rev_info *rev,
+ struct string_list *paths_of_changed_objects)
{
int saved_output_format = rev->diffopt.output_format;
const char *commit_buffer;
@@ -615,6 +629,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
if (full_tree)
printf("deleteall\n");
log_tree_diff_flush(rev);
+ string_list_clear(paths_of_changed_objects, 0);
rev->diffopt.output_format = saved_output_format;
printf("\n");
@@ -630,15 +645,15 @@ static void *anonymize_tag(const void *old, size_t *len)
return strbuf_detach(&out, len);
}
-static void handle_tail(struct object_array *commits, struct rev_info *revs)
+static void handle_tail(struct object_array *commits, struct rev_info *revs,
+ struct string_list *paths_of_changed_objects)
{
struct commit *commit;
while (commits->nr) {
- commit = (struct commit *)commits->objects[commits->nr - 1].item;
+ commit = (struct commit *)object_array_pop(commits);
if (has_unshown_parent(commit))
return;
- handle_commit(commit, revs);
- commits->nr--;
+ handle_commit(commit, revs, paths_of_changed_objects);
}
}
@@ -977,6 +992,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
char *export_filename = NULL, *import_filename = NULL;
uint32_t lastimportid;
struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
+ struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP;
struct option options[] = {
OPT_INTEGER(0, "progress", &progress,
N_("show progress after <n> objects")),
@@ -1049,14 +1065,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
revs.diffopt.format_callback = show_filemodify;
+ revs.diffopt.format_callback_data = &paths_of_changed_objects;
DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
while ((commit = get_revision(&revs))) {
if (has_unshown_parent(commit)) {
add_object_array(&commit->object, NULL, &commits);
}
else {
- handle_commit(commit, &revs);
- handle_tail(&commits, &revs);
+ handle_commit(commit, &revs, &paths_of_changed_objects);
+ handle_tail(&commits, &revs, &paths_of_changed_objects);
}
}
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 5d7c921a77..e931be9ce4 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -36,6 +36,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
OPT_GROUP(""),
OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
+ OPT__COLOR(&format.use_color, N_("respect format colors")),
OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
N_("field name to sort on"), &parse_opt_ref_sorting),
OPT_CALLBACK(0, "points-at", &filter.points_at,
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 1e4c471b41..56afe405b8 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -182,12 +182,7 @@ static int traverse_reachable(void)
if (show_progress)
progress = start_delayed_progress(_("Checking connectivity"), 0);
while (pending.nr) {
- struct object_array_entry *entry;
- struct object *obj;
-
- entry = pending.objects + --pending.nr;
- obj = entry->item;
- result |= traverse_one_object(obj);
+ result |= traverse_one_object(object_array_pop(&pending));
display_progress(progress, ++nr);
}
stop_progress(&progress);
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
index 6d9a79f9b3..2706fcfaf2 100644
--- a/builtin/get-tar-commit-id.c
+++ b/builtin/get-tar-commit-id.c
@@ -26,8 +26,10 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
usage(builtin_get_tar_commit_id_usage);
n = read_in_full(0, buffer, HEADERSIZE);
- if (n < HEADERSIZE)
- die("git get-tar-commit-id: read error");
+ if (n < 0)
+ die_errno("git get-tar-commit-id: read error");
+ if (n != HEADERSIZE)
+ die_errno("git get-tar-commit-id: EOF before reading tar header");
if (header->typeflag[0] != 'g')
return 1;
if (!skip_prefix(content, "52 comment=", &comment))
diff --git a/builtin/log.c b/builtin/log.c
index f8cccbc964..d81a09051e 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1660,10 +1660,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
check_head = 1;
if (check_head) {
- struct object_id oid;
const char *ref, *v;
ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
- oid.hash, NULL);
+ NULL, NULL);
if (ref && skip_prefix(ref, "refs/heads/", &v))
branch_name = xstrdup(v);
else
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 598da6c8bc..9e088ebd11 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -494,5 +494,6 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
always, allow_undefined, data.name_only);
}
+ UNLEAK(revs);
return 0;
}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index f721137eaf..5ee2c48ffb 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -2563,8 +2563,8 @@ struct in_pack_object {
};
struct in_pack {
- int alloc;
- int nr;
+ unsigned int alloc;
+ unsigned int nr;
struct in_pack_object *array;
};
diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index c82b4dce68..f8519363a3 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -12,15 +12,30 @@ static const char * const builtin_rebase_helper_usage[] = {
int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts = REPLAY_OPTS_INIT;
+ int keep_empty = 0;
enum {
- CONTINUE = 1, ABORT
+ CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
+ CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH
} command = 0;
struct option options[] = {
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
+ OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
CONTINUE),
OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
ABORT),
+ OPT_CMDMODE(0, "make-script", &command,
+ N_("make rebase script"), MAKE_SCRIPT),
+ OPT_CMDMODE(0, "shorten-ids", &command,
+ N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S),
+ OPT_CMDMODE(0, "expand-ids", &command,
+ N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
+ OPT_CMDMODE(0, "check-todo-list", &command,
+ N_("check the todo list"), CHECK_TODO_LIST),
+ OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
+ N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
+ OPT_CMDMODE(0, "rearrange-squash", &command,
+ N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
OPT_END()
};
@@ -37,5 +52,17 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
return !!sequencer_continue(&opts);
if (command == ABORT && argc == 1)
return !!sequencer_remove_state(&opts);
+ if (command == MAKE_SCRIPT && argc > 1)
+ return !!sequencer_make_script(keep_empty, stdout, argc, argv);
+ if (command == SHORTEN_SHA1S && argc == 1)
+ return !!transform_todo_ids(1);
+ if (command == EXPAND_SHA1S && argc == 1)
+ return !!transform_todo_ids(0);
+ if (command == CHECK_TODO_LIST && argc == 1)
+ return !!check_todo_list();
+ if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
+ return !!skip_unnecessary_picks();
+ if (command == REARRANGE_SQUASH && argc == 1)
+ return !!rearrange_squash();
usage_with_options(builtin_rebase_helper_usage, options);
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index dd06b3fb4f..cc48767405 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1207,11 +1207,10 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
const char *dst_name;
struct string_list_item *item;
struct command *dst_cmd;
- unsigned char sha1[GIT_MAX_RAWSZ];
int flag;
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
- dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);
+ dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag);
strbuf_release(&buf);
if (!(flag & REF_ISSYMREF))
@@ -1459,7 +1458,6 @@ static void execute_commands(struct command *commands,
{
struct check_connected_options opt = CHECK_CONNECTED_INIT;
struct command *cmd;
- struct object_id oid;
struct iterate_data data;
struct async muxer;
int err_fd = 0;
@@ -1516,7 +1514,7 @@ static void execute_commands(struct command *commands,
check_aliased_updates(commands);
free(head_name_to_free);
- head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL);
+ head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
if (use_atomic)
execute_commands_atomic(commands, si);
diff --git a/builtin/reflog.c b/builtin/reflog.c
index e237d927a0..2067cca5b1 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -126,7 +126,7 @@ static int commit_is_complete(struct commit *commit)
struct commit *c;
struct commit_list *parent;
- c = (struct commit *)study.objects[--study.nr].item;
+ c = (struct commit *)object_array_pop(&study);
if (!c->object.parsed && !parse_object(&c->object.oid))
c->object.flags |= INCOMPLETE;
@@ -182,8 +182,8 @@ static int commit_is_complete(struct commit *commit)
found.objects[i].item->flags |= SEEN;
}
/* free object arrays */
- free(study.objects);
- free(found.objects);
+ object_array_clear(&study);
+ object_array_clear(&found);
return !is_incomplete;
}
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
index bfb21ba7d2..6a9127a33c 100644
--- a/builtin/remote-ext.c
+++ b/builtin/remote-ext.c
@@ -57,7 +57,7 @@ static char *strip_escapes(const char *str, const char *service,
special = str[rpos];
if (rpos == 1)
break;
- /* Fall-through to error. */
+ /* fallthrough */
default:
die("Bad remote-ext placeholder '%%%c'.",
str[rpos]);
diff --git a/builtin/remote.c b/builtin/remote.c
index 33ba739332..4f5cac96b0 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -558,14 +558,13 @@ static int read_remote_branches(const char *refname,
struct strbuf buf = STRBUF_INIT;
struct string_list_item *item;
int flag;
- struct object_id orig_oid;
const char *symref;
strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
if (starts_with(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, xstrdup(refname));
symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
- orig_oid.hash, &flag);
+ NULL, &flag);
if (flag & REF_ISSYMREF)
item->util = xstrdup(symref);
else
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index b9c13d3d9d..a8d7e6f7ae 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -387,6 +387,14 @@ static const char *skipspaces(const char *s)
return s;
}
+static char *findspace(const char *s)
+{
+ for (; *s; s++)
+ if (isspace(*s))
+ return (char*)s;
+ return NULL;
+}
+
static int cmd_parseopt(int argc, const char **argv, const char *prefix)
{
static int keep_dashdash = 0, stop_at_non_option = 0;
@@ -434,7 +442,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
/* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
while (strbuf_getline(&sb, stdin) != EOF) {
const char *s;
- const char *help;
+ char *help;
struct option *o;
if (!sb.len)
@@ -444,15 +452,17 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
memset(opts + onb, 0, sizeof(opts[onb]));
o = &opts[onb++];
- help = strchr(sb.buf, ' ');
- if (!help || *sb.buf == ' ') {
+ help = findspace(sb.buf);
+ if (!help || sb.buf == help) {
o->type = OPTION_GROUP;
o->help = xstrdup(skipspaces(sb.buf));
continue;
}
+ *help = '\0';
+
o->type = OPTION_CALLBACK;
- o->help = xstrdup(skipspaces(help));
+ o->help = xstrdup(skipspaces(help+1));
o->value = &parsed;
o->flags = PARSE_OPT_NOARG;
o->callback = &parseopt_dump;
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 818fe74f0a..06ed02f994 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -17,9 +17,8 @@
static char *get_default_remote(void)
{
char *dest = NULL, *ret;
- unsigned char sha1[20];
struct strbuf sb = STRBUF_INIT;
- const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
+ const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
if (!refname)
die(_("No such ref: %s"), "HEAD");
@@ -1089,8 +1088,7 @@ static const char *remote_submodule_branch(const char *path)
return "master";
if (!strcmp(branch, ".")) {
- unsigned char sha1[20];
- const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
+ const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
if (!refname)
die(_("No such ref: %s"), "HEAD");
@@ -1189,6 +1187,7 @@ static int push_check(int argc, const char **argv, const char *prefix)
break;
die("HEAD does not match the named branch in the superproject");
}
+ /* fallthrough */
default:
die("src refspec '%s' must name a ref",
rs->src);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index df75cb9d4a..17aabaa679 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -12,9 +12,8 @@ static const char * const git_symbolic_ref_usage[] = {
static int check_symref(const char *HEAD, int quiet, int shorten, int print)
{
- unsigned char sha1[20];
int flag;
- const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag);
+ const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag);
if (!refname)
die("No such ref: %s", HEAD);
diff --git a/builtin/tag.c b/builtin/tag.c
index c627794181..695cb0778e 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -411,6 +411,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
},
OPT_STRING( 0 , "format", &format.format, N_("format"),
N_("format to use for the output")),
+ OPT__COLOR(&format.use_color, N_("respect format colors")),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END()
};
@@ -552,9 +553,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (force && !is_null_oid(&prev) && oidcmp(&prev, &object))
printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev.hash, DEFAULT_ABBREV));
- strbuf_release(&err);
- strbuf_release(&buf);
- strbuf_release(&ref);
- strbuf_release(&reflog_msg);
+ UNLEAK(buf);
+ UNLEAK(ref);
+ UNLEAK(reflog_msg);
+ UNLEAK(msg);
+ UNLEAK(err);
return 0;
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index de26849f55..7b9307aa58 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -38,7 +38,9 @@ static int prune_worktree(const char *id, struct strbuf *reason)
{
struct stat st;
char *path;
- int fd, len;
+ int fd;
+ size_t len;
+ ssize_t read_result;
if (!is_directory(git_path("worktrees/%s", id))) {
strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
@@ -56,10 +58,26 @@ static int prune_worktree(const char *id, struct strbuf *reason)
id, strerror(errno));
return 1;
}
- len = st.st_size;
+ len = xsize_t(st.st_size);
path = xmallocz(len);
- read_in_full(fd, path, len);
+
+ read_result = read_in_full(fd, path, len);
+ if (read_result < 0) {
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
+ id, strerror(errno));
+ close(fd);
+ free(path);
+ return 1;
+ }
close(fd);
+
+ if (read_result != len) {
+ strbuf_addf(reason,
+ _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
+ id, (uintmax_t)len, (uintmax_t)read_result);
+ free(path);
+ return 1;
+ }
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
len--;
if (!len) {
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 9a1f6c49ab..3310fd210a 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -115,7 +115,10 @@ static int stream_to_pack(struct bulk_checkin_state *state,
if (size && !s.avail_in) {
ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
- if (read_in_full(fd, ibuf, rsize) != rsize)
+ ssize_t read_result = read_in_full(fd, ibuf, rsize);
+ if (read_result < 0)
+ die_errno("failed to read from '%s'", path);
+ if (read_result != rsize)
die("failed to read %d bytes from '%s'",
(int)rsize, path);
offset += rsize;
diff --git a/bundle.c b/bundle.c
index d15db03c84..c092d5d68f 100644
--- a/bundle.c
+++ b/bundle.c
@@ -157,9 +157,14 @@ int verify_bundle(struct bundle_header *header, int verbose)
req_nr = revs.pending.nr;
setup_revisions(2, argv, &revs, NULL);
+ /* Save pending objects, so they can be cleaned up later. */
refs = revs.pending;
revs.leak_pending = 1;
+ /*
+ * prepare_revision_walk (together with .leak_pending = 1) makes us
+ * the sole owner of the list of pending objects.
+ */
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
@@ -176,8 +181,10 @@ int verify_bundle(struct bundle_header *header, int verbose)
refs.objects[i].name);
}
+ /* Clean up objects used, as they will be reused. */
clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
- free(refs.objects);
+
+ object_array_clear(&refs);
if (verbose) {
struct ref_list *r;
diff --git a/cache.h b/cache.h
index 49b083ee0a..6440e2bf21 100644
--- a/cache.h
+++ b/cache.h
@@ -444,6 +444,7 @@ static inline enum object_type object_type(unsigned int mode)
#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
#define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
+#define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
/*
* This environment variable is expected to contain a boolean indicating
@@ -784,6 +785,11 @@ extern int protect_ntfs;
extern int ref_paranoia;
/*
+ * Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value).
+ */
+int use_optional_locks(void);
+
+/*
* The character that begins a commented line in user-editable file
* that is subject to stripspace.
*/
@@ -1244,8 +1250,8 @@ static inline unsigned int hexval(unsigned char c)
*/
static inline int hex2chr(const char *s)
{
- int val = hexval(s[0]);
- return (val < 0) ? val : (val << 4) | hexval(s[1]);
+ unsigned int val = hexval(s[0]);
+ return (val & ~0xf) ? val : (val << 4) | hexval(s[1]);
}
/* Convert to/from hex/sha1 representation */
diff --git a/color.c b/color.c
index 9ccd954d6b..9c0dc82370 100644
--- a/color.c
+++ b/color.c
@@ -308,7 +308,7 @@ int git_config_colorbool(const char *var, const char *value)
if (!strcasecmp(value, "never"))
return 0;
if (!strcasecmp(value, "always"))
- return 1;
+ return var ? GIT_COLOR_AUTO : 1;
if (!strcasecmp(value, "auto"))
return GIT_COLOR_AUTO;
}
diff --git a/commit-slab.h b/commit-slab.h
index 333d81e370..dcaab8ca04 100644
--- a/commit-slab.h
+++ b/commit-slab.h
@@ -78,7 +78,7 @@ static MAYBE_UNUSED void init_ ##slabname(struct slabname *s) \
\
static MAYBE_UNUSED void clear_ ##slabname(struct slabname *s) \
{ \
- int i; \
+ unsigned int i; \
for (i = 0; i < s->slab_count; i++) \
free(s->slab[i]); \
s->slab_count = 0; \
@@ -89,13 +89,13 @@ static MAYBE_UNUSED elemtype *slabname## _at_peek(struct slabname *s, \
const struct commit *c, \
int add_if_missing) \
{ \
- int nth_slab, nth_slot; \
+ unsigned int nth_slab, nth_slot; \
\
nth_slab = c->index / s->slab_size; \
nth_slot = c->index % s->slab_size; \
\
if (s->slab_count <= nth_slab) { \
- int i; \
+ unsigned int i; \
if (!add_if_missing) \
return NULL; \
REALLOC_ARRAY(s->slab, nth_slab + 1); \
diff --git a/commit.c b/commit.c
index 906298052d..1e0e633790 100644
--- a/commit.c
+++ b/commit.c
@@ -1086,6 +1086,7 @@ struct commit_list *reduce_heads(struct commit_list *heads)
num_head = remove_redundant(array, num_head);
for (i = 0; i < num_head; i++)
tail = &commit_list_insert(array[i], tail)->next;
+ free(array);
return result;
}
diff --git a/compat/poll/poll.c b/compat/poll/poll.c
index b10adc780f..ae03b74a6f 100644
--- a/compat/poll/poll.c
+++ b/compat/poll/poll.c
@@ -438,6 +438,10 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout)
pfd[i].revents = happened;
rc++;
}
+ else
+ {
+ pfd[i].revents = 0;
+ }
}
return rc;
diff --git a/compat/win32/lazyload.h b/compat/win32/lazyload.h
new file mode 100644
index 0000000000..9e631c8593
--- /dev/null
+++ b/compat/win32/lazyload.h
@@ -0,0 +1,57 @@
+#ifndef LAZYLOAD_H
+#define LAZYLOAD_H
+
+/*
+ * A pair of macros to simplify loading of DLL functions. Example:
+ *
+ * DECLARE_PROC_ADDR(kernel32.dll, BOOL, CreateHardLinkW,
+ * LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
+ *
+ * if (!INIT_PROC_ADDR(CreateHardLinkW))
+ * return error("Could not find CreateHardLinkW() function";
+ *
+ * if (!CreateHardLinkW(source, target, NULL))
+ * return error("could not create hardlink from %S to %S",
+ * source, target);
+ */
+
+struct proc_addr {
+ const char *const dll;
+ const char *const function;
+ FARPROC pfunction;
+ unsigned initialized : 1;
+};
+
+/* Declares a function to be loaded dynamically from a DLL. */
+#define DECLARE_PROC_ADDR(dll, rettype, function, ...) \
+ static struct proc_addr proc_addr_##function = \
+ { #dll, #function, NULL, 0 }; \
+ static rettype (WINAPI *function)(__VA_ARGS__)
+
+/*
+ * Loads a function from a DLL (once-only).
+ * Returns non-NULL function pointer on success.
+ * Returns NULL + errno == ENOSYS on failure.
+ * This function is not thread-safe.
+ */
+#define INIT_PROC_ADDR(function) \
+ (function = get_proc_addr(&proc_addr_##function))
+
+static inline void *get_proc_addr(struct proc_addr *proc)
+{
+ /* only do this once */
+ if (!proc->initialized) {
+ HANDLE hnd;
+ proc->initialized = 1;
+ hnd = LoadLibraryExA(proc->dll, NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (hnd)
+ proc->pfunction = GetProcAddress(hnd, proc->function);
+ }
+ /* set ENOSYS if DLL or function was not found */
+ if (!proc->pfunction)
+ errno = ENOSYS;
+ return proc->pfunction;
+}
+
+#endif
diff --git a/config.c b/config.c
index a77cae56ed..4831c12735 100644
--- a/config.c
+++ b/config.c
@@ -2200,7 +2200,7 @@ static struct {
size_t *offset;
unsigned int offset_alloc;
enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
- int seen;
+ unsigned int seen;
} store;
static int matches(const char *key, const char *value)
@@ -2292,11 +2292,10 @@ static int write_error(const char *filename)
return 4;
}
-static ssize_t write_section(int fd, const char *key)
+static struct strbuf store_create_section(const char *key)
{
const char *dot;
int i;
- ssize_t ret;
struct strbuf sb = STRBUF_INIT;
dot = memchr(key, '.', store.baselen);
@@ -2312,7 +2311,15 @@ static ssize_t write_section(int fd, const char *key)
strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
}
- ret = write_in_full(fd, sb.buf, sb.len);
+ return sb;
+}
+
+static ssize_t write_section(int fd, const char *key)
+{
+ struct strbuf sb = store_create_section(key);
+ ssize_t ret;
+
+ ret = write_in_full(fd, sb.buf, sb.len) == sb.len;
strbuf_release(&sb);
return ret;
@@ -2355,6 +2362,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value)
case '"':
case '\\':
strbuf_addch(&sb, '\\');
+ /* fallthrough */
default:
strbuf_addch(&sb, value[i]);
break;
@@ -2742,8 +2750,8 @@ static int section_name_is_ok(const char *name)
}
/* if new_name == NULL, the section is removed instead */
-int git_config_rename_section_in_file(const char *config_filename,
- const char *old_name, const char *new_name)
+static int git_config_copy_or_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name, int copy)
{
int ret = 0, remove = 0;
char *filename_buf = NULL;
@@ -2752,6 +2760,7 @@ int git_config_rename_section_in_file(const char *config_filename,
char buf[1024];
FILE *config_file = NULL;
struct stat st;
+ struct strbuf copystr = STRBUF_INIT;
if (new_name && !section_name_is_ok(new_name)) {
ret = error("invalid section name: %s", new_name);
@@ -2790,12 +2799,30 @@ int git_config_rename_section_in_file(const char *config_filename,
while (fgets(buf, sizeof(buf), config_file)) {
int i;
int length;
+ int is_section = 0;
char *output = buf;
for (i = 0; buf[i] && isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] == '[') {
/* it's a section */
- int offset = section_name_match(&buf[i], old_name);
+ int offset;
+ is_section = 1;
+
+ /*
+ * When encountering a new section under -c we
+ * need to flush out any section we're already
+ * coping and begin anew. There might be
+ * multiple [branch "$name"] sections.
+ */
+ if (copystr.len > 0) {
+ if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+ strbuf_reset(&copystr);
+ }
+
+ offset = section_name_match(&buf[i], old_name);
if (offset > 0) {
ret++;
if (new_name == NULL) {
@@ -2803,25 +2830,29 @@ int git_config_rename_section_in_file(const char *config_filename,
continue;
}
store.baselen = strlen(new_name);
- if (write_section(out_fd, new_name) < 0) {
- ret = write_error(get_lock_file_path(lock));
- goto out;
- }
- /*
- * We wrote out the new section, with
- * a newline, now skip the old
- * section's length
- */
- output += offset + i;
- if (strlen(output) > 0) {
+ if (!copy) {
+ if (write_section(out_fd, new_name) < 0) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
/*
- * More content means there's
- * a declaration to put on the
- * next line; indent with a
- * tab
+ * We wrote out the new section, with
+ * a newline, now skip the old
+ * section's length
*/
- output -= 1;
- output[0] = '\t';
+ output += offset + i;
+ if (strlen(output) > 0) {
+ /*
+ * More content means there's
+ * a declaration to put on the
+ * next line; indent with a
+ * tab
+ */
+ output -= 1;
+ output[0] = '\t';
+ }
+ } else {
+ copystr = store_create_section(new_name);
}
}
remove = 0;
@@ -2829,11 +2860,30 @@ int git_config_rename_section_in_file(const char *config_filename,
if (remove)
continue;
length = strlen(output);
+
+ if (!is_section && copystr.len > 0) {
+ strbuf_add(&copystr, output, length);
+ }
+
if (write_in_full(out_fd, output, length) < 0) {
ret = write_error(get_lock_file_path(lock));
goto out;
}
}
+
+ /*
+ * Copy a trailing section at the end of the config, won't be
+ * flushed by the usual "flush because we have a new section
+ * logic in the loop above.
+ */
+ if (copystr.len > 0) {
+ if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+ strbuf_reset(&copystr);
+ }
+
fclose(config_file);
config_file = NULL;
commit_and_out:
@@ -2849,11 +2899,30 @@ out_no_rollback:
return ret;
}
+int git_config_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
+{
+ return git_config_copy_or_rename_section_in_file(config_filename,
+ old_name, new_name, 0);
+}
+
int git_config_rename_section(const char *old_name, const char *new_name)
{
return git_config_rename_section_in_file(NULL, old_name, new_name);
}
+int git_config_copy_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
+{
+ return git_config_copy_or_rename_section_in_file(config_filename,
+ old_name, new_name, 1);
+}
+
+int git_config_copy_section(const char *old_name, const char *new_name)
+{
+ return git_config_copy_section_in_file(NULL, old_name, new_name);
+}
+
/*
* Call this to report error for your variable that should not
* get a boolean value (i.e. "[my] var" means "true").
diff --git a/config.h b/config.h
index 456b3d134d..a49d264416 100644
--- a/config.h
+++ b/config.h
@@ -70,6 +70,8 @@ extern int git_config_set_multivar_in_file_gently(const char *, const char *, co
extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
extern int git_config_rename_section(const char *, const char *);
extern int git_config_rename_section_in_file(const char *, const char *, const char *);
+extern int git_config_copy_section(const char *, const char *);
+extern int git_config_copy_section_in_file(const char *, const char *, const char *);
extern const char *git_etc_gitconfig(void);
extern int git_env_bool(const char *, int);
extern unsigned long git_env_ulong(const char *, unsigned long);
diff --git a/config.mak.uname b/config.mak.uname
index 6604b130f8..685a80d138 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -184,6 +184,7 @@ ifeq ($(uname_O),Cygwin)
UNRELIABLE_FSTAT = UnfortunatelyYes
SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
+ MMAP_PREVENTS_DELETE = UnfortunatelyYes
COMPAT_OBJS += compat/cygwin.o
FREAD_READS_DIRECTORIES = UnfortunatelyYes
endif
@@ -353,6 +354,7 @@ ifeq ($(uname_S),Windows)
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
USE_WIN32_MMAP = YesPlease
+ MMAP_PREVENTS_DELETE = UnfortunatelyYes
# USE_NED_ALLOCATOR = YesPlease
UNRELIABLE_FSTAT = UnfortunatelyYes
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
@@ -501,6 +503,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
USE_WIN32_MMAP = YesPlease
+ MMAP_PREVENTS_DELETE = UnfortunatelyYes
USE_NED_ALLOCATOR = YesPlease
UNRELIABLE_FSTAT = UnfortunatelyYes
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci
index c61d1ca8dc..01586821dc 100644
--- a/contrib/coccinelle/array.cocci
+++ b/contrib/coccinelle/array.cocci
@@ -4,7 +4,7 @@ T *dst;
T *src;
expression n;
@@
-- memcpy(dst, src, n * sizeof(*dst));
+- memcpy(dst, src, (n) * sizeof(*dst));
+ COPY_ARRAY(dst, src, n);
@@
@@ -13,7 +13,7 @@ T *dst;
T *src;
expression n;
@@
-- memcpy(dst, src, n * sizeof(*src));
+- memcpy(dst, src, (n) * sizeof(*src));
+ COPY_ARRAY(dst, src, n);
@@
@@ -22,7 +22,7 @@ T *dst;
T *src;
expression n;
@@
-- memcpy(dst, src, n * sizeof(T));
+- memcpy(dst, src, (n) * sizeof(T));
+ COPY_ARRAY(dst, src, n);
@@
@@ -47,7 +47,7 @@ type T;
T *ptr;
expression n;
@@
-- ptr = xmalloc(n * sizeof(*ptr));
+- ptr = xmalloc((n) * sizeof(*ptr));
+ ALLOC_ARRAY(ptr, n);
@@
@@ -55,5 +55,5 @@ type T;
T *ptr;
expression n;
@@
-- ptr = xmalloc(n * sizeof(T));
+- ptr = xmalloc((n) * sizeof(T));
+ ALLOC_ARRAY(ptr, n);
diff --git a/convert.c b/convert.c
index a09935cb81..20d7ab67bd 100644
--- a/convert.c
+++ b/convert.c
@@ -1545,8 +1545,9 @@ static int ident_filter_fn(struct stream_filter *filter,
switch (ident->state) {
default:
strbuf_add(&ident->left, head, ident->state);
+ /* fallthrough */
case IDENT_SKIPPING:
- /* fallthru */
+ /* fallthrough */
case IDENT_DRAINING:
ident_drain(ident, &output, osize_p);
}
diff --git a/csum-file.c b/csum-file.c
index a172199e44..2adae04073 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -19,7 +19,7 @@ static void flush(struct sha1file *f, const void *buf, unsigned int count)
if (ret < 0)
die_errno("%s: sha1 file read error", f->name);
- if (ret < count)
+ if (ret != count)
die("%s: sha1 file truncated", f->name);
if (memcmp(buf, check_buffer, count))
die("sha1 file '%s' validation error", f->name);
diff --git a/diff-delta.c b/diff-delta.c
index 3797ce6041..e49643353b 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -319,7 +319,9 @@ create_delta(const struct delta_index *index,
const void *trg_buf, unsigned long trg_size,
unsigned long *delta_size, unsigned long max_size)
{
- unsigned int i, outpos, outsize, moff, msize, val;
+ unsigned int i, val;
+ off_t outpos, moff;
+ size_t l, outsize, msize;
int inscnt;
const unsigned char *ref_data, *ref_top, *data, *top;
unsigned char *out;
@@ -336,20 +338,20 @@ create_delta(const struct delta_index *index,
return NULL;
/* store reference buffer size */
- i = index->src_size;
- while (i >= 0x80) {
- out[outpos++] = i | 0x80;
- i >>= 7;
+ l = index->src_size;
+ while (l >= 0x80) {
+ out[outpos++] = l | 0x80;
+ l >>= 7;
}
- out[outpos++] = i;
+ out[outpos++] = l;
/* store target buffer size */
- i = trg_size;
- while (i >= 0x80) {
- out[outpos++] = i | 0x80;
- i >>= 7;
+ l = trg_size;
+ while (l >= 0x80) {
+ out[outpos++] = l | 0x80;
+ l >>= 7;
}
- out[outpos++] = i;
+ out[outpos++] = l;
ref_data = index->src_buf;
ref_top = ref_data + index->src_size;
@@ -452,6 +454,9 @@ create_delta(const struct delta_index *index,
moff += msize;
msize = left;
+ if (moff > 0xffffffff)
+ msize = 0;
+
if (msize < 4096) {
int j;
val = 0;
diff --git a/diff-lib.c b/diff-lib.c
index 2a52b07954..4e0980caa8 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -549,7 +549,6 @@ int index_differs_from(const char *def, int diff_flags,
rev.diffopt.flags |= diff_flags;
rev.diffopt.ita_invisible_in_index = ita_invisible_in_index;
run_diff_index(&rev, 1);
- if (rev.pending.alloc)
- free(rev.pending.objects);
+ object_array_clear(&rev.pending);
return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0);
}
diff --git a/diff.c b/diff.c
index 3c6a3e0faa..69f03570ad 100644
--- a/diff.c
+++ b/diff.c
@@ -1541,7 +1541,7 @@ static void emit_rewrite_diff(const char *name_a,
struct diff_words_buffer {
mmfile_t text;
- long alloc;
+ unsigned long alloc;
struct diff_words_orig {
const char *begin, *end;
} *orig;
@@ -5272,6 +5272,7 @@ static void show_mode_change(struct diff_options *opt, struct diff_filepair *p,
strbuf_addch(&sb, ' ');
quote_c_style(p->two->path, &sb, NULL, 0);
}
+ strbuf_addch(&sb, '\n');
emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY,
sb.buf, sb.len, 0);
strbuf_release(&sb);
diff --git a/dir.c b/dir.c
index 1c55dc3e36..1d17b800cf 100644
--- a/dir.c
+++ b/dir.c
@@ -49,7 +49,7 @@ struct cached_dir {
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
struct index_state *istate, const char *path, int len,
struct untracked_cache_dir *untracked,
- int check_only, const struct pathspec *pathspec);
+ int check_only, int stop_at_first_file, const struct pathspec *pathspec);
static int get_dtype(struct dirent *de, struct index_state *istate,
const char *path, int len);
@@ -1404,8 +1404,13 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
untracked = lookup_untracked(dir->untracked, untracked,
dirname + baselen, len - baselen);
+
+ /*
+ * If this is an excluded directory, then we only need to check if
+ * the directory contains any files.
+ */
return read_directory_recursive(dir, istate, dirname, len,
- untracked, 1, pathspec);
+ untracked, 1, exclude, pathspec);
}
/*
@@ -1633,7 +1638,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
* with check_only set.
*/
return read_directory_recursive(dir, istate, path->buf, path->len,
- cdir->ucd, 1, pathspec);
+ cdir->ucd, 1, 0, pathspec);
/*
* We get path_recurse in the first run when
* directory_exists_in_index() returns index_nonexistent. We
@@ -1793,12 +1798,20 @@ static void close_cached_dir(struct cached_dir *cdir)
* Also, we ignore the name ".git" (even if it is not a directory).
* That likely will not change.
*
+ * If 'stop_at_first_file' is specified, 'path_excluded' is returned
+ * to signal that a file was found. This is the least significant value that
+ * indicates that a file was encountered that does not depend on the order of
+ * whether an untracked or exluded path was encountered first.
+ *
* Returns the most significant path_treatment value encountered in the scan.
+ * If 'stop_at_first_file' is specified, `path_excluded` is the most
+ * significant path_treatment value that will be returned.
*/
+
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
struct index_state *istate, const char *base, int baselen,
struct untracked_cache_dir *untracked, int check_only,
- const struct pathspec *pathspec)
+ int stop_at_first_file, const struct pathspec *pathspec)
{
struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
@@ -1832,12 +1845,34 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
subdir_state =
read_directory_recursive(dir, istate, path.buf,
path.len, ud,
- check_only, pathspec);
+ check_only, stop_at_first_file, pathspec);
if (subdir_state > dir_state)
dir_state = subdir_state;
}
if (check_only) {
+ if (stop_at_first_file) {
+ /*
+ * If stopping at first file, then
+ * signal that a file was found by
+ * returning `path_excluded`. This is
+ * to return a consistent value
+ * regardless of whether an ignored or
+ * excluded file happened to be
+ * encountered 1st.
+ *
+ * In current usage, the
+ * `stop_at_first_file` is passed when
+ * an ancestor directory has matched
+ * an exclude pattern, so any found
+ * files will be excluded.
+ */
+ if (dir_state >= path_excluded) {
+ dir_state = path_excluded;
+ break;
+ }
+ }
+
/* abort early if maximum state has been reached */
if (dir_state == path_untracked) {
if (cdir.fdir)
@@ -2108,7 +2143,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
*/
dir->untracked = NULL;
if (!len || treat_leading_path(dir, istate, path, len, pathspec))
- read_directory_recursive(dir, istate, path, len, untracked, 0, pathspec);
+ read_directory_recursive(dir, istate, path, len, untracked, 0, 0, pathspec);
QSORT(dir->entries, dir->nr, cmp_dir_entry);
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
diff --git a/environment.c b/environment.c
index f1f934b6fd..8289c25b44 100644
--- a/environment.c
+++ b/environment.c
@@ -338,3 +338,8 @@ void reset_shared_repository(void)
{
need_shared_repository_from_config = 1;
}
+
+int use_optional_locks(void)
+{
+ return git_env_bool(GIT_OPTIONAL_LOCKS_ENVIRONMENT, 1);
+}
diff --git a/fast-import.c b/fast-import.c
index 35bf671f12..d5e4cf0bad 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3189,10 +3189,10 @@ static void checkpoint(void)
checkpoint_requested = 0;
if (object_count) {
cycle_packfile();
- dump_branches();
- dump_tags();
- dump_marks();
}
+ dump_branches();
+ dump_tags();
+ dump_marks();
}
static void parse_checkpoint(void)
diff --git a/fetch-pack.c b/fetch-pack.c
index 105506e9aa..008b25d3db 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -611,7 +611,7 @@ static int tip_oids_contain(struct oidset *tip_oids,
* add to "newlist" between calls, the additions will always be for
* oids that are already in the set.
*/
- if (!tip_oids->map.tablesize) {
+ if (!tip_oids->map.map.tablesize) {
add_refs_to_oidset(tip_oids, unmatched);
add_refs_to_oidset(tip_oids, newlist);
}
diff --git a/fsck.c b/fsck.c
index 2d2d2e9432..2ad00fc4d0 100644
--- a/fsck.c
+++ b/fsck.c
@@ -588,6 +588,7 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
case S_IFREG | 0664:
if (!options->strict)
break;
+ /* fallthrough */
default:
has_bad_modes = 1;
}
diff --git a/git-archimport.perl b/git-archimport.perl
index 9cb123a07d..b7c173c345 100755
--- a/git-archimport.perl
+++ b/git-archimport.perl
@@ -983,7 +983,7 @@ sub find_parents {
# check that we actually know about the branch
next unless -e "$git_dir/refs/heads/$branch";
- my $mergebase = `git-merge-base $branch $ps->{branch}`;
+ my $mergebase = safe_pipe_capture(qw(git-merge-base), $branch, $ps->{branch});
if ($?) {
# Don't die here, Arch supports one-way cherry-picking
# between branches with no common base (or any relationship
@@ -1074,7 +1074,7 @@ sub find_parents {
sub git_rev_parse {
my $name = shift;
- my $val = `git-rev-parse $name`;
+ my $val = safe_pipe_capture(qw(git-rev-parse), $name);
die "Error: git-rev-parse $name" if $?;
chomp $val;
return $val;
diff --git a/git-compat-util.h b/git-compat-util.h
index 9bc15b0363..cedad4d581 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -898,9 +898,11 @@ static inline char *xstrdup_or_null(const char *str)
static inline size_t xsize_t(off_t len)
{
- if (len > (size_t) len)
+ size_t size = (size_t) len;
+
+ if (len != (off_t) size)
die("Cannot handle files this big");
- return (size_t)len;
+ return size;
}
__attribute__((format (printf, 3, 4)))
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 1e4e65a45d..36929921ea 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -642,6 +642,7 @@ sub is_sha1 {
sub get_headref ($) {
my $name = shift;
+ $name =~ s/'/'\\''/;
my $r = `git rev-parse --verify '$name' 2>/dev/null`;
return undef unless $? == 0;
chomp $r;
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index d50c85ed7b..ae1044273d 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -356,7 +356,7 @@ sub req_Root
return 0;
}
- my @gitvars = `git config -l`;
+ my @gitvars = safe_pipe_capture(qw(git config -l));
if ($?) {
print "E problems executing git-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
print "E \n";
@@ -841,7 +841,7 @@ sub req_Modified
# Save the file data in $state
$state->{entries}{$state->{directory}.$data}{modified_filename} = $filename;
$state->{entries}{$state->{directory}.$data}{modified_mode} = $mode;
- $state->{entries}{$state->{directory}.$data}{modified_hash} = `git hash-object $filename`;
+ $state->{entries}{$state->{directory}.$data}{modified_hash} = safe_pipe_capture('git','hash-object',$filename);
$state->{entries}{$state->{directory}.$data}{modified_hash} =~ s/\s.*$//s;
#$log->debug("req_Modified : file=$data mode=$mode size=$size");
@@ -943,7 +943,7 @@ sub req_co
# Provide list of modules, if -c was used.
if (exists $state->{opt}{c}) {
- my $showref = `git show-ref --heads`;
+ my $showref = safe_pipe_capture(qw(git show-ref --heads));
for my $line (split '\n', $showref) {
if ( $line =~ m% refs/heads/(.*)$% ) {
print "M $1\t$1\n";
@@ -1181,7 +1181,7 @@ sub req_update
# projects (heads in this case) to checkout.
#
if ($state->{module} eq '') {
- my $showref = `git show-ref --heads`;
+ my $showref = safe_pipe_capture(qw(git show-ref --heads));
print "E cvs update: Updating .\n";
for my $line (split '\n', $showref) {
if ( $line =~ m% refs/heads/(.*)$% ) {
@@ -1463,7 +1463,7 @@ sub req_update
# transmit file, format is single integer on a line by itself (file
# size) followed by the file contents
# TODO : we should copy files in blocks
- my $data = `cat $mergedFile`;
+ my $data = safe_pipe_capture('cat', $mergedFile);
$log->debug("File size : " . length($data));
print length($data) . "\n";
print $data;
@@ -1579,7 +1579,7 @@ sub req_ci
$branchRef = "refs/heads/$stickyInfo->{tag}";
}
- $parenthash = `git show-ref -s $branchRef`;
+ $parenthash = safe_pipe_capture('git', 'show-ref', '-s', $branchRef);
chomp $parenthash;
if ($parenthash !~ /^[0-9a-f]{40}$/)
{
@@ -1687,7 +1687,7 @@ sub req_ci
return;
}
- my $treehash = `git write-tree`;
+ my $treehash = safe_pipe_capture(qw(git write-tree));
chomp $treehash;
$log->debug("Treehash : $treehash, Parenthash : $parenthash");
@@ -1704,7 +1704,7 @@ sub req_ci
}
close $msg_fh;
- my $commithash = `git commit-tree $treehash -p $parenthash < $msg_filename`;
+ my $commithash = safe_pipe_capture('git', 'commit-tree', $treehash, '-p', $parenthash, '-F', $msg_filename);
chomp($commithash);
$log->info("Commit hash : $commithash");
@@ -2854,12 +2854,12 @@ sub transmitfile
die "Need filehash" unless ( defined ( $filehash ) and $filehash =~ /^[a-zA-Z0-9]{40}$/ );
- my $type = `git cat-file -t $filehash`;
+ my $type = safe_pipe_capture('git', 'cat-file', '-t', $filehash);
chomp $type;
die ( "Invalid type '$type' (expected 'blob')" ) unless ( defined ( $type ) and $type eq "blob" );
- my $size = `git cat-file -s $filehash`;
+ my $size = safe_pipe_capture('git', 'cat-file', '-s', $filehash);
chomp $size;
$log->debug("transmitfile($filehash) size=$size, type=$type");
@@ -3040,7 +3040,7 @@ sub ensureWorkTree
chdir $work->{emptyDir} or
die "Unable to chdir to $work->{emptyDir}\n";
- my $ver = `git show-ref -s refs/heads/$state->{module}`;
+ my $ver = safe_pipe_capture('git', 'show-ref', '-s', "refs/heads/$state->{module}");
chomp $ver;
if ($ver !~ /^[0-9a-f]{40}$/)
{
@@ -3287,7 +3287,7 @@ sub open_blob_or_die
die "Need filehash\n";
}
- my $type = `git cat-file -t $name`;
+ my $type = safe_pipe_capture('git', 'cat-file', '-t', $name);
chomp $type;
unless ( defined ( $type ) and $type eq "blob" )
@@ -3296,7 +3296,7 @@ sub open_blob_or_die
die ( "Invalid type '$type' (expected 'blob')" )
}
- my $size = `git cat-file -s $name`;
+ my $size = safe_pipe_capture('git', 'cat-file', '-s', $name);
chomp $size;
$log->debug("open_blob_or_die($name) size=$size, type=$type");
@@ -3406,6 +3406,22 @@ sub refHashEqual
return $out;
}
+# an alternative to `command` that allows input to be passed as an array
+# to work around shell problems with weird characters in arguments
+
+sub safe_pipe_capture {
+
+ my @output;
+
+ if (my $pid = open my $child, '-|') {
+ @output = (<$child>);
+ close $child or die join(' ',@_).": $! $?";
+ } else {
+ exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+ }
+ return wantarray ? @output : join('',@output);
+}
+
package GITCVS::log;
@@ -3797,10 +3813,10 @@ sub update
# first lets get the commit list
$ENV{GIT_DIR} = $self->{git_path};
- my $commitsha1 = `git rev-parse $self->{module}`;
+ my $commitsha1 = ::safe_pipe_capture('git', 'rev-parse', $self->{module});
chomp $commitsha1;
- my $commitinfo = `git cat-file commit $self->{module} 2>&1`;
+ my $commitinfo = ::safe_pipe_capture('git', 'cat-file', 'commit', $self->{module});
unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ )
{
die("Invalid module '$self->{module}'");
@@ -3882,7 +3898,7 @@ sub update
# several candidate merge bases. let's assume
# that the first one is the best one.
my $base = eval {
- safe_pipe_capture('git', 'merge-base',
+ ::safe_pipe_capture('git', 'merge-base',
$lastpicked, $parent);
};
# The two branches may not be related at all,
@@ -4749,7 +4765,7 @@ sub getMetaFromCommithash
return $retVal;
}
- my($fileHash)=safe_pipe_capture("git","rev-parse","$revCommit:$filename");
+ my($fileHash) = ::safe_pipe_capture("git","rev-parse","$revCommit:$filename");
chomp $fileHash;
if(!($fileHash=~/^[0-9a-f]{40}$/))
{
@@ -4844,8 +4860,8 @@ sub lookupCommitRef
return $commitHash;
}
- $commitHash=safe_pipe_capture("git","rev-parse","--verify","--quiet",
- $self->unescapeRefName($ref));
+ $commitHash = ::safe_pipe_capture("git","rev-parse","--verify","--quiet",
+ $self->unescapeRefName($ref));
$commitHash=~s/\s*$//;
if(!($commitHash=~/^[0-9a-f]{40}$/))
{
@@ -4854,7 +4870,7 @@ sub lookupCommitRef
if( defined($commitHash) )
{
- my $type=safe_pipe_capture("git","cat-file","-t",$commitHash);
+ my $type = ::safe_pipe_capture("git","cat-file","-t",$commitHash);
if( ! ($type=~/^commit\s*$/ ) )
{
$commitHash=undef;
@@ -4907,7 +4923,7 @@ sub commitmessage
return $message;
}
- my @lines = safe_pipe_capture("git", "cat-file", "commit", $commithash);
+ my @lines = ::safe_pipe_capture("git", "cat-file", "commit", $commithash);
shift @lines while ( $lines[0] =~ /\S/ );
$message = join("",@lines);
$message .= " " if ( $message =~ /\n$/ );
@@ -5056,25 +5072,6 @@ sub in_array
return $retval;
}
-=head2 safe_pipe_capture
-
-an alternative to `command` that allows input to be passed as an array
-to work around shell problems with weird characters in arguments
-
-=cut
-sub safe_pipe_capture {
-
- my @output;
-
- if (my $pid = open my $child, '-|') {
- @output = (<$child>);
- close $child or die join(' ',@_).": $! $?";
- } else {
- exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
- }
- return wantarray ? @output : join('',@output);
-}
-
=head2 mangle_dirname
create a string from a directory name that is suitable to use as
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 3a74602ef3..3365a3b866 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -86,7 +86,7 @@ USAGE="[--setup <command>] [--env-filter <command>]
[--parent-filter <command>] [--msg-filter <command>]
[--commit-filter <command>] [--tag-name-filter <command>]
[--subdirectory-filter <directory>] [--original <namespace>]
- [-d <directory>] [-f | --force]
+ [-d <directory>] [-f | --force] [--state-branch <branch>]
[--] [<rev-list options>...]"
OPTIONS_SPEC=
@@ -106,6 +106,7 @@ filter_msg=cat
filter_commit=
filter_tag_name=
filter_subdir=
+state_branch=
orig_namespace=refs/original/
force=
prune_empty=
@@ -181,6 +182,9 @@ do
--original)
orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
;;
+ --state-branch)
+ state_branch="$OPTARG"
+ ;;
*)
usage
;;
@@ -219,6 +223,13 @@ trap 'cd "$orig_dir"; rm -rf "$tempdir"' 0
ORIG_GIT_DIR="$GIT_DIR"
ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
+ORIG_GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME"
+ORIG_GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL"
+ORIG_GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE"
+ORIG_GIT_COMMITTER_NAME="$GIT_COMMITTER_NAME"
+ORIG_GIT_COMMITTER_EMAIL="$GIT_COMMITTER_EMAIL"
+ORIG_GIT_COMMITTER_DATE="$GIT_COMMITTER_DATE"
+
GIT_WORK_TREE=.
export GIT_DIR GIT_WORK_TREE
@@ -252,6 +263,26 @@ export GIT_INDEX_FILE
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"
+if test -n "$state_branch"
+then
+ state_commit=$(git rev-parse --no-flags --revs-only "$state_branch")
+ if test -n "$state_commit"
+ then
+ echo "Populating map from $state_branch ($state_commit)" 1>&2
+ perl -e'open(MAP, "-|", "git show $ARGV[0]:filter.map") or die;
+ while (<MAP>) {
+ m/(.*):(.*)/ or die;
+ open F, ">../map/$1" or die;
+ print F "$2" or die;
+ close(F) or die;
+ }
+ close(MAP) or die;' "$state_commit" \
+ || die "Unable to load state from $state_branch:filter.map"
+ else
+ echo "Branch $state_branch does not exist. Will create" 1>&2
+ fi
+fi
+
# we need "--" only if there are no path arguments in $@
nonrevs=$(git rev-parse --no-revs "$@") || exit
if test -z "$nonrevs"
@@ -530,7 +561,7 @@ if [ "$filter_tag_name" ]; then
}' \
-e '/^-----BEGIN PGP SIGNATURE-----/q' \
-e 'p' ) |
- git mktag) ||
+ git hash-object -t tag -w --stdin) ||
die "Could not create new tag object for $ref"
if git cat-file tag "$ref" | \
sane_grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1
@@ -544,12 +575,9 @@ if [ "$filter_tag_name" ]; then
done
fi
-cd "$orig_dir"
-rm -rf "$tempdir"
-
-trap - 0
-
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
+unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE
test -z "$ORIG_GIT_DIR" || {
GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
}
@@ -561,6 +589,58 @@ test -z "$ORIG_GIT_INDEX_FILE" || {
GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
export GIT_INDEX_FILE
}
+test -z "$ORIG_GIT_AUTHOR_NAME" || {
+ GIT_AUTHOR_NAME="$ORIG_GIT_AUTHOR_NAME" &&
+ export GIT_AUTHOR_NAME
+}
+test -z "$ORIG_GIT_AUTHOR_EMAIL" || {
+ GIT_AUTHOR_EMAIL="$ORIG_GIT_AUTHOR_EMAIL" &&
+ export GIT_AUTHOR_EMAIL
+}
+test -z "$ORIG_GIT_AUTHOR_DATE" || {
+ GIT_AUTHOR_DATE="$ORIG_GIT_AUTHOR_DATE" &&
+ export GIT_AUTHOR_DATE
+}
+test -z "$ORIG_GIT_COMMITTER_NAME" || {
+ GIT_COMMITTER_NAME="$ORIG_GIT_COMMITTER_NAME" &&
+ export GIT_COMMITTER_NAME
+}
+test -z "$ORIG_GIT_COMMITTER_EMAIL" || {
+ GIT_COMMITTER_EMAIL="$ORIG_GIT_COMMITTER_EMAIL" &&
+ export GIT_COMMITTER_EMAIL
+}
+test -z "$ORIG_GIT_COMMITTER_DATE" || {
+ GIT_COMMITTER_DATE="$ORIG_GIT_COMMITTER_DATE" &&
+ export GIT_COMMITTER_DATE
+}
+
+if test -n "$state_branch"
+then
+ echo "Saving rewrite state to $state_branch" 1>&2
+ state_blob=$(
+ perl -e'opendir D, "../map" or die;
+ open H, "|-", "git hash-object -w --stdin" or die;
+ foreach (sort readdir(D)) {
+ next if m/^\.\.?$/;
+ open F, "<../map/$_" or die;
+ chomp($f = <F>);
+ print H "$_:$f\n" or die;
+ }
+ close(H) or die;' || die "Unable to save state")
+ state_tree=$(/bin/echo -e "100644 blob $state_blob\tfilter.map" | git mktree)
+ if test -n "$state_commit"
+ then
+ state_commit=$(/bin/echo "Sync" | git commit-tree "$state_tree" -p "$state_commit")
+ else
+ state_commit=$(/bin/echo "Sync" | git commit-tree "$state_tree" )
+ fi
+ git update-ref "$state_branch" "$state_commit"
+fi
+
+cd "$orig_dir"
+rm -rf "$tempdir"
+
+trap - 0
if [ "$(is_bare_repository)" = false ]; then
git read-tree -u -m HEAD || exit
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 29b7e8824b..2563dc52da 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -155,13 +155,13 @@ reschedule_last_action () {
append_todo_help () {
gettext "
Commands:
- p, pick = use commit
- r, reword = use commit, but edit the commit message
- e, edit = use commit, but stop for amending
- s, squash = use commit, but meld into previous commit
- f, fixup = like \"squash\", but discard this commit's log message
- x, exec = run command (the rest of the line) using shell
- d, drop = remove commit
+p, pick = use commit
+r, reword = use commit, but edit the commit message
+e, edit = use commit, but stop for amending
+s, squash = use commit, but meld into previous commit
+f, fixup = like \"squash\", but discard this commit's log message
+x, exec = run command (the rest of the line) using shell
+d, drop = remove commit
These lines can be re-ordered; they are executed from top to bottom.
" | git stripspace --comment-lines >>"$todo"
@@ -714,154 +714,12 @@ do_rest () {
done
}
-# skip picking commits whose parents are unchanged
-skip_unnecessary_picks () {
- fd=3
- while read -r command rest
- do
- # fd=3 means we skip the command
- case "$fd,$command" in
- 3,pick|3,p)
- # pick a commit whose parent is current $onto -> skip
- sha1=${rest%% *}
- case "$(git rev-parse --verify --quiet "$sha1"^)" in
- "$onto"*)
- onto=$sha1
- ;;
- *)
- fd=1
- ;;
- esac
- ;;
- 3,"$comment_char"*|3,)
- # copy comments
- ;;
- *)
- fd=1
- ;;
- esac
- printf '%s\n' "$command${rest:+ }$rest" >&$fd
- done <"$todo" >"$todo.new" 3>>"$done" &&
- mv -f "$todo".new "$todo" &&
- case "$(peek_next_command)" in
- squash|s|fixup|f)
- record_in_rewritten "$onto"
- ;;
- esac ||
- die "$(gettext "Could not skip unnecessary pick commands")"
-}
-
-transform_todo_ids () {
- while read -r command rest
- do
- case "$command" in
- "$comment_char"* | exec)
- # Be careful for oddball commands like 'exec'
- # that do not have a SHA-1 at the beginning of $rest.
- ;;
- *)
- sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) &&
- rest="$sha1 ${rest#*[ ]}"
- ;;
- esac
- printf '%s\n' "$command${rest:+ }$rest"
- done <"$todo" >"$todo.new" &&
- mv -f "$todo.new" "$todo"
-}
-
expand_todo_ids() {
- transform_todo_ids
+ git rebase--helper --expand-ids
}
collapse_todo_ids() {
- transform_todo_ids --short
-}
-
-# Rearrange the todo list that has both "pick sha1 msg" and
-# "pick sha1 fixup!/squash! msg" appears in it so that the latter
-# comes immediately after the former, and change "pick" to
-# "fixup"/"squash".
-#
-# Note that if the config has specified a custom instruction format
-# each log message will be re-retrieved in order to normalize the
-# autosquash arrangement
-rearrange_squash () {
- # extract fixup!/squash! lines and resolve any referenced sha1's
- while read -r pick sha1 message
- do
- test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
- case "$message" in
- "squash! "*|"fixup! "*)
- action="${message%%!*}"
- rest=$message
- prefix=
- # skip all squash! or fixup! (but save for later)
- while :
- do
- case "$rest" in
- "squash! "*|"fixup! "*)
- prefix="$prefix${rest%%!*},"
- rest="${rest#*! }"
- ;;
- *)
- break
- ;;
- esac
- done
- printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest"
- # if it's a single word, try to resolve to a full sha1 and
- # emit a second copy. This allows us to match on both message
- # and on sha1 prefix
- if test "${rest#* }" = "$rest"; then
- fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)"
- if test -n "$fullsha"; then
- # prefix the action to uniquely identify this line as
- # intended for full sha1 match
- echo "$sha1 +$action $prefix $fullsha"
- fi
- fi
- esac
- done >"$1.sq" <"$1"
- test -s "$1.sq" || return
-
- used=
- while read -r pick sha1 message
- do
- case " $used" in
- *" $sha1 "*) continue ;;
- esac
- printf '%s\n' "$pick $sha1 $message"
- test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
- used="$used$sha1 "
- while read -r squash action msg_prefix msg_content
- do
- case " $used" in
- *" $squash "*) continue ;;
- esac
- emit=0
- case "$action" in
- +*)
- action="${action#+}"
- # full sha1 prefix test
- case "$msg_content" in "$sha1"*) emit=1;; esac ;;
- *)
- # message prefix test
- case "$message" in "$msg_content"*) emit=1;; esac ;;
- esac
- if test $emit = 1; then
- if test -n "${format}"
- then
- msg_content=$(git log -n 1 --format="${format}" ${squash})
- else
- msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content"
- fi
- printf '%s\n' "$action $squash $msg_content"
- used="$used$squash "
- fi
- done <"$1.sq"
- done >"$1.rearranged" <"$1"
- cat "$1.rearranged" >"$1"
- rm -f "$1.sq" "$1.rearranged"
+ git rebase--helper --shorten-ids
}
# Add commands after a pick or after a squash/fixup serie
@@ -885,96 +743,6 @@ add_exec_commands () {
mv "$1.new" "$1"
}
-# Check if the SHA-1 passed as an argument is a
-# correct one, if not then print $2 in "$todo".badsha
-# $1: the SHA-1 to test
-# $2: the line number of the input
-# $3: the input filename
-check_commit_sha () {
- badsha=0
- if test -z "$1"
- then
- badsha=1
- else
- sha1_verif="$(git rev-parse --verify --quiet $1^{commit})"
- if test -z "$sha1_verif"
- then
- badsha=1
- fi
- fi
-
- if test $badsha -ne 0
- then
- line="$(sed -n -e "${2}p" "$3")"
- warn "$(eval_gettext "\
-Warning: the SHA-1 is missing or isn't a commit in the following line:
- - \$line")"
- warn
- fi
-
- return $badsha
-}
-
-# prints the bad commits and bad commands
-# from the todolist in stdin
-check_bad_cmd_and_sha () {
- retval=0
- lineno=0
- while read -r command rest
- do
- lineno=$(( $lineno + 1 ))
- case $command in
- "$comment_char"*|''|noop|x|exec)
- # Doesn't expect a SHA-1
- ;;
- "$cr")
- # Work around CR left by "read" (e.g. with Git for
- # Windows' Bash).
- ;;
- pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
- if ! check_commit_sha "${rest%%[ ]*}" "$lineno" "$1"
- then
- retval=1
- fi
- ;;
- *)
- line="$(sed -n -e "${lineno}p" "$1")"
- warn "$(eval_gettext "\
-Warning: the command isn't recognized in the following line:
- - \$line")"
- warn
- retval=1
- ;;
- esac
- done <"$1"
- return $retval
-}
-
-# Print the list of the SHA-1 of the commits
-# from stdin to stdout
-todo_list_to_sha_list () {
- git stripspace --strip-comments |
- while read -r command sha1 rest
- do
- case $command in
- "$comment_char"*|''|noop|x|"exec")
- ;;
- *)
- long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null)
- printf "%s\n" "$long_sha"
- ;;
- esac
- done
-}
-
-# Use warn for each line in stdin
-warn_lines () {
- while read -r line
- do
- warn " - $line"
- done
-}
-
# Switch to the branch in $into and notify it in the reflog
checkout_onto () {
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
@@ -989,74 +757,6 @@ get_missing_commit_check_level () {
printf '%s' "$check_level" | tr 'A-Z' 'a-z'
}
-# Check if the user dropped some commits by mistake
-# Behaviour determined by rebase.missingCommitsCheck.
-# Check if there is an unrecognized command or a
-# bad SHA-1 in a command.
-check_todo_list () {
- raise_error=f
-
- check_level=$(get_missing_commit_check_level)
-
- case "$check_level" in
- warn|error)
- # Get the SHA-1 of the commits
- todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1
- todo_list_to_sha_list <"$todo" >"$todo".newsha1
-
- # Sort the SHA-1 and compare them
- sort -u "$todo".oldsha1 >"$todo".oldsha1+
- mv "$todo".oldsha1+ "$todo".oldsha1
- sort -u "$todo".newsha1 >"$todo".newsha1+
- mv "$todo".newsha1+ "$todo".newsha1
- comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss
-
- # Warn about missing commits
- if test -s "$todo".miss
- then
- test "$check_level" = error && raise_error=t
-
- warn "$(gettext "\
-Warning: some commits may have been dropped accidentally.
-Dropped commits (newer to older):")"
-
- # Make the list user-friendly and display
- opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
- git rev-list $opt <"$todo".miss | warn_lines
-
- warn "$(gettext "\
-To avoid this message, use \"drop\" to explicitly remove a commit.
-
-Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
-The possible behaviours are: ignore, warn, error.")"
- warn
- fi
- ;;
- ignore)
- ;;
- *)
- warn "$(eval_gettext "Unrecognized setting \$check_level for option rebase.missingCommitsCheck. Ignoring.")"
- ;;
- esac
-
- if ! check_bad_cmd_and_sha "$todo"
- then
- raise_error=t
- fi
-
- if test $raise_error = t
- then
- # Checkout before the first commit of the
- # rebase: this way git rebase --continue
- # will work correctly as it expects HEAD to be
- # placed before the commit of the next action
- checkout_onto
-
- warn "$(gettext "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.")"
- die "$(gettext "Or you can abort the rebase with 'git rebase --abort'.")"
- fi
-}
-
# The whole contents of this file is run by dot-sourcing it from
# inside a shell function. It used to be that "return"s we see
# below were not inside any function, and expected to return
@@ -1211,26 +911,27 @@ else
revisions=$onto...$orig_head
shortrevisions=$shorthead
fi
-format=$(git config --get rebase.instructionFormat)
-# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
-git rev-list $merges_option --format="%m%H ${format:-%s}" \
- --reverse --left-right --topo-order \
- $revisions ${restrict_revision+^$restrict_revision} | \
- sed -n "s/^>//p" |
-while read -r sha1 rest
-do
-
- if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
- then
- comment_out="$comment_char "
- else
- comment_out=
- fi
+if test t != "$preserve_merges"
+then
+ git rebase--helper --make-script ${keep_empty:+--keep-empty} \
+ $revisions ${restrict_revision+^$restrict_revision} >"$todo"
+else
+ format=$(git config --get rebase.instructionFormat)
+ # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
+ git rev-list $merges_option --format="%m%H ${format:-%s}" \
+ --reverse --left-right --topo-order \
+ $revisions ${restrict_revision+^$restrict_revision} | \
+ sed -n "s/^>//p" |
+ while read -r sha1 rest
+ do
+
+ if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
+ then
+ comment_out="$comment_char "
+ else
+ comment_out=
+ fi
- if test t != "$preserve_merges"
- then
- printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
- else
if test -z "$rebase_root"
then
preserve=t
@@ -1249,8 +950,8 @@ do
touch "$rewritten"/$sha1
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
fi
- fi
-done
+ done
+fi
# Watch for commits that been dropped by --cherry-pick
if test t = "$preserve_merges"
@@ -1280,7 +981,7 @@ then
fi
test -s "$todo" || echo noop >> "$todo"
-test -n "$autosquash" && rearrange_squash "$todo"
+test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
test -n "$cmd" && add_exec_commands "$todo"
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
@@ -1316,11 +1017,17 @@ git_sequence_editor "$todo" ||
has_action "$todo" ||
return 2
-check_todo_list
+git rebase--helper --check-todo-list || {
+ ret=$?
+ checkout_onto
+ exit $ret
+}
expand_todo_ids
-test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
+test -d "$rewritten" || test -n "$force_rebase" ||
+onto="$(git rebase--helper --skip-unnecessary-picks)" ||
+die "Could not skip unnecessary pick commands"
checkout_onto
if test -z "$rebase_root" && test ! -d "$rewritten"
diff --git a/git-rebase.sh b/git-rebase.sh
index ad8415e3cf..6344e8d5e3 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -350,6 +350,9 @@ do
shift
break
;;
+ *)
+ usage
+ ;;
esac
shift
done
diff --git a/git-request-pull.sh b/git-request-pull.sh
index eebd33276d..13c172bd94 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -128,7 +128,7 @@ git show -s --format='The following changes since commit %H:
%s (%ci)
-are available in the git repository at:
+are available in the Git repository at:
' $merge_base &&
echo " $url $pretty_remote" &&
git show -s --format='
diff --git a/git.c b/git.c
index f31dca6962..9e96dd4090 100644
--- a/git.c
+++ b/git.c
@@ -182,6 +182,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
setenv(GIT_ICASE_PATHSPECS_ENVIRONMENT, "1", 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--no-optional-locks")) {
+ setenv(GIT_OPTIONAL_LOCKS_ENVIRONMENT, "0", 1);
+ if (envchanged)
+ *envchanged = 1;
} else if (!strcmp(cmd, "--shallow-file")) {
(*argv)++;
(*argc)--;
diff --git a/graph.c b/graph.c
index e7e20650da..e1f6d3bddb 100644
--- a/graph.c
+++ b/graph.c
@@ -696,12 +696,8 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
* This way, fields printed to the right of the graph will remain
* aligned for the entire commit.
*/
- int extra;
- if (chars_written >= graph->width)
- return;
-
- extra = graph->width - chars_written;
- strbuf_addf(sb, "%*s", (int) extra, "");
+ if (chars_written < graph->width)
+ strbuf_addchars(sb, ' ', graph->width - chars_written);
}
static void graph_output_padding_line(struct git_graph *graph,
@@ -787,7 +783,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
if (col->commit == graph->commit) {
seen_this = 1;
strbuf_write_column(sb, col, '|');
- strbuf_addf(sb, "%*s", graph->expansion_row, "");
+ strbuf_addchars(sb, ' ', graph->expansion_row);
chars_written += 1 + graph->expansion_row;
} else if (seen_this && (graph->expansion_row == 0)) {
/*
diff --git a/http-backend.c b/http-backend.c
index e51c7805c8..f3dc218b2a 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -486,10 +486,9 @@ static int show_head_ref(const char *refname, const struct object_id *oid,
struct strbuf *buf = cb_data;
if (flag & REF_ISSYMREF) {
- struct object_id unused;
const char *target = resolve_ref_unsafe(refname,
RESOLVE_REF_READING,
- unused.hash, NULL);
+ NULL, NULL);
if (target)
strbuf_addf(buf, "ref: %s\n", strip_namespace(target));
diff --git a/http-push.c b/http-push.c
index e4c9b065ce..493ee7d719 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1018,7 +1018,7 @@ static int get_oid_hex_from_objpath(const char *path, struct object_id *oid)
memcpy(hex, path, 2);
path += 2;
path++; /* skip '/' */
- memcpy(hex, path, GIT_SHA1_HEXSZ - 2);
+ memcpy(hex + 2, path, GIT_SHA1_HEXSZ - 2);
return get_oid_hex(hex, oid);
}
@@ -1523,6 +1523,7 @@ static int remote_exists(const char *path)
break;
case HTTP_ERROR:
error("unable to access '%s': %s", url, curl_errorstr);
+ /* fallthrough */
default:
ret = -1;
}
diff --git a/http.c b/http.c
index 9e40a465fd..713525f38e 100644
--- a/http.c
+++ b/http.c
@@ -638,9 +638,7 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size,
switch (type) {
case CURLINFO_TEXT:
trace_printf_key(&trace_curl, "== Info: %s", data);
- default: /* we ignore unknown types by default */
- return 0;
-
+ break;
case CURLINFO_HEADER_OUT:
text = "=> Send header";
curl_dump_header(text, (unsigned char *)data, size, DO_FILTER);
@@ -665,6 +663,9 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size,
text = "<= Recv SSL data";
curl_dump_data(text, (unsigned char *)data, size);
break;
+
+ default: /* we ignore unknown types by default */
+ return 0;
}
return 0;
}
diff --git a/line-log.c b/line-log.c
index ab0709f9ae..545ad0f28b 100644
--- a/line-log.c
+++ b/line-log.c
@@ -90,7 +90,7 @@ static int range_cmp(const void *_r, const void *_s)
*/
static void range_set_check_invariants(struct range_set *rs)
{
- int i;
+ unsigned int i;
if (!rs)
return;
@@ -110,8 +110,8 @@ static void range_set_check_invariants(struct range_set *rs)
*/
void sort_and_merge_range_set(struct range_set *rs)
{
- int i;
- int o = 0; /* output cursor */
+ unsigned int i;
+ unsigned int o = 0; /* output cursor */
QSORT(rs->ranges, rs->nr, range_cmp);
@@ -144,7 +144,7 @@ void sort_and_merge_range_set(struct range_set *rs)
static void range_set_union(struct range_set *out,
struct range_set *a, struct range_set *b)
{
- int i = 0, j = 0;
+ unsigned int i = 0, j = 0;
struct range *ra = a->ranges;
struct range *rb = b->ranges;
/* cannot make an alias of out->ranges: it may change during grow */
@@ -186,7 +186,7 @@ static void range_set_union(struct range_set *out,
static void range_set_difference(struct range_set *out,
struct range_set *a, struct range_set *b)
{
- int i, j = 0;
+ unsigned int i, j = 0;
for (i = 0; i < a->nr; i++) {
long start = a->ranges[i].start;
long end = a->ranges[i].end;
@@ -397,7 +397,7 @@ static void diff_ranges_filter_touched(struct diff_ranges *out,
struct diff_ranges *diff,
struct range_set *rs)
{
- int i, j = 0;
+ unsigned int i, j = 0;
assert(out->target.nr == 0);
@@ -426,7 +426,7 @@ static void range_set_shift_diff(struct range_set *out,
struct range_set *rs,
struct diff_ranges *diff)
{
- int i, j = 0;
+ unsigned int i, j = 0;
long offset = 0;
struct range *src = rs->ranges;
struct range *target = diff->target.ranges;
@@ -873,7 +873,7 @@ static char *output_prefix(struct diff_options *opt)
static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
{
- int i, j = 0;
+ unsigned int i, j = 0;
long p_lines, t_lines;
unsigned long *p_ends = NULL, *t_ends = NULL;
struct diff_filepair *pair = range->pair;
@@ -906,7 +906,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
long t_start = range->ranges.ranges[i].start;
long t_end = range->ranges.ranges[i].end;
long t_cur = t_start;
- int j_last;
+ unsigned int j_last;
while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
j++;
diff --git a/line-log.h b/line-log.h
index 7a5c24e2df..e2a5ee7c6d 100644
--- a/line-log.h
+++ b/line-log.h
@@ -14,7 +14,7 @@ struct range {
/* A set of ranges. The ranges must always be disjoint and sorted. */
struct range_set {
- int alloc, nr;
+ unsigned int alloc, nr;
struct range *ranges;
};
diff --git a/log-tree.c b/log-tree.c
index 410ab4f02d..cea056234d 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -185,7 +185,6 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d
{
const struct name_decoration *list, *head = NULL;
const char *branch_name = NULL;
- struct object_id unused;
int rru_flags;
/* First find HEAD */
@@ -198,7 +197,7 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d
return NULL;
/* Now resolve and find the matching current branch */
- branch_name = resolve_ref_unsafe("HEAD", 0, unused.hash, &rru_flags);
+ branch_name = resolve_ref_unsafe("HEAD", 0, NULL, &rru_flags);
if (!(rru_flags & REF_ISSYMREF))
return NULL;
diff --git a/mailinfo.c b/mailinfo.c
index f2387a3267..a89db22ab0 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -367,11 +367,16 @@ static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
while ((c = *in++) != 0) {
if (c == '=') {
- int d = *in++;
+ int ch, d = *in;
if (d == '\n' || !d)
break; /* drop trailing newline */
- strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
- continue;
+ ch = hex2chr(in);
+ if (ch >= 0) {
+ strbuf_addch(out, ch);
+ in += 2;
+ continue;
+ }
+ /* garbage -- fall through */
}
if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
c = 0x20;
@@ -822,6 +827,7 @@ static void handle_filter(struct mailinfo *mi, struct strbuf *line)
if (!handle_commit_msg(mi, line))
break;
mi->filter_stage++;
+ /* fallthrough */
case 1:
handle_patch(mi, line);
break;
diff --git a/notes-merge.c b/notes-merge.c
index 597d43f65c..4352c34a6e 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -308,8 +308,6 @@ static void write_buf_to_worktree(const struct object_id *obj,
if (errno == EPIPE)
break;
die_errno("notes-merge");
- } else if (!ret) {
- die("notes-merge: disk full?");
}
size -= ret;
buf += ret;
diff --git a/object.c b/object.c
index 321d7e9201..b9a4a0e501 100644
--- a/object.c
+++ b/object.c
@@ -353,6 +353,19 @@ static void object_array_release_entry(struct object_array_entry *ent)
free(ent->path);
}
+struct object *object_array_pop(struct object_array *array)
+{
+ struct object *ret;
+
+ if (!array->nr)
+ return NULL;
+
+ ret = array->objects[array->nr - 1].item;
+ object_array_release_entry(&array->objects[array->nr - 1]);
+ array->nr--;
+ return ret;
+}
+
void object_array_filter(struct object_array *array,
object_array_each_func_t want, void *cb_data)
{
diff --git a/object.h b/object.h
index 0a419ba8da..df8abe96f7 100644
--- a/object.h
+++ b/object.h
@@ -116,6 +116,14 @@ int object_list_contains(struct object_list *list, struct object *obj);
void add_object_array(struct object *obj, const char *name, struct object_array *array);
void add_object_array_with_path(struct object *obj, const char *name, struct object_array *array, unsigned mode, const char *path);
+/*
+ * Returns NULL if the array is empty. Otherwise, returns the last object
+ * after removing its entry from the array. Other resources associated
+ * with that object are left in an unspecified state and should not be
+ * examined.
+ */
+struct object *object_array_pop(struct object_array *array);
+
typedef int (*object_array_each_func_t)(struct object_array_entry *, void *);
/*
diff --git a/oidmap.c b/oidmap.c
new file mode 100644
index 0000000000..6db4fffcdb
--- /dev/null
+++ b/oidmap.c
@@ -0,0 +1,51 @@
+#include "cache.h"
+#include "oidmap.h"
+
+static int cmpfn(const void *hashmap_cmp_fn_data,
+ const void *entry, const void *entry_or_key,
+ const void *keydata)
+{
+ const struct oidmap_entry *entry_ = entry;
+ if (keydata)
+ return oidcmp(&entry_->oid, (const struct object_id *) keydata);
+ return oidcmp(&entry_->oid,
+ &((const struct oidmap_entry *) entry_or_key)->oid);
+}
+
+static int hash(const struct object_id *oid)
+{
+ int hash;
+ memcpy(&hash, oid->hash, sizeof(hash));
+ return hash;
+}
+
+void oidmap_init(struct oidmap *map, size_t initial_size)
+{
+ hashmap_init(&map->map, cmpfn, NULL, initial_size);
+}
+
+void oidmap_free(struct oidmap *map, int free_entries)
+{
+ if (!map)
+ return;
+ hashmap_free(&map->map, free_entries);
+}
+
+void *oidmap_get(const struct oidmap *map, const struct object_id *key)
+{
+ return hashmap_get_from_hash(&map->map, hash(key), key);
+}
+
+void *oidmap_remove(struct oidmap *map, const struct object_id *key)
+{
+ struct hashmap_entry entry;
+ hashmap_entry_init(&entry, hash(key));
+ return hashmap_remove(&map->map, &entry, key);
+}
+
+void *oidmap_put(struct oidmap *map, void *entry)
+{
+ struct oidmap_entry *to_put = entry;
+ hashmap_entry_init(&to_put->internal_entry, hash(&to_put->oid));
+ return hashmap_put(&map->map, to_put);
+}
diff --git a/oidmap.h b/oidmap.h
new file mode 100644
index 0000000000..18f54cde14
--- /dev/null
+++ b/oidmap.h
@@ -0,0 +1,68 @@
+#ifndef OIDMAP_H
+#define OIDMAP_H
+
+#include "hashmap.h"
+
+/*
+ * struct oidmap_entry is a structure representing an entry in the hash table,
+ * which must be used as first member of user data structures.
+ *
+ * Users should set the oid field. oidmap_put() will populate the
+ * internal_entry field.
+ */
+struct oidmap_entry {
+ /* For internal use only */
+ struct hashmap_entry internal_entry;
+
+ struct object_id oid;
+};
+
+struct oidmap {
+ struct hashmap map;
+};
+
+#define OIDMAP_INIT { { NULL } }
+
+/*
+ * Initializes an oidmap structure.
+ *
+ * `map` is the oidmap to initialize.
+ *
+ * If the total number of entries is known in advance, the `initial_size`
+ * parameter may be used to preallocate a sufficiently large table and thus
+ * prevent expensive resizing. If 0, the table is dynamically resized.
+ */
+extern void oidmap_init(struct oidmap *map, size_t initial_size);
+
+/*
+ * Frees an oidmap structure and allocated memory.
+ *
+ * If `free_entries` is true, each oidmap_entry in the map is freed as well
+ * using stdlibs free().
+ */
+extern void oidmap_free(struct oidmap *map, int free_entries);
+
+/*
+ * Returns the oidmap entry for the specified oid, or NULL if not found.
+ */
+extern void *oidmap_get(const struct oidmap *map,
+ const struct object_id *key);
+
+/*
+ * Adds or replaces an oidmap entry.
+ *
+ * ((struct oidmap_entry *) entry)->internal_entry will be populated by this
+ * function.
+ *
+ * Returns the replaced entry, or NULL if not found (i.e. the entry was added).
+ */
+extern void *oidmap_put(struct oidmap *map, void *entry);
+
+/*
+ * Removes an oidmap entry matching the specified oid.
+ *
+ * Returns the removed entry, or NULL if not found.
+ */
+extern void *oidmap_remove(struct oidmap *map, const struct object_id *key);
+
+#endif
diff --git a/oidset.c b/oidset.c
index a6a08ba52a..f1f874aaad 100644
--- a/oidset.c
+++ b/oidset.c
@@ -1,50 +1,30 @@
#include "cache.h"
#include "oidset.h"
-struct oidset_entry {
- struct hashmap_entry hash;
- struct object_id oid;
-};
-
-static int oidset_hashcmp(const void *unused_cmp_data,
- const void *va, const void *vb,
- const void *vkey)
-{
- const struct oidset_entry *a = va, *b = vb;
- const struct object_id *key = vkey;
- return oidcmp(&a->oid, key ? key : &b->oid);
-}
-
int oidset_contains(const struct oidset *set, const struct object_id *oid)
{
- struct hashmap_entry key;
-
- if (!set->map.cmpfn)
+ if (!set->map.map.tablesize)
return 0;
-
- hashmap_entry_init(&key, sha1hash(oid->hash));
- return !!hashmap_get(&set->map, &key, oid);
+ return !!oidmap_get(&set->map, oid);
}
int oidset_insert(struct oidset *set, const struct object_id *oid)
{
- struct oidset_entry *entry;
-
- if (!set->map.cmpfn)
- hashmap_init(&set->map, oidset_hashcmp, NULL, 0);
+ struct oidmap_entry *entry;
- if (oidset_contains(set, oid))
+ if (!set->map.map.tablesize)
+ oidmap_init(&set->map, 0);
+ else if (oidset_contains(set, oid))
return 1;
entry = xmalloc(sizeof(*entry));
- hashmap_entry_init(&entry->hash, sha1hash(oid->hash));
oidcpy(&entry->oid, oid);
- hashmap_add(&set->map, entry);
+ oidmap_put(&set->map, entry);
return 0;
}
void oidset_clear(struct oidset *set)
{
- hashmap_free(&set->map, 1);
+ oidmap_free(&set->map, 1);
}
diff --git a/oidset.h b/oidset.h
index b7eaab5b88..f4c9e0f9c0 100644
--- a/oidset.h
+++ b/oidset.h
@@ -1,6 +1,8 @@
#ifndef OIDSET_H
#define OIDSET_H
+#include "oidmap.h"
+
/**
* This API is similar to sha1-array, in that it maintains a set of object ids
* in a memory-efficient way. The major differences are:
@@ -17,10 +19,10 @@
* A single oidset; should be zero-initialized (or use OIDSET_INIT).
*/
struct oidset {
- struct hashmap map;
+ struct oidmap map;
};
-#define OIDSET_INIT { { NULL } }
+#define OIDSET_INIT { OIDMAP_INIT }
/**
* Returns true iff `set` contains `oid`.
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index 8e47a96b3b..a8df5ce2ab 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -297,9 +297,7 @@ void bitmap_writer_build(struct packing_data *to_pack)
traverse_commit_list(&revs, show_commit, show_object, base);
- revs.pending.nr = 0;
- revs.pending.alloc = 0;
- revs.pending.objects = NULL;
+ object_array_clear(&revs.pending);
stored->bitmap = bitmap_to_ewah(base);
need_reset = 0;
diff --git a/pack-bitmap.c b/pack-bitmap.c
index cb3d14ba45..42e3d5f4f2 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -654,8 +654,6 @@ static int in_bitmapped_pack(struct object_list *roots)
int prepare_bitmap_walk(struct rev_info *revs)
{
unsigned int i;
- unsigned int pending_nr = revs->pending.nr;
- struct object_array_entry *pending_e = revs->pending.objects;
struct object_list *wants = NULL;
struct object_list *haves = NULL;
@@ -670,8 +668,8 @@ int prepare_bitmap_walk(struct rev_info *revs)
return -1;
}
- for (i = 0; i < pending_nr; ++i) {
- struct object *object = pending_e[i].item;
+ for (i = 0; i < revs->pending.nr; ++i) {
+ struct object *object = revs->pending.objects[i].item;
if (object->type == OBJ_NONE)
parse_object_or_die(&object->oid, NULL);
@@ -715,9 +713,7 @@ int prepare_bitmap_walk(struct rev_info *revs)
if (!bitmap_git.loaded && load_pack_bitmap() < 0)
return -1;
- revs->pending.nr = 0;
- revs->pending.alloc = 0;
- revs->pending.objects = NULL;
+ object_array_clear(&revs->pending);
if (haves) {
revs->ignore_missing_links = 1;
diff --git a/pack-write.c b/pack-write.c
index a333ec6754..fea6284192 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -213,14 +213,19 @@ void fixup_pack_header_footer(int pack_fd,
git_SHA_CTX old_sha1_ctx, new_sha1_ctx;
struct pack_header hdr;
char *buf;
+ ssize_t read_result;
git_SHA1_Init(&old_sha1_ctx);
git_SHA1_Init(&new_sha1_ctx);
if (lseek(pack_fd, 0, SEEK_SET) != 0)
die_errno("Failed seeking to start of '%s'", pack_name);
- if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+ read_result = read_in_full(pack_fd, &hdr, sizeof(hdr));
+ if (read_result < 0)
die_errno("Unable to reread header of '%s'", pack_name);
+ else if (read_result != sizeof(hdr))
+ die_errno("Unexpected short read for header of '%s'",
+ pack_name);
if (lseek(pack_fd, 0, SEEK_SET) != 0)
die_errno("Failed seeking to start of '%s'", pack_name);
git_SHA1_Update(&old_sha1_ctx, &hdr, sizeof(hdr));
diff --git a/packfile.c b/packfile.c
index f69a5c8d60..eab7542487 100644
--- a/packfile.c
+++ b/packfile.c
@@ -442,6 +442,7 @@ static int open_packed_git_1(struct packed_git *p)
unsigned char sha1[20];
unsigned char *idx_sha1;
long fd_flag;
+ ssize_t read_result;
if (!p->index_data && open_pack_index(p))
return error("packfile %s index unavailable", p->pack_name);
@@ -483,7 +484,10 @@ static int open_packed_git_1(struct packed_git *p)
return error("cannot set FD_CLOEXEC");
/* Verify we recognize this pack file format. */
- if (read_in_full(p->pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+ read_result = read_in_full(p->pack_fd, &hdr, sizeof(hdr));
+ if (read_result < 0)
+ return error_errno("error reading from %s", p->pack_name);
+ if (read_result != sizeof(hdr))
return error("file %s is far too short to be a packfile", p->pack_name);
if (hdr.hdr_signature != htonl(PACK_SIGNATURE))
return error("file %s is not a GIT packfile", p->pack_name);
@@ -500,7 +504,10 @@ static int open_packed_git_1(struct packed_git *p)
p->num_objects);
if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1)
return error("end of packfile %s is unavailable", p->pack_name);
- if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1))
+ read_result = read_in_full(p->pack_fd, sha1, sizeof(sha1));
+ if (read_result < 0)
+ return error_errno("error reading from %s", p->pack_name);
+ if (read_result != sizeof(sha1))
return error("packfile %s signature is unavailable", p->pack_name);
idx_sha1 = ((unsigned char *)p->index_data) + p->index_size - 40;
if (hashcmp(sha1, idx_sha1))
diff --git a/parse-options.c b/parse-options.c
index 0dd9fc6a0d..fca7159646 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -581,6 +581,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
const struct option *opts, int full, int err)
{
FILE *outfile = err ? stderr : stdout;
+ int need_newline;
if (!usagestr)
return PARSE_OPT_HELP;
@@ -599,12 +600,11 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (**usagestr)
fprintf_ln(outfile, _(" %s"), _(*usagestr));
else
- putchar('\n');
+ fputc('\n', outfile);
usagestr++;
}
- if (opts->type != OPTION_GROUP)
- fputc('\n', outfile);
+ need_newline = 1;
for (; opts->type != OPTION_END; opts++) {
size_t pos;
@@ -612,6 +612,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
+ need_newline = 0;
if (*opts->help)
fprintf(outfile, "%s\n", _(opts->help));
continue;
@@ -619,6 +620,11 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;
+ if (need_newline) {
+ fputc('\n', outfile);
+ need_newline = 0;
+ }
+
pos = fprintf(outfile, " ");
if (opts->short_name) {
if (opts->flags & PARSE_OPT_NODASH)
diff --git a/path.c b/path.c
index b533ec938d..2e09a7bce0 100644
--- a/path.c
+++ b/path.c
@@ -34,11 +34,10 @@ static struct strbuf *get_pathname(void)
return sb;
}
-static char *cleanup_path(char *path)
+static const char *cleanup_path(const char *path)
{
/* Clean it up */
- if (!memcmp(path, "./", 2)) {
- path += 2;
+ if (skip_prefix(path, "./", &path)) {
while (*path == '/')
path++;
}
@@ -47,7 +46,7 @@ static char *cleanup_path(char *path)
static void strbuf_cleanup_path(struct strbuf *sb)
{
- char *path = cleanup_path(sb->buf);
+ const char *path = cleanup_path(sb->buf);
if (path > sb->buf)
strbuf_remove(sb, 0, path - sb->buf);
}
@@ -64,7 +63,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
strlcpy(buf, bad_path, n);
return buf;
}
- return cleanup_path(buf);
+ return (char *)cleanup_path(buf);
}
static int dir_prefix(const char *buf, const char *dir)
@@ -637,8 +636,9 @@ void strbuf_git_common_path(struct strbuf *sb,
int validate_headref(const char *path)
{
struct stat st;
- char *buf, buffer[256];
- unsigned char sha1[20];
+ char buffer[256];
+ const char *refname;
+ struct object_id oid;
int fd;
ssize_t len;
@@ -662,24 +662,24 @@ int validate_headref(const char *path)
len = read_in_full(fd, buffer, sizeof(buffer)-1);
close(fd);
+ if (len < 0)
+ return -1;
+ buffer[len] = '\0';
+
/*
* Is it a symbolic ref?
*/
- if (len < 4)
- return -1;
- if (!memcmp("ref:", buffer, 4)) {
- buf = buffer + 4;
- len -= 4;
- while (len && isspace(*buf))
- buf++, len--;
- if (len >= 5 && !memcmp("refs/", buf, 5))
+ if (skip_prefix(buffer, "ref:", &refname)) {
+ while (isspace(*refname))
+ refname++;
+ if (starts_with(refname, "refs/"))
return 0;
}
/*
* Is this a detached HEAD?
*/
- if (!get_sha1_hex(buffer, sha1))
+ if (!get_oid_hex(buffer, &oid))
return 0;
return -1;
@@ -717,7 +717,7 @@ char *expand_user_path(const char *path, int real_home)
if (!home)
goto return_null;
if (real_home)
- strbuf_addstr(&user_path, real_path(home));
+ strbuf_add_real_path(&user_path, home);
else
strbuf_addstr(&user_path, home);
#ifdef GIT_WINDOWS_NATIVE
diff --git a/pkt-line.c b/pkt-line.c
index 647bbd3bce..93ea311443 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -258,7 +258,7 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
}
/* And complain if we didn't get enough bytes to satisfy the read. */
- if (ret < size) {
+ if (ret != size) {
if (options & PACKET_READ_GENTLE_ON_EOF)
return -1;
diff --git a/pretty.c b/pretty.c
index 94eab5c89e..2f6b0ae6c1 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1056,6 +1056,24 @@ static size_t parse_padding_placeholder(struct strbuf *sb,
return 0;
}
+static int match_placeholder_arg(const char *to_parse, const char *candidate,
+ const char **end)
+{
+ const char *p;
+
+ if (!(skip_prefix(to_parse, candidate, &p)))
+ return 0;
+ if (*p == ',') {
+ *end = p + 1;
+ return 1;
+ }
+ if (*p == ')') {
+ *end = p;
+ return 1;
+ }
+ return 0;
+}
+
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const char *placeholder,
void *context)
@@ -1285,11 +1303,16 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (skip_prefix(placeholder, "(trailers", &arg)) {
struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
- while (*arg == ':') {
- if (skip_prefix(arg, ":only", &arg))
- opts.only_trailers = 1;
- else if (skip_prefix(arg, ":unfold", &arg))
- opts.unfold = 1;
+ if (*arg == ':') {
+ arg++;
+ for (;;) {
+ if (match_placeholder_arg(arg, "only", &arg))
+ opts.only_trailers = 1;
+ else if (match_placeholder_arg(arg, "unfold", &arg))
+ opts.unfold = 1;
+ else
+ break;
+ }
}
if (*arg == ')') {
format_trailers_from_commit(sb, msg + c->subject_off, &opts);
diff --git a/quote.c b/quote.c
index 53b98a5b84..de2922ddd6 100644
--- a/quote.c
+++ b/quote.c
@@ -431,6 +431,7 @@ void tcl_quote_buf(struct strbuf *sb, const char *src)
case '{': case '}':
case '$': case '\\': case '"':
strbuf_addch(sb, '\\');
+ /* fallthrough */
default:
strbuf_addch(sb, c);
break;
diff --git a/read-cache.c b/read-cache.c
index cdcd11c71e..65f4fe8375 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -220,6 +220,7 @@ static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
case S_IFDIR:
if (S_ISGITLINK(ce->ce_mode))
return ce_compare_gitlink(ce) ? DATA_CHANGED : 0;
+ /* else fallthrough */
default:
return TYPE_CHANGED;
}
diff --git a/ref-filter.c b/ref-filter.c
index bc591f4f3d..e728b15b3a 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -82,6 +82,7 @@ static struct used_atom {
} remote_ref;
struct {
enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
+ struct process_trailer_options trailer_opts;
unsigned int nlines;
} contents;
struct {
@@ -182,9 +183,23 @@ static void subject_atom_parser(const struct ref_format *format, struct used_ato
static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
- if (arg)
- die(_("%%(trailers) does not take arguments"));
+ struct string_list params = STRING_LIST_INIT_DUP;
+ int i;
+
+ if (arg) {
+ string_list_split(&params, arg, ',', -1);
+ for (i = 0; i < params.nr; i++) {
+ const char *s = params.items[i].string;
+ if (!strcmp(s, "unfold"))
+ atom->u.contents.trailer_opts.unfold = 1;
+ else if (!strcmp(s, "only"))
+ atom->u.contents.trailer_opts.only_trailers = 1;
+ else
+ die(_("unknown %%(trailers) argument: %s"), s);
+ }
+ }
atom->u.contents.option = C_TRAILERS;
+ string_list_clear(&params, 0);
}
static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
@@ -197,9 +212,10 @@ static void contents_atom_parser(const struct ref_format *format, struct used_at
atom->u.contents.option = C_SIG;
else if (!strcmp(arg, "subject"))
atom->u.contents.option = C_SUB;
- else if (!strcmp(arg, "trailers"))
- atom->u.contents.option = C_TRAILERS;
- else if (skip_prefix(arg, "lines=", &arg)) {
+ else if (skip_prefix(arg, "trailers", &arg)) {
+ skip_prefix(arg, ":", &arg);
+ trailers_atom_parser(format, atom, *arg ? arg : NULL);
+ } else if (skip_prefix(arg, "lines=", &arg)) {
atom->u.contents.option = C_LINES;
if (strtoul_ui(arg, 10, &atom->u.contents.nlines))
die(_("positive value expected contents:lines=%s"), arg);
@@ -295,9 +311,7 @@ static void if_atom_parser(const struct ref_format *format, struct used_atom *at
static void head_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
- struct object_id unused;
-
- atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused.hash, NULL);
+ atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
}
static struct {
@@ -415,8 +429,16 @@ static int parse_ref_filter_atom(const struct ref_format *format,
REALLOC_ARRAY(used_atom, used_atom_cnt);
used_atom[at].name = xmemdupz(atom, ep - atom);
used_atom[at].type = valid_atom[i].cmp_type;
- if (arg)
+ if (arg) {
arg = used_atom[at].name + (arg - atom) + 1;
+ if (!*arg) {
+ /*
+ * Treat empty sub-arguments list as NULL (i.e.,
+ * "%(atom:)" is equivalent to "%(atom)").
+ */
+ arg = NULL;
+ }
+ }
memset(&used_atom[at].u, 0, sizeof(used_atom[at].u));
if (valid_atom[i].parser)
valid_atom[i].parser(format, &used_atom[at], arg);
@@ -1042,7 +1064,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
name++;
if (strcmp(name, "subject") &&
strcmp(name, "body") &&
- strcmp(name, "trailers") &&
+ !starts_with(name, "trailers") &&
!starts_with(name, "contents"))
continue;
if (!subpos)
@@ -1067,13 +1089,12 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines);
v->s = strbuf_detach(&s, NULL);
} else if (atom->u.contents.option == C_TRAILERS) {
- struct trailer_info info;
+ struct strbuf s = STRBUF_INIT;
- /* Search for trailer info */
- trailer_info_get(&info, subpos);
- v->s = xmemdupz(info.trailer_start,
- info.trailer_end - info.trailer_start);
- trailer_info_release(&info);
+ /* Format the trailer info according to the trailer_opts given */
+ format_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);
+
+ v->s = strbuf_detach(&s, NULL);
} else if (atom->u.contents.option == C_BARE)
v->s = xstrdup(subpos);
}
@@ -1317,9 +1338,8 @@ static void populate_value(struct ref_array_item *ref)
ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
- struct object_id unused1;
ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
- unused1.hash, NULL);
+ NULL, NULL);
if (!ref->symref)
ref->symref = "";
}
diff --git a/reflog-walk.c b/reflog-walk.c
index 74ebe5148f..842b2f77dc 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -61,11 +61,10 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
reflogs->ref = xstrdup(ref);
for_each_reflog_ent(ref, read_one_reflog, reflogs);
if (reflogs->nr == 0) {
- struct object_id oid;
const char *name;
void *name_to_free;
name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,
- oid.hash, NULL);
+ NULL, NULL);
if (name) {
for_each_reflog_ent(name, read_one_reflog, reflogs);
free(name_to_free);
@@ -151,9 +150,8 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
reflogs = item->util;
else {
if (*branch == '\0') {
- struct object_id oid;
free(branch);
- branch = resolve_refdup("HEAD", 0, oid.hash, NULL);
+ branch = resolve_refdup("HEAD", 0, NULL, NULL);
if (!branch)
die ("No current branch");
diff --git a/refs.c b/refs.c
index 83f0e68785..df075fcd06 100644
--- a/refs.c
+++ b/refs.c
@@ -239,8 +239,7 @@ int read_ref(const char *refname, unsigned char *sha1)
int ref_exists(const char *refname)
{
- unsigned char sha1[20];
- return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
+ return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL);
}
static int filter_refs(const char *refname, const struct object_id *oid,
@@ -286,12 +285,11 @@ static int warn_if_dangling_symref(const char *refname, const struct object_id *
{
struct warn_if_dangling_data *d = cb_data;
const char *resolves_to;
- struct object_id junk;
if (!(flags & REF_ISSYMREF))
return 0;
- resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);
+ resolves_to = resolve_ref_unsafe(refname, 0, NULL, NULL);
if (!resolves_to
|| (d->refname
? strcmp(resolves_to, d->refname)
@@ -1287,6 +1285,10 @@ struct ref_iterator *refs_ref_iterator_begin(
if (trim)
iter = prefix_ref_iterator_begin(iter, "", trim);
+ /* Sanity check for subclasses: */
+ if (!iter->ordered)
+ BUG("reference iterator is not ordered");
+
return iter;
}
@@ -1398,9 +1400,12 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
unsigned char *sha1, int *flags)
{
static struct strbuf sb_refname = STRBUF_INIT;
+ struct object_id unused_oid;
int unused_flags;
int symref_count;
+ if (!sha1)
+ sha1 = unused_oid.hash;
if (!flags)
flags = &unused_flags;
@@ -1685,7 +1690,23 @@ int refs_pack_refs(struct ref_store *refs, unsigned int flags)
int refs_peel_ref(struct ref_store *refs, const char *refname,
unsigned char *sha1)
{
- return refs->be->peel_ref(refs, refname, sha1);
+ int flag;
+ unsigned char base[20];
+
+ if (current_ref_iter && current_ref_iter->refname == refname) {
+ struct object_id peeled;
+
+ if (ref_iterator_peel(current_ref_iter, &peeled))
+ return -1;
+ hashcpy(sha1, peeled.hash);
+ return 0;
+ }
+
+ if (refs_read_ref_full(refs, refname,
+ RESOLVE_REF_READING, base, &flag))
+ return -1;
+
+ return peel_object(base, sha1);
}
int peel_ref(const char *refname, unsigned char *sha1)
@@ -2035,3 +2056,14 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
{
return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
}
+
+int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+ const char *newref, const char *logmsg)
+{
+ return refs->be->copy_ref(refs, oldref, newref, logmsg);
+}
+
+int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+ return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
+}
diff --git a/refs.h b/refs.h
index ae33a5f74d..a02b628c8f 100644
--- a/refs.h
+++ b/refs.h
@@ -10,10 +10,11 @@ struct worktree;
/*
* Resolve a reference, recursively following symbolic refererences.
*
- * Store the referred-to object's name in sha1 and return the name of
- * the non-symbolic reference that ultimately pointed at it. The
- * return value, if not NULL, is a pointer into either a static buffer
- * or the input ref.
+ * Return the name of the non-symbolic reference that ultimately pointed
+ * at the resolved object name. The return value, if not NULL, is a
+ * pointer into either a static buffer or the input ref.
+ *
+ * If sha1 is non-NULL, store the referred-to object's name in it.
*
* If the reference cannot be resolved to an object, the behavior
* depends on the RESOLVE_REF_READING flag:
@@ -441,7 +442,14 @@ char *shorten_unambiguous_ref(const char *refname, int strict);
/** rename ref, return 0 on success **/
int refs_rename_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg);
-int rename_ref(const char *oldref, const char *newref, const char *logmsg);
+int rename_ref(const char *oldref, const char *newref,
+ const char *logmsg);
+
+/** copy ref, return 0 on success **/
+int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+ const char *newref, const char *logmsg);
+int copy_existing_ref(const char *oldref, const char *newref,
+ const char *logmsg);
int refs_create_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dac33628b3..014dabb0bf 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -641,43 +641,6 @@ out:
return ret;
}
-static int files_peel_ref(struct ref_store *ref_store,
- const char *refname, unsigned char *sha1)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_READ | REF_STORE_ODB,
- "peel_ref");
- int flag;
- unsigned char base[20];
-
- if (current_ref_iter && current_ref_iter->refname == refname) {
- struct object_id peeled;
-
- if (ref_iterator_peel(current_ref_iter, &peeled))
- return -1;
- hashcpy(sha1, peeled.hash);
- return 0;
- }
-
- if (refs_read_ref_full(ref_store, refname,
- RESOLVE_REF_READING, base, &flag))
- return -1;
-
- /*
- * If the reference is packed, read its ref_entry from the
- * cache in the hope that we already know its peeled value.
- * We only try this optimization on packed references because
- * (a) forcing the filling of the loose reference cache could
- * be expensive and (b) loose references anyway usually do not
- * have REF_KNOWS_PEELED.
- */
- if (flag & REF_ISPACKED &&
- !refs_peel_ref(refs->packed_ref_store, refname, sha1))
- return 0;
-
- return peel_object(base, sha1);
-}
-
struct files_ref_iterator {
struct ref_iterator base;
@@ -748,7 +711,7 @@ static struct ref_iterator *files_ref_iterator_begin(
const char *prefix, unsigned int flags)
{
struct files_ref_store *refs;
- struct ref_iterator *loose_iter, *packed_iter;
+ struct ref_iterator *loose_iter, *packed_iter, *overlay_iter;
struct files_ref_iterator *iter;
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
@@ -758,10 +721,6 @@ static struct ref_iterator *files_ref_iterator_begin(
refs = files_downcast(ref_store, required_flags, "ref_iterator_begin");
- iter = xcalloc(1, sizeof(*iter));
- ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable);
-
/*
* We must make sure that all loose refs are read before
* accessing the packed-refs file; this avoids a race
@@ -797,7 +756,13 @@ static struct ref_iterator *files_ref_iterator_begin(
refs->packed_ref_store, prefix, 0,
DO_FOR_EACH_INCLUDE_BROKEN);
- iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
+ overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
+
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+ base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
+ overlay_iter->ordered);
+ iter->iter0 = overlay_iter;
iter->flags = flags;
return ref_iterator;
@@ -1258,9 +1223,9 @@ static int commit_ref_update(struct files_ref_store *refs,
const struct object_id *oid, const char *logmsg,
struct strbuf *err);
-static int files_rename_ref(struct ref_store *ref_store,
+static int files_copy_or_rename_ref(struct ref_store *ref_store,
const char *oldrefname, const char *newrefname,
- const char *logmsg)
+ const char *logmsg, int copy)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "rename_ref");
@@ -1292,8 +1257,12 @@ static int files_rename_ref(struct ref_store *ref_store,
}
if (flag & REF_ISSYMREF) {
- ret = error("refname %s is a symbolic ref, renaming it is not supported",
- oldrefname);
+ if (copy)
+ ret = error("refname %s is a symbolic ref, copying it is not supported",
+ oldrefname);
+ else
+ ret = error("refname %s is a symbolic ref, renaming it is not supported",
+ oldrefname);
goto out;
}
if (!refs_rename_ref_available(&refs->base, oldrefname, newrefname)) {
@@ -1301,13 +1270,19 @@ static int files_rename_ref(struct ref_store *ref_store,
goto out;
}
- if (log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
+ if (!copy && log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
ret = error("unable to move logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
oldrefname, strerror(errno));
goto out;
}
- if (refs_delete_ref(&refs->base, logmsg, oldrefname,
+ if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) {
+ ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
+ oldrefname, strerror(errno));
+ goto out;
+ }
+
+ if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname,
orig_oid.hash, REF_NODEREF)) {
error("unable to delete old %s", oldrefname);
goto rollback;
@@ -1320,7 +1295,7 @@ static int files_rename_ref(struct ref_store *ref_store,
* the safety anyway; we want to delete the reference whatever
* its current value.
*/
- if (!refs_read_ref_full(&refs->base, newrefname,
+ if (!copy && !refs_read_ref_full(&refs->base, newrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
oid.hash, NULL) &&
refs_delete_ref(&refs->base, NULL, newrefname,
@@ -1351,7 +1326,10 @@ static int files_rename_ref(struct ref_store *ref_store,
lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
REF_NODEREF, NULL, &err);
if (!lock) {
- error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ if (copy)
+ error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ else
+ error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
strbuf_release(&err);
goto rollback;
}
@@ -1402,6 +1380,22 @@ static int files_rename_ref(struct ref_store *ref_store,
return ret;
}
+static int files_rename_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+{
+ return files_copy_or_rename_ref(ref_store, oldrefname,
+ newrefname, logmsg, 0);
+}
+
+static int files_copy_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+{
+ return files_copy_or_rename_ref(ref_store, oldrefname,
+ newrefname, logmsg, 1);
+}
+
static int close_ref_gently(struct ref_lock *lock)
{
if (close_lock_file_gently(&lock->lk))
@@ -1676,13 +1670,12 @@ static int commit_ref_update(struct files_ref_store *refs,
* check with HEAD only which should cover 99% of all usage
* scenarios (even 100% of the default ones).
*/
- struct object_id head_oid;
int head_flag;
const char *head_ref;
head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD",
RESOLVE_REF_READING,
- head_oid.hash, &head_flag);
+ NULL, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) {
struct strbuf log_err = STRBUF_INIT;
@@ -2066,7 +2059,7 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
struct ref_iterator *ref_iterator = &iter->base;
struct strbuf sb = STRBUF_INIT;
- base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
+ base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable, 0);
strbuf_addf(&sb, "%s/logs", gitdir);
iter->dir_iterator = dir_iterator_begin(sb.buf);
iter->ref_store = ref_store;
@@ -2110,6 +2103,7 @@ static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_st
return reflog_iterator_begin(ref_store, refs->gitcommondir);
} else {
return merge_ref_iterator_begin(
+ 0,
reflog_iterator_begin(ref_store, refs->gitdir),
reflog_iterator_begin(ref_store, refs->gitcommondir),
reflog_iterator_select, refs);
@@ -2500,7 +2494,6 @@ static int files_transaction_prepare(struct ref_store *ref_store,
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
char *head_ref = NULL;
int head_type;
- struct object_id head_oid;
struct files_transaction_backend_data *backend_data;
struct ref_transaction *packed_transaction = NULL;
@@ -2557,7 +2550,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
*/
head_ref = refs_resolve_refdup(ref_store, "HEAD",
RESOLVE_REF_NO_RECURSE,
- head_oid.hash, &head_type);
+ NULL, &head_type);
if (head_ref && !(head_type & REF_ISSYMREF)) {
FREE_AND_NULL(head_ref);
@@ -3007,7 +3000,7 @@ static int files_reflog_expire(struct ref_store *ref_store,
} else if (update &&
(write_in_full(get_lock_file_fd(&lock->lk),
oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) < 0 ||
- write_str_in_full(get_lock_file_fd(&lock->lk), "\n") < 1 ||
+ write_str_in_full(get_lock_file_fd(&lock->lk), "\n") < 0 ||
close_ref_gently(lock) < 0)) {
status |= error("couldn't write %s",
get_lock_file_path(&lock->lk));
@@ -3061,10 +3054,10 @@ struct ref_storage_be refs_be_files = {
files_initial_transaction_commit,
files_pack_refs,
- files_peel_ref,
files_create_symref,
files_delete_refs,
files_rename_ref,
+ files_copy_ref,
files_ref_iterator_begin,
files_read_raw_ref,
diff --git a/refs/iterator.c b/refs/iterator.c
index 4cf449ef66..bd35da4e62 100644
--- a/refs/iterator.c
+++ b/refs/iterator.c
@@ -25,9 +25,11 @@ int ref_iterator_abort(struct ref_iterator *ref_iterator)
}
void base_ref_iterator_init(struct ref_iterator *iter,
- struct ref_iterator_vtable *vtable)
+ struct ref_iterator_vtable *vtable,
+ int ordered)
{
iter->vtable = vtable;
+ iter->ordered = !!ordered;
iter->refname = NULL;
iter->oid = NULL;
iter->flags = 0;
@@ -72,7 +74,7 @@ struct ref_iterator *empty_ref_iterator_begin(void)
struct empty_ref_iterator *iter = xcalloc(1, sizeof(*iter));
struct ref_iterator *ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable);
+ base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable, 1);
return ref_iterator;
}
@@ -205,6 +207,7 @@ static struct ref_iterator_vtable merge_ref_iterator_vtable = {
};
struct ref_iterator *merge_ref_iterator_begin(
+ int ordered,
struct ref_iterator *iter0, struct ref_iterator *iter1,
ref_iterator_select_fn *select, void *cb_data)
{
@@ -219,7 +222,7 @@ struct ref_iterator *merge_ref_iterator_begin(
* references through only if they exist in both iterators.
*/
- base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable);
+ base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable, ordered);
iter->iter0 = iter0;
iter->iter1 = iter1;
iter->select = select;
@@ -268,9 +271,11 @@ struct ref_iterator *overlay_ref_iterator_begin(
} else if (is_empty_ref_iterator(back)) {
ref_iterator_abort(back);
return front;
+ } else if (!front->ordered || !back->ordered) {
+ BUG("overlay_ref_iterator requires ordered inputs");
}
- return merge_ref_iterator_begin(front, back,
+ return merge_ref_iterator_begin(1, front, back,
overlay_iterator_select, NULL);
}
@@ -282,6 +287,20 @@ struct prefix_ref_iterator {
int trim;
};
+/* Return -1, 0, 1 if refname is before, inside, or after the prefix. */
+static int compare_prefix(const char *refname, const char *prefix)
+{
+ while (*prefix) {
+ if (*refname != *prefix)
+ return ((unsigned char)*refname < (unsigned char)*prefix) ? -1 : +1;
+
+ refname++;
+ prefix++;
+ }
+
+ return 0;
+}
+
static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
{
struct prefix_ref_iterator *iter =
@@ -289,9 +308,25 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
int ok;
while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
- if (!starts_with(iter->iter0->refname, iter->prefix))
+ int cmp = compare_prefix(iter->iter0->refname, iter->prefix);
+
+ if (cmp < 0)
continue;
+ if (cmp > 0) {
+ /*
+ * If the source iterator is ordered, then we
+ * can stop the iteration as soon as we see a
+ * refname that comes after the prefix:
+ */
+ if (iter->iter0->ordered) {
+ ok = ref_iterator_abort(iter->iter0);
+ break;
+ } else {
+ continue;
+ }
+ }
+
if (iter->trim) {
/*
* It is nonsense to trim off characters that
@@ -361,7 +396,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
iter = xcalloc(1, sizeof(*iter));
ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable);
+ base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable, iter0->ordered);
iter->iter0 = iter0;
iter->prefix = xstrdup(prefix);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 3bc47ffd5e..3279d42c5a 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -2,53 +2,121 @@
#include "../config.h"
#include "../refs.h"
#include "refs-internal.h"
-#include "ref-cache.h"
#include "packed-backend.h"
#include "../iterator.h"
#include "../lockfile.h"
-struct packed_ref_cache {
- struct ref_cache *cache;
+enum mmap_strategy {
+ /*
+ * Don't use mmap() at all for reading `packed-refs`.
+ */
+ MMAP_NONE,
/*
- * Count of references to the data structure in this instance,
- * including the pointer from files_ref_store::packed if any.
- * The data will not be freed as long as the reference count
- * is nonzero.
+ * Can use mmap() for reading `packed-refs`, but the file must
+ * not remain mmapped. This is the usual option on Windows,
+ * where you cannot rename a new version of a file onto a file
+ * that is currently mmapped.
*/
- unsigned int referrers;
+ MMAP_TEMPORARY,
- /* The metadata from when this packed-refs cache was read */
- struct stat_validity validity;
+ /*
+ * It is OK to leave the `packed-refs` file mmapped while
+ * arbitrary other code is running.
+ */
+ MMAP_OK
};
-/*
- * Increment the reference count of *packed_refs.
- */
-static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
- packed_refs->referrers++;
-}
+#if defined(NO_MMAP)
+static enum mmap_strategy mmap_strategy = MMAP_NONE;
+#elif defined(MMAP_PREVENTS_DELETE)
+static enum mmap_strategy mmap_strategy = MMAP_TEMPORARY;
+#else
+static enum mmap_strategy mmap_strategy = MMAP_OK;
+#endif
+
+struct packed_ref_store;
/*
- * Decrease the reference count of *packed_refs. If it goes to zero,
- * free *packed_refs and return true; otherwise return false.
+ * A `snapshot` represents one snapshot of a `packed-refs` file.
+ *
+ * Normally, this will be a mmapped view of the contents of the
+ * `packed-refs` file at the time the snapshot was created. However,
+ * if the `packed-refs` file was not sorted, this might point at heap
+ * memory holding the contents of the `packed-refs` file with its
+ * records sorted by refname.
+ *
+ * `snapshot` instances are reference counted (via
+ * `acquire_snapshot()` and `release_snapshot()`). This is to prevent
+ * an instance from disappearing while an iterator is still iterating
+ * over it. Instances are garbage collected when their `referrers`
+ * count goes to zero.
+ *
+ * The most recent `snapshot`, if available, is referenced by the
+ * `packed_ref_store`. Its freshness is checked whenever
+ * `get_snapshot()` is called; if the existing snapshot is obsolete, a
+ * new snapshot is taken.
*/
-static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
- if (!--packed_refs->referrers) {
- free_ref_cache(packed_refs->cache);
- stat_validity_clear(&packed_refs->validity);
- free(packed_refs);
- return 1;
- } else {
- return 0;
- }
-}
+struct snapshot {
+ /*
+ * A back-pointer to the packed_ref_store with which this
+ * snapshot is associated:
+ */
+ struct packed_ref_store *refs;
+
+ /* Is the `packed-refs` file currently mmapped? */
+ int mmapped;
+
+ /*
+ * The contents of the `packed-refs` file. If the file was
+ * already sorted, this points at the mmapped contents of the
+ * file. If not, this points at heap-allocated memory
+ * containing the contents, sorted. If there were no contents
+ * (e.g., because the file didn't exist), `buf` and `eof` are
+ * both NULL.
+ */
+ char *buf, *eof;
+
+ /* The size of the header line, if any; otherwise, 0: */
+ size_t header_len;
+
+ /*
+ * What is the peeled state of the `packed-refs` file that
+ * this snapshot represents? (This is usually determined from
+ * the file's header.)
+ */
+ enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled;
+
+ /*
+ * Count of references to this instance, including the pointer
+ * from `packed_ref_store::snapshot`, if any. The instance
+ * will not be freed as long as the reference count is
+ * nonzero.
+ */
+ unsigned int referrers;
+
+ /*
+ * The metadata of the `packed-refs` file from which this
+ * snapshot was created, used to tell if the file has been
+ * replaced since we read it.
+ */
+ struct stat_validity validity;
+};
/*
- * A container for `packed-refs`-related data. It is not (yet) a
- * `ref_store`.
+ * A `ref_store` representing references stored in a `packed-refs`
+ * file. It implements the `ref_store` interface, though it has some
+ * limitations:
+ *
+ * - It cannot store symbolic references.
+ *
+ * - It cannot store reflogs.
+ *
+ * - It does not support reference renaming (though it could).
+ *
+ * On the other hand, it can be locked outside of a reference
+ * transaction. In that case, it remains locked even after the
+ * transaction is done and the new `packed-refs` file is activated.
*/
struct packed_ref_store {
struct ref_store base;
@@ -59,10 +127,10 @@ struct packed_ref_store {
char *path;
/*
- * A cache of the values read from the `packed-refs` file, if
- * it might still be current; otherwise, NULL.
+ * A snapshot of the values read from the `packed-refs` file,
+ * if it might still be current; otherwise, NULL.
*/
- struct packed_ref_cache *cache;
+ struct snapshot *snapshot;
/*
* Lock used for the "packed-refs" file. Note that this (and
@@ -78,6 +146,49 @@ struct packed_ref_store {
struct tempfile *tempfile;
};
+/*
+ * Increment the reference count of `*snapshot`.
+ */
+static void acquire_snapshot(struct snapshot *snapshot)
+{
+ snapshot->referrers++;
+}
+
+/*
+ * If the buffer in `snapshot` is active, then either munmap the
+ * memory and close the file, or free the memory. Then set the buffer
+ * pointers to NULL.
+ */
+static void clear_snapshot_buffer(struct snapshot *snapshot)
+{
+ if (snapshot->mmapped) {
+ if (munmap(snapshot->buf, snapshot->eof - snapshot->buf))
+ die_errno("error ummapping packed-refs file %s",
+ snapshot->refs->path);
+ snapshot->mmapped = 0;
+ } else {
+ free(snapshot->buf);
+ }
+ snapshot->buf = snapshot->eof = NULL;
+ snapshot->header_len = 0;
+}
+
+/*
+ * Decrease the reference count of `*snapshot`. If it goes to zero,
+ * free `*snapshot` and return true; otherwise return false.
+ */
+static int release_snapshot(struct snapshot *snapshot)
+{
+ if (!--snapshot->referrers) {
+ stat_validity_clear(&snapshot->validity);
+ clear_snapshot_buffer(snapshot);
+ free(snapshot);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
struct ref_store *packed_ref_store_create(const char *path,
unsigned int store_flags)
{
@@ -116,64 +227,369 @@ static struct packed_ref_store *packed_downcast(struct ref_store *ref_store,
return refs;
}
-static void clear_packed_ref_cache(struct packed_ref_store *refs)
+static void clear_snapshot(struct packed_ref_store *refs)
{
- if (refs->cache) {
- struct packed_ref_cache *cache = refs->cache;
+ if (refs->snapshot) {
+ struct snapshot *snapshot = refs->snapshot;
- refs->cache = NULL;
- release_packed_ref_cache(cache);
+ refs->snapshot = NULL;
+ release_snapshot(snapshot);
}
}
-/* The length of a peeled reference line in packed-refs, including EOL: */
-#define PEELED_LINE_LENGTH 42
+static NORETURN void die_unterminated_line(const char *path,
+ const char *p, size_t len)
+{
+ if (len < 80)
+ die("unterminated line in %s: %.*s", path, (int)len, p);
+ else
+ die("unterminated line in %s: %.75s...", path, p);
+}
+
+static NORETURN void die_invalid_line(const char *path,
+ const char *p, size_t len)
+{
+ const char *eol = memchr(p, '\n', len);
+
+ if (!eol)
+ die_unterminated_line(path, p, len);
+ else if (eol - p < 80)
+ die("unexpected line in %s: %.*s", path, (int)(eol - p), p);
+ else
+ die("unexpected line in %s: %.75s...", path, p);
+
+}
+
+struct snapshot_record {
+ const char *start;
+ size_t len;
+};
+
+static int cmp_packed_ref_records(const void *v1, const void *v2)
+{
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + GIT_SHA1_HEXSZ + 1;
+ const char *r2 = e2->start + GIT_SHA1_HEXSZ + 1;
+
+ while (1) {
+ if (*r1 == '\n')
+ return *r2 == '\n' ? 0 : -1;
+ if (*r1 != *r2) {
+ if (*r2 == '\n')
+ return 1;
+ else
+ return (unsigned char)*r1 < (unsigned char)*r2 ? -1 : +1;
+ }
+ r1++;
+ r2++;
+ }
+}
/*
- * Parse one line from a packed-refs file. Write the SHA1 to sha1.
- * Return a pointer to the refname within the line (null-terminated),
- * or NULL if there was a problem.
+ * Compare a snapshot record at `rec` to the specified NUL-terminated
+ * refname.
*/
-static const char *parse_ref_line(struct strbuf *line, struct object_id *oid)
+static int cmp_record_to_refname(const char *rec, const char *refname)
{
- const char *ref;
+ const char *r1 = rec + GIT_SHA1_HEXSZ + 1;
+ const char *r2 = refname;
+
+ while (1) {
+ if (*r1 == '\n')
+ return *r2 ? -1 : 0;
+ if (!*r2)
+ return 1;
+ if (*r1 != *r2)
+ return (unsigned char)*r1 < (unsigned char)*r2 ? -1 : +1;
+ r1++;
+ r2++;
+ }
+}
- if (parse_oid_hex(line->buf, oid, &ref) < 0)
- return NULL;
- if (!isspace(*ref++))
- return NULL;
+/*
+ * `snapshot->buf` is not known to be sorted. Check whether it is, and
+ * if not, sort it into new memory and munmap/free the old storage.
+ */
+static void sort_snapshot(struct snapshot *snapshot)
+{
+ struct snapshot_record *records = NULL;
+ size_t alloc = 0, nr = 0;
+ int sorted = 1;
+ const char *pos, *eof, *eol;
+ size_t len, i;
+ char *new_buffer, *dst;
- if (isspace(*ref))
- return NULL;
+ pos = snapshot->buf + snapshot->header_len;
+ eof = snapshot->eof;
+ len = eof - pos;
- if (line->buf[line->len - 1] != '\n')
- return NULL;
- line->buf[--line->len] = 0;
+ if (!len)
+ return;
+
+ /*
+ * Initialize records based on a crude estimate of the number
+ * of references in the file (we'll grow it below if needed):
+ */
+ ALLOC_GROW(records, len / 80 + 20, alloc);
+
+ while (pos < eof) {
+ eol = memchr(pos, '\n', eof - pos);
+ if (!eol)
+ /* The safety check should prevent this. */
+ BUG("unterminated line found in packed-refs");
+ if (eol - pos < GIT_SHA1_HEXSZ + 2)
+ die_invalid_line(snapshot->refs->path,
+ pos, eof - pos);
+ eol++;
+ if (eol < eof && *eol == '^') {
+ /*
+ * Keep any peeled line together with its
+ * reference:
+ */
+ const char *peeled_start = eol;
+
+ eol = memchr(peeled_start, '\n', eof - peeled_start);
+ if (!eol)
+ /* The safety check should prevent this. */
+ BUG("unterminated peeled line found in packed-refs");
+ eol++;
+ }
+
+ ALLOC_GROW(records, nr + 1, alloc);
+ records[nr].start = pos;
+ records[nr].len = eol - pos;
+ nr++;
+
+ if (sorted &&
+ nr > 1 &&
+ cmp_packed_ref_records(&records[nr - 2],
+ &records[nr - 1]) >= 0)
+ sorted = 0;
+
+ pos = eol;
+ }
+
+ if (sorted)
+ goto cleanup;
+
+ /* We need to sort the memory. First we sort the records array: */
+ QSORT(records, nr, cmp_packed_ref_records);
+
+ /*
+ * Allocate a new chunk of memory, and copy the old memory to
+ * the new in the order indicated by `records` (not bothering
+ * with the header line):
+ */
+ new_buffer = xmalloc(len);
+ for (dst = new_buffer, i = 0; i < nr; i++) {
+ memcpy(dst, records[i].start, records[i].len);
+ dst += records[i].len;
+ }
+
+ /*
+ * Now munmap the old buffer and use the sorted buffer in its
+ * place:
+ */
+ clear_snapshot_buffer(snapshot);
+ snapshot->buf = new_buffer;
+ snapshot->eof = new_buffer + len;
+ snapshot->header_len = 0;
+
+cleanup:
+ free(records);
+}
+
+/*
+ * Return a pointer to the start of the record that contains the
+ * character `*p` (which must be within the buffer). If no other
+ * record start is found, return `buf`.
+ */
+static const char *find_start_of_record(const char *buf, const char *p)
+{
+ while (p > buf && (p[-1] != '\n' || p[0] == '^'))
+ p--;
+ return p;
+}
+
+/*
+ * Return a pointer to the start of the record following the record
+ * that contains `*p`. If none is found before `end`, return `end`.
+ */
+static const char *find_end_of_record(const char *p, const char *end)
+{
+ while (++p < end && (p[-1] != '\n' || p[0] == '^'))
+ ;
+ return p;
+}
+
+/*
+ * We want to be able to compare mmapped reference records quickly,
+ * without totally parsing them. We can do so because the records are
+ * LF-terminated, and the refname should start exactly (GIT_SHA1_HEXSZ
+ * + 1) bytes past the beginning of the record.
+ *
+ * But what if the `packed-refs` file contains garbage? We're willing
+ * to tolerate not detecting the problem, as long as we don't produce
+ * totally garbled output (we can't afford to check the integrity of
+ * the whole file during every Git invocation). But we do want to be
+ * sure that we never read past the end of the buffer in memory and
+ * perform an illegal memory access.
+ *
+ * Guarantee that minimum level of safety by verifying that the last
+ * record in the file is LF-terminated, and that it has at least
+ * (GIT_SHA1_HEXSZ + 1) characters before the LF. Die if either of
+ * these checks fails.
+ */
+static void verify_buffer_safe(struct snapshot *snapshot)
+{
+ const char *buf = snapshot->buf + snapshot->header_len;
+ const char *eof = snapshot->eof;
+ const char *last_line;
+
+ if (buf == eof)
+ return;
+
+ last_line = find_start_of_record(buf, eof - 1);
+ if (*(eof - 1) != '\n' || eof - last_line < GIT_SHA1_HEXSZ + 2)
+ die_invalid_line(snapshot->refs->path,
+ last_line, eof - last_line);
+}
- return ref;
+/*
+ * Depending on `mmap_strategy`, either mmap or read the contents of
+ * the `packed-refs` file into the snapshot. Return 1 if the file
+ * existed and was read, or 0 if the file was absent. Die on errors.
+ */
+static int load_contents(struct snapshot *snapshot)
+{
+ int fd;
+ struct stat st;
+ size_t size;
+ ssize_t bytes_read;
+
+ fd = open(snapshot->refs->path, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT) {
+ /*
+ * This is OK; it just means that no
+ * "packed-refs" file has been written yet,
+ * which is equivalent to it being empty,
+ * which is its state when initialized with
+ * zeros.
+ */
+ return 0;
+ } else {
+ die_errno("couldn't read %s", snapshot->refs->path);
+ }
+ }
+
+ stat_validity_update(&snapshot->validity, fd);
+
+ if (fstat(fd, &st) < 0)
+ die_errno("couldn't stat %s", snapshot->refs->path);
+ size = xsize_t(st.st_size);
+
+ switch (mmap_strategy) {
+ case MMAP_NONE:
+ snapshot->buf = xmalloc(size);
+ bytes_read = read_in_full(fd, snapshot->buf, size);
+ if (bytes_read < 0 || bytes_read != size)
+ die_errno("couldn't read %s", snapshot->refs->path);
+ snapshot->eof = snapshot->buf + size;
+ snapshot->mmapped = 0;
+ break;
+ case MMAP_TEMPORARY:
+ case MMAP_OK:
+ snapshot->buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ snapshot->eof = snapshot->buf + size;
+ snapshot->mmapped = 1;
+ break;
+ }
+ close(fd);
+
+ return 1;
}
/*
- * Read from `packed_refs_file` into a newly-allocated
- * `packed_ref_cache` and return it. The return value will already
- * have its reference count incremented.
+ * Find the place in `snapshot->buf` where the start of the record for
+ * `refname` starts. If `mustexist` is true and the reference doesn't
+ * exist, then return NULL. If `mustexist` is false and the reference
+ * doesn't exist, then return the point where that reference would be
+ * inserted. In the latter mode, `refname` doesn't have to be a proper
+ * reference name; for example, one could search for "refs/replace/"
+ * to find the start of any replace references.
+ *
+ * The record is sought using a binary search, so `snapshot->buf` must
+ * be sorted.
+ */
+static const char *find_reference_location(struct snapshot *snapshot,
+ const char *refname, int mustexist)
+{
+ /*
+ * This is not *quite* a garden-variety binary search, because
+ * the data we're searching is made up of records, and we
+ * always need to find the beginning of a record to do a
+ * comparison. A "record" here is one line for the reference
+ * itself and zero or one peel lines that start with '^'. Our
+ * loop invariant is described in the next two comments.
+ */
+
+ /*
+ * A pointer to the character at the start of a record whose
+ * preceding records all have reference names that come
+ * *before* `refname`.
+ */
+ const char *lo = snapshot->buf + snapshot->header_len;
+
+ /*
+ * A pointer to a the first character of a record whose
+ * reference name comes *after* `refname`.
+ */
+ const char *hi = snapshot->eof;
+
+ while (lo < hi) {
+ const char *mid, *rec;
+ int cmp;
+
+ mid = lo + (hi - lo) / 2;
+ rec = find_start_of_record(lo, mid);
+ cmp = cmp_record_to_refname(rec, refname);
+ if (cmp < 0) {
+ lo = find_end_of_record(mid, hi);
+ } else if (cmp > 0) {
+ hi = rec;
+ } else {
+ return rec;
+ }
+ }
+
+ if (mustexist)
+ return NULL;
+ else
+ return lo;
+}
+
+/*
+ * Create a newly-allocated `snapshot` of the `packed-refs` file in
+ * its current state and return it. The return value will already have
+ * its reference count incremented.
*
* A comment line of the form "# pack-refs with: " may contain zero or
* more traits. We interpret the traits as follows:
*
- * No traits:
+ * Neither `peeled` nor `fully-peeled`:
*
* Probably no references are peeled. But if the file contains a
* peeled value for a reference, we will use it.
*
- * peeled:
+ * `peeled`:
*
* References under "refs/tags/", if they *can* be peeled, *are*
* peeled in this file. References outside of "refs/tags/" are
* probably not peeled even if they could have been, but if we find
* a peeled value for such a reference we will use it.
*
- * fully-peeled:
+ * `fully-peeled`:
*
* All references in the file that can be peeled are peeled.
* Inversely (and this is more important), any references in the
@@ -181,141 +597,122 @@ static const char *parse_ref_line(struct strbuf *line, struct object_id *oid)
* trait should typically be written alongside "peeled" for
* compatibility with older clients, but we do not require it
* (i.e., "peeled" is a no-op if "fully-peeled" is set).
+ *
+ * `sorted`:
+ *
+ * The references in this file are known to be sorted by refname.
*/
-static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
+static struct snapshot *create_snapshot(struct packed_ref_store *refs)
{
- FILE *f;
- struct packed_ref_cache *packed_refs = xcalloc(1, sizeof(*packed_refs));
- struct ref_entry *last = NULL;
- struct strbuf line = STRBUF_INIT;
- enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
- struct ref_dir *dir;
-
- acquire_packed_ref_cache(packed_refs);
- packed_refs->cache = create_ref_cache(NULL, NULL);
- packed_refs->cache->root->flag &= ~REF_INCOMPLETE;
-
- f = fopen(packed_refs_file, "r");
- if (!f) {
- if (errno == ENOENT) {
- /*
- * This is OK; it just means that no
- * "packed-refs" file has been written yet,
- * which is equivalent to it being empty.
- */
- return packed_refs;
- } else {
- die_errno("couldn't read %s", packed_refs_file);
- }
- }
+ struct snapshot *snapshot = xcalloc(1, sizeof(*snapshot));
+ int sorted = 0;
- stat_validity_update(&packed_refs->validity, fileno(f));
+ snapshot->refs = refs;
+ acquire_snapshot(snapshot);
+ snapshot->peeled = PEELED_NONE;
- dir = get_ref_dir(packed_refs->cache->root);
- while (strbuf_getwholeline(&line, f, '\n') != EOF) {
- struct object_id oid;
- const char *refname;
- const char *traits;
+ if (!load_contents(snapshot))
+ return snapshot;
- if (!line.len || line.buf[line.len - 1] != '\n')
- die("unterminated line in %s: %s", packed_refs_file, line.buf);
+ /* If the file has a header line, process it: */
+ if (snapshot->buf < snapshot->eof && *snapshot->buf == '#') {
+ struct strbuf tmp = STRBUF_INIT;
+ char *p;
+ const char *eol;
+ struct string_list traits = STRING_LIST_INIT_NODUP;
- if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
- if (strstr(traits, " fully-peeled "))
- peeled = PEELED_FULLY;
- else if (strstr(traits, " peeled "))
- peeled = PEELED_TAGS;
- /* perhaps other traits later as well */
- continue;
- }
+ eol = memchr(snapshot->buf, '\n',
+ snapshot->eof - snapshot->buf);
+ if (!eol)
+ die_unterminated_line(refs->path,
+ snapshot->buf,
+ snapshot->eof - snapshot->buf);
- refname = parse_ref_line(&line, &oid);
- if (refname) {
- int flag = REF_ISPACKED;
+ strbuf_add(&tmp, snapshot->buf, eol - snapshot->buf);
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- if (!refname_is_safe(refname))
- die("packed refname is dangerous: %s", refname);
- oidclr(&oid);
- flag |= REF_BAD_NAME | REF_ISBROKEN;
- }
- last = create_ref_entry(refname, &oid, flag);
- if (peeled == PEELED_FULLY ||
- (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
- last->flag |= REF_KNOWS_PEELED;
- add_ref_entry(dir, last);
- } else if (last &&
- line.buf[0] == '^' &&
- line.len == PEELED_LINE_LENGTH &&
- line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
- !get_oid_hex(line.buf + 1, &oid)) {
- oidcpy(&last->u.value.peeled, &oid);
- /*
- * Regardless of what the file header said,
- * we definitely know the value of *this*
- * reference:
- */
- last->flag |= REF_KNOWS_PEELED;
- } else {
- strbuf_setlen(&line, line.len - 1);
- die("unexpected line in %s: %s", packed_refs_file, line.buf);
- }
+ if (!skip_prefix(tmp.buf, "# pack-refs with:", (const char **)&p))
+ die_invalid_line(refs->path,
+ snapshot->buf,
+ snapshot->eof - snapshot->buf);
+
+ string_list_split_in_place(&traits, p, ' ', -1);
+
+ if (unsorted_string_list_has_string(&traits, "fully-peeled"))
+ snapshot->peeled = PEELED_FULLY;
+ else if (unsorted_string_list_has_string(&traits, "peeled"))
+ snapshot->peeled = PEELED_TAGS;
+
+ sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+ /* perhaps other traits later as well */
+
+ /* The "+ 1" is for the LF character. */
+ snapshot->header_len = eol + 1 - snapshot->buf;
+
+ string_list_clear(&traits, 0);
+ strbuf_release(&tmp);
}
- fclose(f);
- strbuf_release(&line);
+ verify_buffer_safe(snapshot);
- return packed_refs;
+ if (!sorted) {
+ sort_snapshot(snapshot);
+
+ /*
+ * Reordering the records might have moved a short one
+ * to the end of the buffer, so verify the buffer's
+ * safety again:
+ */
+ verify_buffer_safe(snapshot);
+ }
+
+ if (mmap_strategy != MMAP_OK && snapshot->mmapped) {
+ /*
+ * We don't want to leave the file mmapped, so we are
+ * forced to make a copy now:
+ */
+ size_t size = snapshot->eof -
+ (snapshot->buf + snapshot->header_len);
+ char *buf_copy = xmalloc(size);
+
+ memcpy(buf_copy, snapshot->buf + snapshot->header_len, size);
+ clear_snapshot_buffer(snapshot);
+ snapshot->buf = buf_copy;
+ snapshot->eof = buf_copy + size;
+ }
+
+ return snapshot;
}
/*
- * Check that the packed refs cache (if any) still reflects the
- * contents of the file. If not, clear the cache.
+ * Check that `refs->snapshot` (if present) still reflects the
+ * contents of the `packed-refs` file. If not, clear the snapshot.
*/
-static void validate_packed_ref_cache(struct packed_ref_store *refs)
+static void validate_snapshot(struct packed_ref_store *refs)
{
- if (refs->cache &&
- !stat_validity_check(&refs->cache->validity, refs->path))
- clear_packed_ref_cache(refs);
+ if (refs->snapshot &&
+ !stat_validity_check(&refs->snapshot->validity, refs->path))
+ clear_snapshot(refs);
}
/*
- * Get the packed_ref_cache for the specified packed_ref_store,
- * creating and populating it if it hasn't been read before or if the
- * file has been changed (according to its `validity` field) since it
- * was last read. On the other hand, if we hold the lock, then assume
- * that the file hasn't been changed out from under us, so skip the
- * extra `stat()` call in `stat_validity_check()`.
+ * Get the `snapshot` for the specified packed_ref_store, creating and
+ * populating it if it hasn't been read before or if the file has been
+ * changed (according to its `validity` field) since it was last read.
+ * On the other hand, if we hold the lock, then assume that the file
+ * hasn't been changed out from under us, so skip the extra `stat()`
+ * call in `stat_validity_check()`. This function does *not* increase
+ * the snapshot's reference count on behalf of the caller.
*/
-static struct packed_ref_cache *get_packed_ref_cache(struct packed_ref_store *refs)
+static struct snapshot *get_snapshot(struct packed_ref_store *refs)
{
if (!is_lock_file_locked(&refs->lock))
- validate_packed_ref_cache(refs);
+ validate_snapshot(refs);
- if (!refs->cache)
- refs->cache = read_packed_refs(refs->path);
+ if (!refs->snapshot)
+ refs->snapshot = create_snapshot(refs);
- return refs->cache;
-}
-
-static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
-{
- return get_ref_dir(packed_ref_cache->cache->root);
-}
-
-static struct ref_dir *get_packed_refs(struct packed_ref_store *refs)
-{
- return get_packed_ref_dir(get_packed_ref_cache(refs));
-}
-
-/*
- * Return the ref_entry for the given refname from the packed
- * references. If it does not exist, return NULL.
- */
-static struct ref_entry *get_packed_ref(struct packed_ref_store *refs,
- const char *refname)
-{
- return find_ref_entry(get_packed_refs(refs), refname);
+ return refs->snapshot;
}
static int packed_read_raw_ref(struct ref_store *ref_store,
@@ -324,69 +721,147 @@ static int packed_read_raw_ref(struct ref_store *ref_store,
{
struct packed_ref_store *refs =
packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
-
- struct ref_entry *entry;
+ struct snapshot *snapshot = get_snapshot(refs);
+ const char *rec;
*type = 0;
- entry = get_packed_ref(refs, refname);
- if (!entry) {
+ rec = find_reference_location(snapshot, refname, 1);
+
+ if (!rec) {
+ /* refname is not a packed reference. */
errno = ENOENT;
return -1;
}
- hashcpy(sha1, entry->u.value.oid.hash);
+ if (get_sha1_hex(rec, sha1))
+ die_invalid_line(refs->path, rec, snapshot->eof - rec);
+
*type = REF_ISPACKED;
return 0;
}
-static int packed_peel_ref(struct ref_store *ref_store,
- const char *refname, unsigned char *sha1)
-{
- struct packed_ref_store *refs =
- packed_downcast(ref_store, REF_STORE_READ | REF_STORE_ODB,
- "peel_ref");
- struct ref_entry *r = get_packed_ref(refs, refname);
-
- if (!r || peel_entry(r, 0))
- return -1;
-
- hashcpy(sha1, r->u.value.peeled.hash);
- return 0;
-}
+/*
+ * This value is set in `base.flags` if the peeled value of the
+ * current reference is known. In that case, `peeled` contains the
+ * correct peeled value for the reference, which might be `null_sha1`
+ * if the reference is not a tag or if it is broken.
+ */
+#define REF_KNOWS_PEELED 0x40
+/*
+ * An iterator over a snapshot of a `packed-refs` file.
+ */
struct packed_ref_iterator {
struct ref_iterator base;
- struct packed_ref_cache *cache;
- struct ref_iterator *iter0;
+ struct snapshot *snapshot;
+
+ /* The current position in the snapshot's buffer: */
+ const char *pos;
+
+ /* The end of the part of the buffer that will be iterated over: */
+ const char *eof;
+
+ /* Scratch space for current values: */
+ struct object_id oid, peeled;
+ struct strbuf refname_buf;
+
unsigned int flags;
};
+/*
+ * Move the iterator to the next record in the snapshot, without
+ * respect for whether the record is actually required by the current
+ * iteration. Adjust the fields in `iter` and return `ITER_OK` or
+ * `ITER_DONE`. This function does not free the iterator in the case
+ * of `ITER_DONE`.
+ */
+static int next_record(struct packed_ref_iterator *iter)
+{
+ const char *p = iter->pos, *eol;
+
+ strbuf_reset(&iter->refname_buf);
+
+ if (iter->pos == iter->eof)
+ return ITER_DONE;
+
+ iter->base.flags = REF_ISPACKED;
+
+ if (iter->eof - p < GIT_SHA1_HEXSZ + 2 ||
+ parse_oid_hex(p, &iter->oid, &p) ||
+ !isspace(*p++))
+ die_invalid_line(iter->snapshot->refs->path,
+ iter->pos, iter->eof - iter->pos);
+
+ eol = memchr(p, '\n', iter->eof - p);
+ if (!eol)
+ die_unterminated_line(iter->snapshot->refs->path,
+ iter->pos, iter->eof - iter->pos);
+
+ strbuf_add(&iter->refname_buf, p, eol - p);
+ iter->base.refname = iter->refname_buf.buf;
+
+ if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(iter->base.refname))
+ die("packed refname is dangerous: %s",
+ iter->base.refname);
+ oidclr(&iter->oid);
+ iter->base.flags |= REF_BAD_NAME | REF_ISBROKEN;
+ }
+ if (iter->snapshot->peeled == PEELED_FULLY ||
+ (iter->snapshot->peeled == PEELED_TAGS &&
+ starts_with(iter->base.refname, "refs/tags/")))
+ iter->base.flags |= REF_KNOWS_PEELED;
+
+ iter->pos = eol + 1;
+
+ if (iter->pos < iter->eof && *iter->pos == '^') {
+ p = iter->pos + 1;
+ if (iter->eof - p < GIT_SHA1_HEXSZ + 1 ||
+ parse_oid_hex(p, &iter->peeled, &p) ||
+ *p++ != '\n')
+ die_invalid_line(iter->snapshot->refs->path,
+ iter->pos, iter->eof - iter->pos);
+ iter->pos = p;
+
+ /*
+ * Regardless of what the file header said, we
+ * definitely know the value of *this* reference. But
+ * we suppress it if the reference is broken:
+ */
+ if ((iter->base.flags & REF_ISBROKEN)) {
+ oidclr(&iter->peeled);
+ iter->base.flags &= ~REF_KNOWS_PEELED;
+ } else {
+ iter->base.flags |= REF_KNOWS_PEELED;
+ }
+ } else {
+ oidclr(&iter->peeled);
+ }
+
+ return ITER_OK;
+}
+
static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
{
struct packed_ref_iterator *iter =
(struct packed_ref_iterator *)ref_iterator;
int ok;
- while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
+ while ((ok = next_record(iter)) == ITER_OK) {
if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
- ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE)
+ ref_type(iter->base.refname) != REF_TYPE_PER_WORKTREE)
continue;
if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
- !ref_resolves_to_object(iter->iter0->refname,
- iter->iter0->oid,
- iter->iter0->flags))
+ !ref_resolves_to_object(iter->base.refname, &iter->oid,
+ iter->flags))
continue;
- iter->base.refname = iter->iter0->refname;
- iter->base.oid = iter->iter0->oid;
- iter->base.flags = iter->iter0->flags;
return ITER_OK;
}
- iter->iter0 = NULL;
if (ref_iterator_abort(ref_iterator) != ITER_DONE)
ok = ITER_ERROR;
@@ -399,7 +874,14 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct packed_ref_iterator *iter =
(struct packed_ref_iterator *)ref_iterator;
- return ref_iterator_peel(iter->iter0, peeled);
+ if ((iter->base.flags & REF_KNOWS_PEELED)) {
+ oidcpy(peeled, &iter->peeled);
+ return is_null_oid(&iter->peeled) ? -1 : 0;
+ } else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {
+ return -1;
+ } else {
+ return !!peel_object(iter->oid.hash, peeled->hash);
+ }
}
static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator)
@@ -408,10 +890,8 @@ static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator)
(struct packed_ref_iterator *)ref_iterator;
int ok = ITER_DONE;
- if (iter->iter0)
- ok = ref_iterator_abort(iter->iter0);
-
- release_packed_ref_cache(iter->cache);
+ strbuf_release(&iter->refname_buf);
+ release_snapshot(iter->snapshot);
base_ref_iterator_free(ref_iterator);
return ok;
}
@@ -427,6 +907,8 @@ static struct ref_iterator *packed_ref_iterator_begin(
const char *prefix, unsigned int flags)
{
struct packed_ref_store *refs;
+ struct snapshot *snapshot;
+ const char *start;
struct packed_ref_iterator *iter;
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
@@ -435,22 +917,40 @@ static struct ref_iterator *packed_ref_iterator_begin(
required_flags |= REF_STORE_ODB;
refs = packed_downcast(ref_store, required_flags, "ref_iterator_begin");
+ /*
+ * Note that `get_snapshot()` internally checks whether the
+ * snapshot is up to date with what is on disk, and re-reads
+ * it if not.
+ */
+ snapshot = get_snapshot(refs);
+
+ if (!snapshot->buf)
+ return empty_ref_iterator_begin();
+
iter = xcalloc(1, sizeof(*iter));
ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable);
+ base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable, 1);
- /*
- * Note that get_packed_ref_cache() internally checks whether
- * the packed-ref cache is up to date with what is on disk,
- * and re-reads it if not.
- */
+ iter->snapshot = snapshot;
+ acquire_snapshot(snapshot);
- iter->cache = get_packed_ref_cache(refs);
- acquire_packed_ref_cache(iter->cache);
- iter->iter0 = cache_ref_iterator_begin(iter->cache->cache, prefix, 0);
+ if (prefix && *prefix)
+ start = find_reference_location(snapshot, prefix, 0);
+ else
+ start = snapshot->buf + snapshot->header_len;
+
+ iter->pos = start;
+ iter->eof = snapshot->eof;
+ strbuf_init(&iter->refname_buf, 0);
+
+ iter->base.oid = &iter->oid;
iter->flags = flags;
+ if (prefix && *prefix)
+ /* Stop iteration after we've gone *past* prefix: */
+ ref_iterator = prefix_ref_iterator_begin(ref_iterator, prefix, 0);
+
return ref_iterator;
}
@@ -505,19 +1005,19 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
/*
* Now that we hold the `packed-refs` lock, make sure that our
- * cache matches the current version of the file. Normally
- * `get_packed_ref_cache()` does that for us, but that
- * function assumes that when the file is locked, any existing
- * cache is still valid. We've just locked the file, but it
- * might have changed the moment *before* we locked it.
+ * snapshot matches the current version of the file. Normally
+ * `get_snapshot()` does that for us, but that function
+ * assumes that when the file is locked, any existing snapshot
+ * is still valid. We've just locked the file, but it might
+ * have changed the moment *before* we locked it.
*/
- validate_packed_ref_cache(refs);
+ validate_snapshot(refs);
/*
* Now make sure that the packed-refs file as it exists in the
- * locked state is loaded into the cache:
+ * locked state is loaded into the snapshot:
*/
- get_packed_ref_cache(refs);
+ get_snapshot(refs);
return 0;
}
@@ -544,11 +1044,15 @@ int packed_refs_is_locked(struct ref_store *ref_store)
}
/*
- * The packed-refs header line that we write out. Perhaps other
- * traits will be added later. The trailing space is required.
+ * The packed-refs header line that we write out. Perhaps other traits
+ * will be added later.
+ *
+ * Note that earlier versions of Git used to parse these traits by
+ * looking for " trait " in the line. For this reason, the space after
+ * the colon and the trailing space are required.
*/
static const char PACKED_REFS_HEADER[] =
- "# pack-refs with: peeled fully-peeled \n";
+ "# pack-refs with: peeled fully-peeled sorted \n";
static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)
{
@@ -557,9 +1061,9 @@ static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)
}
/*
- * Write the packed-refs from the cache to the packed-refs tempfile,
- * incorporating any changes from `updates`. `updates` must be a
- * sorted string list whose keys are the refnames and whose util
+ * Write the packed refs from the current snapshot to the packed-refs
+ * tempfile, incorporating any changes from `updates`. `updates` must
+ * be a sorted string list whose keys are the refnames and whose util
* values are `struct ref_update *`. On error, rollback the tempfile,
* write an error message to `err`, and return a nonzero value.
*
@@ -729,8 +1233,8 @@ static int write_with_updates(struct packed_ref_store *refs,
}
if (ok != ITER_DONE) {
- strbuf_addf(err, "unable to write packed-refs file: "
- "error iterating over old contents");
+ strbuf_addstr(err, "unable to write packed-refs file: "
+ "error iterating over old contents");
goto error;
}
@@ -802,9 +1306,10 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
/*
* Note that we *don't* skip transactions with zero updates,
* because such a transaction might be executed for the side
- * effect of ensuring that all of the references are peeled.
- * If the caller wants to optimize away empty transactions, it
- * should do so itself.
+ * effect of ensuring that all of the references are peeled or
+ * ensuring that the `packed-refs` file is sorted. If the
+ * caller wants to optimize away empty transactions, it should
+ * do so itself.
*/
data = xcalloc(1, sizeof(*data));
@@ -870,6 +1375,8 @@ static int packed_transaction_finish(struct ref_store *ref_store,
int ret = TRANSACTION_GENERIC_ERROR;
char *packed_refs_path;
+ clear_snapshot(refs);
+
packed_refs_path = get_locked_file_path(&refs->lock);
if (rename_tempfile(&refs->tempfile, packed_refs_path)) {
strbuf_addf(err, "error replacing %s: %s",
@@ -877,7 +1384,6 @@ static int packed_transaction_finish(struct ref_store *ref_store,
goto cleanup;
}
- clear_packed_ref_cache(refs);
ret = 0;
cleanup:
@@ -966,6 +1472,13 @@ static int packed_rename_ref(struct ref_store *ref_store,
die("BUG: packed reference store does not support renaming references");
}
+static int packed_copy_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+{
+ die("BUG: packed reference store does not support copying references");
+}
+
static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
{
return empty_ref_iterator_begin();
@@ -1027,10 +1540,10 @@ struct ref_storage_be refs_be_packed = {
packed_initial_transaction_commit,
packed_pack_refs,
- packed_peel_ref,
packed_create_symref,
packed_delete_refs,
packed_rename_ref,
+ packed_copy_ref,
packed_ref_iterator_begin,
packed_read_raw_ref,
diff --git a/refs/ref-cache.c b/refs/ref-cache.c
index 76bb723c86..4f850e1b5c 100644
--- a/refs/ref-cache.c
+++ b/refs/ref-cache.c
@@ -38,7 +38,6 @@ struct ref_entry *create_ref_entry(const char *refname,
FLEX_ALLOC_STR(ref, name, refname);
oidcpy(&ref->u.value.oid, oid);
- oidclr(&ref->u.value.peeled);
ref->flag = flag;
return ref;
}
@@ -491,49 +490,10 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
}
}
-enum peel_status peel_entry(struct ref_entry *entry, int repeel)
-{
- enum peel_status status;
-
- if (entry->flag & REF_KNOWS_PEELED) {
- if (repeel) {
- entry->flag &= ~REF_KNOWS_PEELED;
- oidclr(&entry->u.value.peeled);
- } else {
- return is_null_oid(&entry->u.value.peeled) ?
- PEEL_NON_TAG : PEEL_PEELED;
- }
- }
- if (entry->flag & REF_ISBROKEN)
- return PEEL_BROKEN;
- if (entry->flag & REF_ISSYMREF)
- return PEEL_IS_SYMREF;
-
- status = peel_object(entry->u.value.oid.hash, entry->u.value.peeled.hash);
- if (status == PEEL_PEELED || status == PEEL_NON_TAG)
- entry->flag |= REF_KNOWS_PEELED;
- return status;
-}
-
static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
- struct cache_ref_iterator *iter =
- (struct cache_ref_iterator *)ref_iterator;
- struct cache_ref_iterator_level *level;
- struct ref_entry *entry;
-
- level = &iter->levels[iter->levels_nr - 1];
-
- if (level->index == -1)
- die("BUG: peel called before advance for cache iterator");
-
- entry = level->dir->entries[level->index];
-
- if (peel_entry(entry, 0))
- return -1;
- oidcpy(peeled, &entry->u.value.peeled);
- return 0;
+ return peel_object(ref_iterator->oid->hash, peeled->hash);
}
static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
@@ -574,7 +534,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
iter = xcalloc(1, sizeof(*iter));
ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
+ base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable, 1);
ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
iter->levels_nr = 1;
diff --git a/refs/ref-cache.h b/refs/ref-cache.h
index 794f000fd3..eda65e73ed 100644
--- a/refs/ref-cache.h
+++ b/refs/ref-cache.h
@@ -38,14 +38,6 @@ struct ref_value {
* referred to by the last reference in the symlink chain.
*/
struct object_id oid;
-
- /*
- * If REF_KNOWS_PEELED, then this field holds the peeled value
- * of this reference, or null if the reference is known not to
- * be peelable. See the documentation for peel_ref() for an
- * exact definition of "peelable".
- */
- struct object_id peeled;
};
/*
@@ -97,21 +89,14 @@ struct ref_dir {
* public values; see refs.h.
*/
-/*
- * The field ref_entry->u.value.peeled of this value entry contains
- * the correct peeled value for the reference, which might be
- * null_sha1 if the reference is not a tag or if it is broken.
- */
-#define REF_KNOWS_PEELED 0x10
-
/* ref_entry represents a directory of references */
-#define REF_DIR 0x20
+#define REF_DIR 0x10
/*
* Entry has not yet been read from disk (used only for REF_DIR
* entries representing loose references)
*/
-#define REF_INCOMPLETE 0x40
+#define REF_INCOMPLETE 0x20
/*
* A ref_entry represents either a reference or a "subdirectory" of
@@ -245,23 +230,11 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname);
* Start iterating over references in `cache`. If `prefix` is
* specified, only include references whose names start with that
* prefix. If `prime_dir` is true, then fill any incomplete
- * directories before beginning the iteration.
+ * directories before beginning the iteration. The output is ordered
+ * by refname.
*/
struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
const char *prefix,
int prime_dir);
-/*
- * Peel the entry (if possible) and return its new peel_status. If
- * repeel is true, re-peel the entry even if there is an old peeled
- * value that is already stored in it.
- *
- * It is OK to call this function with a packed reference entry that
- * might be stale and might even refer to an object that has since
- * been garbage-collected. In such a case, if the entry has
- * REF_KNOWS_PEELED then leave the status unchanged and return
- * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
- */
-enum peel_status peel_entry(struct ref_entry *entry, int repeel);
-
#endif /* REFS_REF_CACHE_H */
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d7d344de73..448de4bccb 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -329,6 +329,13 @@ int refs_rename_ref_available(struct ref_store *refs,
*/
struct ref_iterator {
struct ref_iterator_vtable *vtable;
+
+ /*
+ * Does this `ref_iterator` iterate over references in order
+ * by refname?
+ */
+ unsigned int ordered : 1;
+
const char *refname;
const struct object_id *oid;
unsigned int flags;
@@ -374,7 +381,7 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
* which the refname begins with prefix. If trim is non-zero, then
* trim that many characters off the beginning of each refname. flags
* can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in
- * the iteration.
+ * the iteration. The output is ordered by refname.
*/
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
@@ -400,9 +407,11 @@ typedef enum iterator_selection ref_iterator_select_fn(
* Iterate over the entries from iter0 and iter1, with the values
* interleaved as directed by the select function. The iterator takes
* ownership of iter0 and iter1 and frees them when the iteration is
- * over.
+ * over. A derived class should set `ordered` to 1 or 0 based on
+ * whether it generates its output in order by reference name.
*/
struct ref_iterator *merge_ref_iterator_begin(
+ int ordered,
struct ref_iterator *iter0, struct ref_iterator *iter1,
ref_iterator_select_fn *select, void *cb_data);
@@ -431,6 +440,8 @@ struct ref_iterator *overlay_ref_iterator_begin(
* As an convenience to callers, if prefix is the empty string and
* trim is zero, this function returns iter0 directly, without
* wrapping it.
+ *
+ * The resulting ref_iterator is ordered if iter0 is.
*/
struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
const char *prefix,
@@ -441,11 +452,14 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
/*
* Base class constructor for ref_iterators. Initialize the
* ref_iterator part of iter, setting its vtable pointer as specified.
+ * `ordered` should be set to 1 if the iterator will iterate over
+ * references in order by refname; otherwise it should be set to 0.
* This is meant to be called only by the initializers of derived
* classes.
*/
void base_ref_iterator_init(struct ref_iterator *iter,
- struct ref_iterator_vtable *vtable);
+ struct ref_iterator_vtable *vtable,
+ int ordered);
/*
* Base class destructor for ref_iterators. Destroy the ref_iterator
@@ -548,8 +562,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
struct strbuf *err);
typedef int pack_refs_fn(struct ref_store *ref_store, unsigned int flags);
-typedef int peel_ref_fn(struct ref_store *ref_store,
- const char *refname, unsigned char *sha1);
typedef int create_symref_fn(struct ref_store *ref_store,
const char *ref_target,
const char *refs_heads_master,
@@ -559,12 +571,16 @@ typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg,
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
+typedef int copy_ref_fn(struct ref_store *ref_store,
+ const char *oldref, const char *newref,
+ const char *logmsg);
/*
* Iterate over the references in `ref_store` whose names start with
* `prefix`. `prefix` is matched as a literal string, without regard
* for path separators. If prefix is NULL or the empty string, iterate
- * over all references in `ref_store`.
+ * over all references in `ref_store`. The output is ordered by
+ * refname.
*/
typedef struct ref_iterator *ref_iterator_begin_fn(
struct ref_store *ref_store,
@@ -653,10 +669,10 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
- peel_ref_fn *peel_ref;
create_symref_fn *create_symref;
delete_refs_fn *delete_refs;
rename_ref_fn *rename_ref;
+ copy_ref_fn *copy_ref;
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
diff --git a/remote.c b/remote.c
index 4113090069..b220f0dfc6 100644
--- a/remote.c
+++ b/remote.c
@@ -466,7 +466,6 @@ static void alias_all_urls(void)
static void read_config(void)
{
static int loaded;
- struct object_id oid;
int flag;
if (loaded)
@@ -475,7 +474,7 @@ static void read_config(void)
current_branch = NULL;
if (startup_info->have_repository) {
- const char *head_ref = resolve_ref_unsafe("HEAD", 0, oid.hash, &flag);
+ const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
skip_prefix(head_ref, "refs/heads/", &head_ref)) {
current_branch = make_branch(head_ref, 0);
@@ -1105,10 +1104,9 @@ static struct ref *make_linked_ref(const char *name, struct ref ***tail)
static char *guess_ref(const char *name, struct ref *peer)
{
struct strbuf buf = STRBUF_INIT;
- struct object_id oid;
const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,
- oid.hash, NULL);
+ NULL, NULL);
if (!r)
return NULL;
@@ -1166,12 +1164,11 @@ static int match_explicit(struct ref *src, struct ref *dst,
return -1;
if (!dst_value) {
- struct object_id oid;
int flag;
dst_value = resolve_ref_unsafe(matched_src->name,
RESOLVE_REF_READING,
- oid.hash, &flag);
+ NULL, &flag);
if (!dst_value ||
((flag & REF_ISSYMREF) &&
!starts_with(dst_value, "refs/heads/")))
@@ -1792,10 +1789,9 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err)
static int ignore_symref_update(const char *refname)
{
- struct object_id oid;
int flag;
- if (!resolve_ref_unsafe(refname, 0, oid.hash, &flag))
+ if (!resolve_ref_unsafe(refname, 0, NULL, &flag))
return 0; /* non-existing refs are OK */
return (flag & REF_ISSYMREF);
}
diff --git a/repository.c b/repository.c
index 97c732bd48..bb2fae5446 100644
--- a/repository.c
+++ b/repository.c
@@ -200,25 +200,17 @@ out:
void repo_clear(struct repository *repo)
{
- free(repo->gitdir);
- repo->gitdir = NULL;
- free(repo->commondir);
- repo->commondir = NULL;
- free(repo->objectdir);
- repo->objectdir = NULL;
- free(repo->graft_file);
- repo->graft_file = NULL;
- free(repo->index_file);
- repo->index_file = NULL;
- free(repo->worktree);
- repo->worktree = NULL;
- free(repo->submodule_prefix);
- repo->submodule_prefix = NULL;
+ FREE_AND_NULL(repo->gitdir);
+ FREE_AND_NULL(repo->commondir);
+ FREE_AND_NULL(repo->objectdir);
+ FREE_AND_NULL(repo->graft_file);
+ FREE_AND_NULL(repo->index_file);
+ FREE_AND_NULL(repo->worktree);
+ FREE_AND_NULL(repo->submodule_prefix);
if (repo->config) {
git_configset_clear(repo->config);
- free(repo->config);
- repo->config = NULL;
+ FREE_AND_NULL(repo->config);
}
if (repo->submodule_cache) {
@@ -228,8 +220,7 @@ void repo_clear(struct repository *repo)
if (repo->index) {
discard_index(repo->index);
- free(repo->index);
- repo->index = NULL;
+ FREE_AND_NULL(repo->index);
}
}
diff --git a/revision.c b/revision.c
index 1520f69d93..d167223e69 100644
--- a/revision.c
+++ b/revision.c
@@ -1106,7 +1106,7 @@ static void add_rev_cmdline(struct rev_info *revs,
unsigned flags)
{
struct rev_cmdline_info *info = &revs->cmdline;
- int nr = info->nr;
+ unsigned int nr = info->nr;
ALLOC_GROW(info->rev, nr + 1, info->alloc);
info->rev[nr].item = item;
@@ -2248,11 +2248,10 @@ static int handle_revision_pseudo_opt(const char *submodule,
static void NORETURN diagnose_missing_default(const char *def)
{
- unsigned char sha1[20];
int flags;
const char *refname;
- refname = resolve_ref_unsafe(def, 0, sha1, &flags);
+ refname = resolve_ref_unsafe(def, 0, NULL, &flags);
if (!refname || !(flags & REF_ISSYMREF) || (flags & REF_ISBROKEN))
die(_("your current branch appears to be broken"));
diff --git a/revision.h b/revision.h
index 3a3d3e2cf8..54761200ad 100644
--- a/revision.h
+++ b/revision.h
@@ -150,6 +150,17 @@ struct rev_info {
date_mode_explicit:1,
preserve_subject:1;
unsigned int disable_stdin:1;
+ /*
+ * Set `leak_pending` to prevent `prepare_revision_walk()` from clearing
+ * the array of pending objects (`pending`). It will still forget about
+ * the array and its entries, so they really are leaked. This can be
+ * useful if the `struct object_array` `pending` is copied before
+ * calling `prepare_revision_walk()`. By setting `leak_pending`, you
+ * effectively claim ownership of the old array, so you should most
+ * likely call `object_array_clear(&pending_copy)` once you are done.
+ * Observe that this is about ownership of the array and its entries,
+ * not the commits referenced by those entries.
+ */
unsigned int leak_pending:1;
/* --show-linear-break */
unsigned int track_linear:1,
diff --git a/run-command.c b/run-command.c
index b5e6eb37c0..014b2165b5 100644
--- a/run-command.c
+++ b/run-command.c
@@ -452,7 +452,7 @@ static char **prep_childenv(const char *const *deltaenv)
}
/* Create an array of 'char *' to be used as the childenv */
- childenv = xmalloc((env.nr + 1) * sizeof(char *));
+ ALLOC_ARRAY(childenv, env.nr + 1);
for (i = 0; i < env.nr; i++)
childenv[i] = env.items[i].util;
childenv[env.nr] = NULL;
diff --git a/send-pack.c b/send-pack.c
index b865f662e4..a8cc6b266e 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -497,7 +497,7 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&cap_buf);
return atomic_push_failure(args, remote_refs, ref);
}
- /* Fallthrough for non atomic case. */
+ /* else fallthrough */
default:
continue;
}
diff --git a/sequencer.c b/sequencer.c
index 60636ce54b..e258bb6469 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -20,6 +20,7 @@
#include "trailer.h"
#include "log-tree.h"
#include "wt-status.h"
+#include "hashmap.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -203,7 +204,7 @@ int sequencer_remove_state(struct replay_opts *opts)
free(opts->xopts[i]);
free(opts->xopts);
- strbuf_addf(&dir, "%s", get_dir(opts));
+ strbuf_addstr(&dir, get_dir(opts));
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
@@ -2435,3 +2436,533 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
strbuf_release(&sob);
}
+
+int sequencer_make_script(int keep_empty, FILE *out,
+ int argc, const char **argv)
+{
+ char *format = NULL;
+ struct pretty_print_context pp = {0};
+ struct strbuf buf = STRBUF_INIT;
+ struct rev_info revs;
+ struct commit *commit;
+
+ init_revisions(&revs, NULL);
+ revs.verbose_header = 1;
+ revs.max_parents = 1;
+ revs.cherry_pick = 1;
+ revs.limited = 1;
+ revs.reverse = 1;
+ revs.right_only = 1;
+ revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
+ revs.topo_order = 1;
+
+ revs.pretty_given = 1;
+ git_config_get_string("rebase.instructionFormat", &format);
+ if (!format || !*format) {
+ free(format);
+ format = xstrdup("%s");
+ }
+ get_commit_format(format, &revs);
+ free(format);
+ pp.fmt = revs.commit_format;
+ pp.output_encoding = get_log_output_encoding();
+
+ if (setup_revisions(argc, argv, &revs, NULL) > 1)
+ return error(_("make_script: unhandled options"));
+
+ if (prepare_revision_walk(&revs) < 0)
+ return error(_("make_script: error preparing revisions"));
+
+ while ((commit = get_revision(&revs))) {
+ strbuf_reset(&buf);
+ if (!keep_empty && is_original_commit_empty(commit))
+ strbuf_addf(&buf, "%c ", comment_line_char);
+ strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
+ pretty_print_commit(&pp, commit, &buf);
+ strbuf_addch(&buf, '\n');
+ fputs(buf.buf, out);
+ }
+ strbuf_release(&buf);
+ return 0;
+}
+
+
+int transform_todo_ids(int shorten_ids)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ int fd, res, i;
+ FILE *out;
+
+ strbuf_reset(&todo_list.buf);
+ fd = open(todo_file, O_RDONLY);
+ if (fd < 0)
+ return error_errno(_("could not open '%s'"), todo_file);
+ if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
+ close(fd);
+ return error(_("could not read '%s'."), todo_file);
+ }
+ close(fd);
+
+ res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
+ if (res) {
+ todo_list_release(&todo_list);
+ return error(_("unusable todo list: '%s'"), todo_file);
+ }
+
+ out = fopen(todo_file, "w");
+ if (!out) {
+ todo_list_release(&todo_list);
+ return error(_("unable to open '%s' for writing"), todo_file);
+ }
+ for (i = 0; i < todo_list.nr; i++) {
+ struct todo_item *item = todo_list.items + i;
+ int bol = item->offset_in_buf;
+ const char *p = todo_list.buf.buf + bol;
+ int eol = i + 1 < todo_list.nr ?
+ todo_list.items[i + 1].offset_in_buf :
+ todo_list.buf.len;
+
+ if (item->command >= TODO_EXEC && item->command != TODO_DROP)
+ fwrite(p, eol - bol, 1, out);
+ else {
+ const char *id = shorten_ids ?
+ short_commit_name(item->commit) :
+ oid_to_hex(&item->commit->object.oid);
+ int len;
+
+ p += strspn(p, " \t"); /* left-trim command */
+ len = strcspn(p, " \t"); /* length of command */
+
+ fprintf(out, "%.*s %s %.*s\n",
+ len, p, id, item->arg_len, item->arg);
+ }
+ }
+ fclose(out);
+ todo_list_release(&todo_list);
+ return 0;
+}
+
+enum check_level {
+ CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
+};
+
+static enum check_level get_missing_commit_check_level(void)
+{
+ const char *value;
+
+ if (git_config_get_value("rebase.missingcommitscheck", &value) ||
+ !strcasecmp("ignore", value))
+ return CHECK_IGNORE;
+ if (!strcasecmp("warn", value))
+ return CHECK_WARN;
+ if (!strcasecmp("error", value))
+ return CHECK_ERROR;
+ warning(_("unrecognized setting %s for option "
+ "rebase.missingCommitsCheck. Ignoring."), value);
+ return CHECK_IGNORE;
+}
+
+/*
+ * Check if the user dropped some commits by mistake
+ * Behaviour determined by rebase.missingCommitsCheck.
+ * Check if there is an unrecognized command or a
+ * bad SHA-1 in a command.
+ */
+int check_todo_list(void)
+{
+ enum check_level check_level = get_missing_commit_check_level();
+ struct strbuf todo_file = STRBUF_INIT;
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct strbuf missing = STRBUF_INIT;
+ int advise_to_edit_todo = 0, res = 0, fd, i;
+
+ strbuf_addstr(&todo_file, rebase_path_todo());
+ fd = open(todo_file.buf, O_RDONLY);
+ if (fd < 0) {
+ res = error_errno(_("could not open '%s'"), todo_file.buf);
+ goto leave_check;
+ }
+ if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
+ close(fd);
+ res = error(_("could not read '%s'."), todo_file.buf);
+ goto leave_check;
+ }
+ close(fd);
+ advise_to_edit_todo = res =
+ parse_insn_buffer(todo_list.buf.buf, &todo_list);
+
+ if (res || check_level == CHECK_IGNORE)
+ goto leave_check;
+
+ /* Mark the commits in git-rebase-todo as seen */
+ for (i = 0; i < todo_list.nr; i++) {
+ struct commit *commit = todo_list.items[i].commit;
+ if (commit)
+ commit->util = (void *)1;
+ }
+
+ todo_list_release(&todo_list);
+ strbuf_addstr(&todo_file, ".backup");
+ fd = open(todo_file.buf, O_RDONLY);
+ if (fd < 0) {
+ res = error_errno(_("could not open '%s'"), todo_file.buf);
+ goto leave_check;
+ }
+ if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
+ close(fd);
+ res = error(_("could not read '%s'."), todo_file.buf);
+ goto leave_check;
+ }
+ close(fd);
+ strbuf_release(&todo_file);
+ res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
+
+ /* Find commits in git-rebase-todo.backup yet unseen */
+ for (i = todo_list.nr - 1; i >= 0; i--) {
+ struct todo_item *item = todo_list.items + i;
+ struct commit *commit = item->commit;
+ if (commit && !commit->util) {
+ strbuf_addf(&missing, " - %s %.*s\n",
+ short_commit_name(commit),
+ item->arg_len, item->arg);
+ commit->util = (void *)1;
+ }
+ }
+
+ /* Warn about missing commits */
+ if (!missing.len)
+ goto leave_check;
+
+ if (check_level == CHECK_ERROR)
+ advise_to_edit_todo = res = 1;
+
+ fprintf(stderr,
+ _("Warning: some commits may have been dropped accidentally.\n"
+ "Dropped commits (newer to older):\n"));
+
+ /* Make the list user-friendly and display */
+ fputs(missing.buf, stderr);
+ strbuf_release(&missing);
+
+ fprintf(stderr, _("To avoid this message, use \"drop\" to "
+ "explicitly remove a commit.\n\n"
+ "Use 'git config rebase.missingCommitsCheck' to change "
+ "the level of warnings.\n"
+ "The possible behaviours are: ignore, warn, error.\n\n"));
+
+leave_check:
+ strbuf_release(&todo_file);
+ todo_list_release(&todo_list);
+
+ if (advise_to_edit_todo)
+ fprintf(stderr,
+ _("You can fix this with 'git rebase --edit-todo' "
+ "and then run 'git rebase --continue'.\n"
+ "Or you can abort the rebase with 'git rebase"
+ " --abort'.\n"));
+
+ return res;
+}
+
+/* skip picking commits whose parents are unchanged */
+int skip_unnecessary_picks(void)
+{
+ const char *todo_file = rebase_path_todo();
+ struct strbuf buf = STRBUF_INIT;
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
+ int fd, i;
+
+ if (!read_oneliner(&buf, rebase_path_onto(), 0))
+ return error(_("could not read 'onto'"));
+ if (get_oid(buf.buf, &onto_oid)) {
+ strbuf_release(&buf);
+ return error(_("need a HEAD to fixup"));
+ }
+ strbuf_release(&buf);
+
+ fd = open(todo_file, O_RDONLY);
+ if (fd < 0) {
+ return error_errno(_("could not open '%s'"), todo_file);
+ }
+ if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
+ close(fd);
+ return error(_("could not read '%s'."), todo_file);
+ }
+ close(fd);
+ if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+ todo_list_release(&todo_list);
+ return -1;
+ }
+
+ for (i = 0; i < todo_list.nr; i++) {
+ struct todo_item *item = todo_list.items + i;
+
+ if (item->command >= TODO_NOOP)
+ continue;
+ if (item->command != TODO_PICK)
+ break;
+ if (parse_commit(item->commit)) {
+ todo_list_release(&todo_list);
+ return error(_("could not parse commit '%s'"),
+ oid_to_hex(&item->commit->object.oid));
+ }
+ if (!item->commit->parents)
+ break; /* root commit */
+ if (item->commit->parents->next)
+ break; /* merge commit */
+ parent_oid = &item->commit->parents->item->object.oid;
+ if (hashcmp(parent_oid->hash, oid->hash))
+ break;
+ oid = &item->commit->object.oid;
+ }
+ if (i > 0) {
+ int offset = i < todo_list.nr ?
+ todo_list.items[i].offset_in_buf : todo_list.buf.len;
+ const char *done_path = rebase_path_done();
+
+ fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ if (fd < 0) {
+ error_errno(_("could not open '%s' for writing"),
+ done_path);
+ todo_list_release(&todo_list);
+ return -1;
+ }
+ if (write_in_full(fd, todo_list.buf.buf, offset) < 0) {
+ error_errno(_("could not write to '%s'"), done_path);
+ todo_list_release(&todo_list);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ fd = open(rebase_path_todo(), O_WRONLY, 0666);
+ if (fd < 0) {
+ error_errno(_("could not open '%s' for writing"),
+ rebase_path_todo());
+ todo_list_release(&todo_list);
+ return -1;
+ }
+ if (write_in_full(fd, todo_list.buf.buf + offset,
+ todo_list.buf.len - offset) < 0) {
+ error_errno(_("could not write to '%s'"),
+ rebase_path_todo());
+ close(fd);
+ todo_list_release(&todo_list);
+ return -1;
+ }
+ if (ftruncate(fd, todo_list.buf.len - offset) < 0) {
+ error_errno(_("could not truncate '%s'"),
+ rebase_path_todo());
+ todo_list_release(&todo_list);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ todo_list.current = i;
+ if (is_fixup(peek_command(&todo_list, 0)))
+ record_in_rewritten(oid, peek_command(&todo_list, 0));
+ }
+
+ todo_list_release(&todo_list);
+ printf("%s\n", oid_to_hex(oid));
+
+ return 0;
+}
+
+struct subject2item_entry {
+ struct hashmap_entry entry;
+ int i;
+ char subject[FLEX_ARRAY];
+};
+
+static int subject2item_cmp(const void *fndata,
+ const struct subject2item_entry *a,
+ const struct subject2item_entry *b, const void *key)
+{
+ return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
+}
+
+/*
+ * Rearrange the todo list that has both "pick commit-id msg" and "pick
+ * commit-id fixup!/squash! msg" in it so that the latter is put immediately
+ * after the former, and change "pick" to "fixup"/"squash".
+ *
+ * Note that if the config has specified a custom instruction format, each log
+ * message will have to be retrieved from the commit (as the oneline in the
+ * script cannot be trusted) in order to normalize the autosquash arrangement.
+ */
+int rearrange_squash(void)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct hashmap subject2item;
+ int res = 0, rearranged = 0, *next, *tail, fd, i;
+ char **subjects;
+
+ fd = open(todo_file, O_RDONLY);
+ if (fd < 0)
+ return error_errno(_("could not open '%s'"), todo_file);
+ if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
+ close(fd);
+ return error(_("could not read '%s'."), todo_file);
+ }
+ close(fd);
+ if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+ todo_list_release(&todo_list);
+ return -1;
+ }
+
+ /*
+ * The hashmap maps onelines to the respective todo list index.
+ *
+ * If any items need to be rearranged, the next[i] value will indicate
+ * which item was moved directly after the i'th.
+ *
+ * In that case, last[i] will indicate the index of the latest item to
+ * be moved to appear after the i'th.
+ */
+ hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp,
+ NULL, todo_list.nr);
+ ALLOC_ARRAY(next, todo_list.nr);
+ ALLOC_ARRAY(tail, todo_list.nr);
+ ALLOC_ARRAY(subjects, todo_list.nr);
+ for (i = 0; i < todo_list.nr; i++) {
+ struct strbuf buf = STRBUF_INIT;
+ struct todo_item *item = todo_list.items + i;
+ const char *commit_buffer, *subject, *p;
+ size_t subject_len;
+ int i2 = -1;
+ struct subject2item_entry *entry;
+
+ next[i] = tail[i] = -1;
+ if (item->command >= TODO_EXEC) {
+ subjects[i] = NULL;
+ continue;
+ }
+
+ if (is_fixup(item->command)) {
+ todo_list_release(&todo_list);
+ return error(_("the script was already rearranged."));
+ }
+
+ item->commit->util = item;
+
+ parse_commit(item->commit);
+ commit_buffer = get_commit_buffer(item->commit, NULL);
+ find_commit_subject(commit_buffer, &subject);
+ format_subject(&buf, subject, " ");
+ subject = subjects[i] = strbuf_detach(&buf, &subject_len);
+ unuse_commit_buffer(item->commit, commit_buffer);
+ if ((skip_prefix(subject, "fixup! ", &p) ||
+ skip_prefix(subject, "squash! ", &p))) {
+ struct commit *commit2;
+
+ for (;;) {
+ while (isspace(*p))
+ p++;
+ if (!skip_prefix(p, "fixup! ", &p) &&
+ !skip_prefix(p, "squash! ", &p))
+ break;
+ }
+
+ if ((entry = hashmap_get_from_hash(&subject2item,
+ strhash(p), p)))
+ /* found by title */
+ i2 = entry->i;
+ else if (!strchr(p, ' ') &&
+ (commit2 =
+ lookup_commit_reference_by_name(p)) &&
+ commit2->util)
+ /* found by commit name */
+ i2 = (struct todo_item *)commit2->util
+ - todo_list.items;
+ else {
+ /* copy can be a prefix of the commit subject */
+ for (i2 = 0; i2 < i; i2++)
+ if (subjects[i2] &&
+ starts_with(subjects[i2], p))
+ break;
+ if (i2 == i)
+ i2 = -1;
+ }
+ }
+ if (i2 >= 0) {
+ rearranged = 1;
+ todo_list.items[i].command =
+ starts_with(subject, "fixup!") ?
+ TODO_FIXUP : TODO_SQUASH;
+ if (next[i2] < 0)
+ next[i2] = i;
+ else
+ next[tail[i2]] = i;
+ tail[i2] = i;
+ } else if (!hashmap_get_from_hash(&subject2item,
+ strhash(subject), subject)) {
+ FLEX_ALLOC_MEM(entry, subject, subject, subject_len);
+ entry->i = i;
+ hashmap_entry_init(entry, strhash(entry->subject));
+ hashmap_put(&subject2item, entry);
+ }
+ }
+
+ if (rearranged) {
+ struct strbuf buf = STRBUF_INIT;
+
+ for (i = 0; i < todo_list.nr; i++) {
+ enum todo_command command = todo_list.items[i].command;
+ int cur = i;
+
+ /*
+ * Initially, all commands are 'pick's. If it is a
+ * fixup or a squash now, we have rearranged it.
+ */
+ if (is_fixup(command))
+ continue;
+
+ while (cur >= 0) {
+ int offset = todo_list.items[cur].offset_in_buf;
+ int end_offset = cur + 1 < todo_list.nr ?
+ todo_list.items[cur + 1].offset_in_buf :
+ todo_list.buf.len;
+ char *bol = todo_list.buf.buf + offset;
+ char *eol = todo_list.buf.buf + end_offset;
+
+ /* replace 'pick', by 'fixup' or 'squash' */
+ command = todo_list.items[cur].command;
+ if (is_fixup(command)) {
+ strbuf_addstr(&buf,
+ todo_command_info[command].str);
+ bol += strcspn(bol, " \t");
+ }
+
+ strbuf_add(&buf, bol, eol - bol);
+
+ cur = next[cur];
+ }
+ }
+
+ fd = open(todo_file, O_WRONLY);
+ if (fd < 0)
+ res = error_errno(_("could not open '%s'"), todo_file);
+ else if (write(fd, buf.buf, buf.len) < 0)
+ res = error_errno(_("could not read '%s'."), todo_file);
+ else if (ftruncate(fd, buf.len) < 0)
+ res = error_errno(_("could not finish '%s'"),
+ todo_file);
+ close(fd);
+ strbuf_release(&buf);
+ }
+
+ free(next);
+ free(tail);
+ for (i = 0; i < todo_list.nr; i++)
+ free(subjects[i]);
+ free(subjects);
+ hashmap_free(&subject2item, 1);
+ todo_list_release(&todo_list);
+
+ return res;
+}
diff --git a/sequencer.h b/sequencer.h
index f885b68395..6f3d3df82c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -45,6 +45,14 @@ int sequencer_continue(struct replay_opts *opts);
int sequencer_rollback(struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts);
+int sequencer_make_script(int keep_empty, FILE *out,
+ int argc, const char **argv);
+
+int transform_todo_ids(int shorten_ids);
+int check_todo_list(void);
+int skip_unnecessary_picks(void);
+int rearrange_squash(void);
+
extern const char sign_off_header[];
void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
diff --git a/setup.c b/setup.c
index 6d8380acd2..03f51e056c 100644
--- a/setup.c
+++ b/setup.c
@@ -230,7 +230,7 @@ void verify_filename(const char *prefix,
int diagnose_misspelt_rev)
{
if (*arg == '-')
- die("bad flag '%s' used after filename", arg);
+ die("option '%s' must come before non-option arguments", arg);
if (looks_like_pathspec(arg) || check_filename(prefix, arg))
return;
die_verify_filename(prefix, arg, diagnose_misspelt_rev);
@@ -541,7 +541,8 @@ void read_gitfile_error_die(int error_code, const char *path, const char *dir)
/*
* Try to read the location of the git directory from the .git file,
- * return path to git directory if found.
+ * return path to git directory if found. The return value comes from
+ * a shared buffer.
*
* On failure, if return_error_code is not NULL, return_error_code
* will be set to an error code and NULL will be returned. If
diff --git a/sha1_file.c b/sha1_file.c
index 5a2014811f..09ad64ce55 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -1748,10 +1748,15 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
ret = index_mem(sha1, "", size, type, path, flags);
} else if (size <= SMALL_FILE_SIZE) {
char *buf = xmalloc(size);
- if (size == read_in_full(fd, buf, size))
- ret = index_mem(sha1, buf, size, type, path, flags);
+ ssize_t read_result = read_in_full(fd, buf, size);
+ if (read_result < 0)
+ ret = error_errno("read error while indexing %s",
+ path ? path : "<unknown>");
+ else if (read_result != size)
+ ret = error("short read while indexing %s",
+ path ? path : "<unknown>");
else
- ret = error_errno("short read");
+ ret = index_mem(sha1, buf, size, type, path, flags);
free(buf);
} else {
void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
diff --git a/shallow.c b/shallow.c
index eabb65d3a7..df4d44ea7a 100644
--- a/shallow.c
+++ b/shallow.c
@@ -99,7 +99,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
cur_depth = 0;
} else {
commit = (struct commit *)
- stack.objects[--stack.nr].item;
+ object_array_pop(&stack);
cur_depth = *(int *)commit->util;
}
}
diff --git a/shell.c b/shell.c
index fe2d314593..234b2d4f16 100644
--- a/shell.c
+++ b/shell.c
@@ -25,19 +25,6 @@ static int do_generic_cmd(const char *me, char *arg)
return execv_git_cmd(my_argv);
}
-static int do_cvs_cmd(const char *me, char *arg)
-{
- const char *cvsserver_argv[3] = {
- "cvsserver", "server", NULL
- };
-
- if (!arg || strcmp(arg, "server"))
- die("git-cvsserver only handles server: %s", arg);
-
- setup_path();
- return execv_git_cmd(cvsserver_argv);
-}
-
static int is_valid_cmd_name(const char *cmd)
{
/* Test command contains no . or / characters */
@@ -134,7 +121,6 @@ static struct commands {
{ "git-receive-pack", do_generic_cmd },
{ "git-upload-pack", do_generic_cmd },
{ "git-upload-archive", do_generic_cmd },
- { "cvs", do_cvs_cmd },
{ NULL },
};
diff --git a/strbuf.h b/strbuf.h
index 7496cb8ec5..0a74acb236 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -82,8 +82,12 @@ extern char strbuf_slopbuf[];
extern void strbuf_init(struct strbuf *, size_t);
/**
- * Release a string buffer and the memory it used. You should not use the
- * string buffer after using this function, unless you initialize it again.
+ * Release a string buffer and the memory it used. After this call, the
+ * strbuf points to an empty string that does not need to be free()ed, as
+ * if it had been set to `STRBUF_INIT` and never modified.
+ *
+ * To clear a strbuf in preparation for further use without the overhead
+ * of free()ing and malloc()ing again, use strbuf_reset() instead.
*/
extern void strbuf_release(struct strbuf *);
@@ -91,6 +95,9 @@ extern void strbuf_release(struct strbuf *);
* Detach the string from the strbuf and returns it; you now own the
* storage the string occupies and it is your responsibility from then on
* to release it with `free(3)` when you are done with it.
+ *
+ * The strbuf that previously held the string is reset to `STRBUF_INIT` so
+ * it can be reused after calling this function.
*/
extern char *strbuf_detach(struct strbuf *, size_t *);
diff --git a/sub-process.c b/sub-process.c
index 6dde5062be..8d2a1707cf 100644
--- a/sub-process.c
+++ b/sub-process.c
@@ -77,13 +77,12 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co
{
int err;
struct child_process *process;
- const char *argv[] = { cmd, NULL };
entry->cmd = cmd;
process = &entry->process;
child_process_init(process);
- process->argv = argv;
+ argv_array_push(&process->args, cmd);
process->use_shell = 1;
process->in = -1;
process->out = -1;
diff --git a/submodule.c b/submodule.c
index b12600fc79..63e7094e16 100644
--- a/submodule.c
+++ b/submodule.c
@@ -503,7 +503,7 @@ static void show_submodule_header(struct diff_options *o, const char *path,
if (add_submodule_odb(path)) {
if (!message)
- message = "(not initialized)";
+ message = "(commits not present)";
goto output_header;
}
@@ -1685,7 +1685,7 @@ static int find_first_merges(struct object_array *result, const char *path,
add_object_array(merges.objects[i].item, NULL, result);
}
- free(merges.objects);
+ object_array_clear(&merges);
return result->nr;
}
@@ -1790,7 +1790,7 @@ int merge_submodule(struct object_id *result, const char *path,
print_commit((struct commit *) merges.objects[i].item);
}
- free(merges.objects);
+ object_array_clear(&merges);
return 0;
}
@@ -1997,6 +1997,10 @@ const char *get_superproject_working_tree(void)
return ret;
}
+/*
+ * Put the gitdir for a submodule (given relative to the main
+ * repository worktree) into `buf`, or return -1 on error.
+ */
int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
{
const struct submodule *sub;
diff --git a/submodule.h b/submodule.h
index 6b52133c88..f0da0277a4 100644
--- a/submodule.h
+++ b/submodule.h
@@ -120,7 +120,7 @@ extern int submodule_move_head(const char *path,
/*
* Prepare the "env_array" parameter of a "struct child_process" for executing
- * a submodule by clearing any repo-specific envirionment variables, but
+ * a submodule by clearing any repo-specific environment variables, but
* retaining any config in the environment.
*/
extern void prepare_submodule_repo_env(struct argv_array *out);
diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c
index 81575fe2ab..078dd7e29d 100644
--- a/t/helper/test-line-buffer.c
+++ b/t/helper/test-line-buffer.c
@@ -17,27 +17,17 @@ static uint32_t strtouint32(const char *s)
static void handle_command(const char *command, const char *arg, struct line_buffer *buf)
{
- switch (*command) {
- case 'b':
- if (starts_with(command, "binary ")) {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addch(&sb, '>');
- buffer_read_binary(buf, &sb, strtouint32(arg));
- fwrite(sb.buf, 1, sb.len, stdout);
- strbuf_release(&sb);
- return;
- }
- case 'c':
- if (starts_with(command, "copy ")) {
- buffer_copy_bytes(buf, strtouint32(arg));
- return;
- }
- case 's':
- if (starts_with(command, "skip ")) {
- buffer_skip_bytes(buf, strtouint32(arg));
- return;
- }
- default:
+ if (starts_with(command, "binary ")) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addch(&sb, '>');
+ buffer_read_binary(buf, &sb, strtouint32(arg));
+ fwrite(sb.buf, 1, sb.len, stdout);
+ strbuf_release(&sb);
+ } else if (starts_with(command, "copy ")) {
+ buffer_copy_bytes(buf, strtouint32(arg));
+ } else if (starts_with(command, "skip ")) {
+ buffer_skip_bytes(buf, strtouint32(arg));
+ } else {
die("unrecognized command: %s", command);
}
}
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
index 75fe883aac..630c76d127 100644
--- a/t/helper/test-parse-options.c
+++ b/t/helper/test-parse-options.c
@@ -99,6 +99,8 @@ int cmd_main(int argc, const char **argv)
const char *prefix = "prefix/";
const char *usage[] = {
"test-parse-options <options>",
+ "",
+ "A helper function for the parse-options API.",
NULL
};
struct string_list expect = STRING_LIST_INIT_NODUP;
diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c
index c502fa16d3..829ec3d7d2 100644
--- a/t/helper/test-string-list.c
+++ b/t/helper/test-string-list.c
@@ -108,7 +108,7 @@ int cmd_main(int argc, const char **argv)
* Split by newline, but don't create a string_list item
* for the empty string after the last separator.
*/
- if (sb.buf[sb.len - 1] == '\n')
+ if (sb.len && sb.buf[sb.len - 1] == '\n')
strbuf_setlen(&sb, sb.len - 1);
string_list_split_in_place(&list, sb.buf, '\n', -1);
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 74d2cd76fe..0c2fc81d7b 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -10,6 +10,8 @@ test_description='our own option parser'
cat >expect <<\EOF
usage: test-parse-options <options>
+ A helper function for the parse-options API.
+
--yes get a boolean
-D, --no-doubt begins with 'no-'
-B, --no-fear be brave
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
index 310f93fd30..a859abedf5 100755
--- a/t/t1502-rev-parse-parseopt.sh
+++ b/t/t1502-rev-parse-parseopt.sh
@@ -28,6 +28,9 @@ test_expect_success 'setup optionspec' '
|g,fluf?path short and long option optional argument
|longest=very-long-argument-hint a very long argument hint
|pair=key=value with an equals sign in the hint
+|aswitch help te=t contains? fl*g characters!`
+|bswitch=hint hint has trailing tab character
+|cswitch switch has trailing tab character
|short-hint=a with a one symbol hint
|
|Extras
@@ -35,6 +38,25 @@ test_expect_success 'setup optionspec' '
EOF
'
+test_expect_success 'setup optionspec-no-switches' '
+ sed -e "s/^|//" >optionspec_no_switches <<\EOF
+|some-command [options] <args>...
+|
+|some-command does foo and bar!
+|--
+EOF
+'
+
+test_expect_success 'setup optionspec-only-hidden-switches' '
+ sed -e "s/^|//" >optionspec_only_hidden_switches <<\EOF
+|some-command [options] <args>...
+|
+|some-command does foo and bar!
+|--
+|hidden1* A hidden switch
+EOF
+'
+
test_expect_success 'test --parseopt help output' '
sed -e "s/^|//" >expect <<\END_EXPECT &&
|cat <<\EOF
@@ -62,6 +84,9 @@ test_expect_success 'test --parseopt help output' '
| --longest <very-long-argument-hint>
| a very long argument hint
| --pair <key=value> with an equals sign in the hint
+| --aswitch help te=t contains? fl*g characters!`
+| --bswitch <hint> hint has trailing tab character
+| --cswitch switch has trailing tab character
| --short-hint <a> with a one symbol hint
|
|Extras
@@ -73,19 +98,100 @@ END_EXPECT
test_i18ncmp expect output
'
+test_expect_success 'test --parseopt help output no switches' '
+ sed -e "s/^|//" >expect <<\END_EXPECT &&
+|cat <<\EOF
+|usage: some-command [options] <args>...
+|
+| some-command does foo and bar!
+|
+|EOF
+END_EXPECT
+ test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec_no_switches &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'test --parseopt help output hidden switches' '
+ sed -e "s/^|//" >expect <<\END_EXPECT &&
+|cat <<\EOF
+|usage: some-command [options] <args>...
+|
+| some-command does foo and bar!
+|
+|EOF
+END_EXPECT
+ test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec_only_hidden_switches &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'test --parseopt help-all output hidden switches' '
+ sed -e "s/^|//" >expect <<\END_EXPECT &&
+|cat <<\EOF
+|usage: some-command [options] <args>...
+|
+| some-command does foo and bar!
+|
+| --hidden1 A hidden switch
+|
+|EOF
+END_EXPECT
+ test_expect_code 129 git rev-parse --parseopt -- --help-all > output < optionspec_only_hidden_switches &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'test --parseopt invalid switch help output' '
+ sed -e "s/^|//" >expect <<\END_EXPECT &&
+|error: unknown option `does-not-exist'\''
+|usage: some-command [options] <args>...
+|
+| some-command does foo and bar!
+|
+| -h, --help show the help
+| --foo some nifty option --foo
+| --bar ... some cool option --bar with an argument
+| -b, --baz a short and long option
+|
+|An option group Header
+| -C[...] option C with an optional argument
+| -d, --data[=...] short and long option with an optional argument
+|
+|Argument hints
+| -B <arg> short option required argument
+| --bar2 <arg> long option required argument
+| -e, --fuz <with-space>
+| short and long option required argument
+| -s[<some>] short option optional argument
+| --long[=<data>] long option optional argument
+| -g, --fluf[=<path>] short and long option optional argument
+| --longest <very-long-argument-hint>
+| a very long argument hint
+| --pair <key=value> with an equals sign in the hint
+| --aswitch help te=t contains? fl*g characters!`
+| --bswitch <hint> hint has trailing tab character
+| --cswitch switch has trailing tab character
+| --short-hint <a> with a one symbol hint
+|
+|Extras
+| --extra1 line above used to cause a segfault but no longer does
+|
+END_EXPECT
+ test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec &&
+ test_i18ncmp expect output
+'
+
test_expect_success 'setup expect.1' "
cat > expect <<EOF
-set -- --foo --bar 'ham' -b -- 'arg'
+set -- --foo --bar 'ham' -b --aswitch -- 'arg'
EOF
"
test_expect_success 'test --parseopt' '
- git rev-parse --parseopt -- --foo --bar=ham --baz arg < optionspec > output &&
+ git rev-parse --parseopt -- --foo --bar=ham --baz --aswitch arg < optionspec > output &&
test_cmp expect output
'
test_expect_success 'test --parseopt with mixed options and arguments' '
- git rev-parse --parseopt -- --foo arg --bar=ham --baz < optionspec > output &&
+ git rev-parse --parseopt -- --foo arg --bar=ham --baz --aswitch < optionspec > output &&
test_cmp expect output
'
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index d971649979..3ac7ebf85f 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -381,6 +381,262 @@ test_expect_success 'config information was renamed, too' '
test_must_fail git config branch.s/s.dummy
'
+test_expect_success 'git branch -m correctly renames multiple config sections' '
+ test_when_finished "git checkout master" &&
+ git checkout -b source master &&
+
+ # Assert that a config file with multiple config sections has
+ # those sections preserved...
+ cat >expect <<-\EOF &&
+ branch.dest.key1=value1
+ some.gar.b=age
+ branch.dest.key2=value2
+ EOF
+ cat >config.branch <<\EOF &&
+;; Note the lack of -\EOF above & mixed indenting here. This is
+;; intentional, we are also testing that the formatting of copied
+;; sections is preserved.
+
+;; Comment for source. Tabs
+[branch "source"]
+ ;; Comment for the source value
+ key1 = value1
+;; Comment for some.gar. Spaces
+[some "gar"]
+ ;; Comment for the some.gar value
+ b = age
+;; Comment for source, again. Mixed tabs/spaces.
+[branch "source"]
+ ;; Comment for the source value, again
+ key2 = value2
+EOF
+ cat config.branch >>.git/config &&
+ git branch -m source dest &&
+ git config -f .git/config -l | grep -F -e source -e dest -e some.gar >actual &&
+ test_cmp expect actual &&
+
+ # ...and that the comments for those sections are also
+ # preserved.
+ cat config.branch | sed "s/\"source\"/\"dest\"/" >expect &&
+ sed -n -e "/Note the lack/,\$p" .git/config >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git branch -c dumps usage' '
+ test_expect_code 128 git branch -c 2>err &&
+ test_i18ngrep "branch name required" err
+'
+
+test_expect_success 'git branch --copy dumps usage' '
+ test_expect_code 128 git branch --copy 2>err &&
+ test_i18ngrep "branch name required" err
+'
+
+test_expect_success 'git branch -c d e should work' '
+ git branch -l d &&
+ git reflog exists refs/heads/d &&
+ git config branch.d.dummy Hello &&
+ git branch -c d e &&
+ git reflog exists refs/heads/d &&
+ git reflog exists refs/heads/e &&
+ echo Hello >expect &&
+ git config branch.e.dummy >actual &&
+ test_cmp expect actual &&
+ echo Hello >expect &&
+ git config branch.d.dummy >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git branch --copy is a synonym for -c' '
+ git branch -l copy &&
+ git reflog exists refs/heads/copy &&
+ git config branch.copy.dummy Hello &&
+ git branch --copy copy copy-to &&
+ git reflog exists refs/heads/copy &&
+ git reflog exists refs/heads/copy-to &&
+ echo Hello >expect &&
+ git config branch.copy.dummy >actual &&
+ test_cmp expect actual &&
+ echo Hello >expect &&
+ git config branch.copy-to.dummy >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git branch -c ee ef should copy ee to create branch ef' '
+ git checkout -b ee &&
+ git reflog exists refs/heads/ee &&
+ git config branch.ee.dummy Hello &&
+ git branch -c ee ef &&
+ git reflog exists refs/heads/ee &&
+ git reflog exists refs/heads/ef &&
+ test $(git config branch.ee.dummy) = Hello &&
+ test $(git config branch.ef.dummy) = Hello &&
+ test $(git rev-parse --abbrev-ref HEAD) = ee
+'
+
+test_expect_success 'git branch -c f/f g/g should work' '
+ git branch -l f/f &&
+ git reflog exists refs/heads/f/f &&
+ git config branch.f/f.dummy Hello &&
+ git branch -c f/f g/g &&
+ git reflog exists refs/heads/f/f &&
+ git reflog exists refs/heads/g/g &&
+ test $(git config branch.f/f.dummy) = Hello &&
+ test $(git config branch.g/g.dummy) = Hello
+'
+
+test_expect_success 'git branch -c m2 m2 should work' '
+ git branch -l m2 &&
+ git reflog exists refs/heads/m2 &&
+ git config branch.m2.dummy Hello &&
+ git branch -c m2 m2 &&
+ git reflog exists refs/heads/m2 &&
+ test $(git config branch.m2.dummy) = Hello
+'
+
+test_expect_success 'git branch -c zz zz/zz should fail' '
+ git branch -l zz &&
+ git reflog exists refs/heads/zz &&
+ test_must_fail git branch -c zz zz/zz
+'
+
+test_expect_success 'git branch -c b/b b should fail' '
+ git branch -l b/b &&
+ test_must_fail git branch -c b/b b
+'
+
+test_expect_success 'git branch -C o/q o/p should work when o/p exists' '
+ git branch -l o/q &&
+ git reflog exists refs/heads/o/q &&
+ git reflog exists refs/heads/o/p &&
+ git branch -C o/q o/p
+'
+
+test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' '
+ git reflog exists refs/heads/o/q &&
+ git reflog exists refs/heads/o/p &&
+ git branch -c -f o/q o/p
+'
+
+test_expect_success 'git branch -c qq rr/qq should fail when r exists' '
+ git branch qq &&
+ git branch rr &&
+ test_must_fail git branch -c qq rr/qq
+'
+
+test_expect_success 'git branch -C b1 b2 should fail when b2 is checked out' '
+ git branch b1 &&
+ git checkout -b b2 &&
+ test_must_fail git branch -C b1 b2
+'
+
+test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' '
+ git checkout -b c1 &&
+ git branch c2 &&
+ git branch -C c1 c2 &&
+ test $(git rev-parse --abbrev-ref HEAD) = c1
+'
+
+test_expect_success 'git branch -C c1 c2 should never touch HEAD' '
+ msg="Branch: copied refs/heads/c1 to refs/heads/c2" &&
+ ! grep "$msg$" .git/logs/HEAD
+'
+
+test_expect_success 'git branch -C master should work when master is checked out' '
+ git checkout master &&
+ git branch -C master
+'
+
+test_expect_success 'git branch -C master master should work when master is checked out' '
+ git checkout master &&
+ git branch -C master master
+'
+
+test_expect_success 'git branch -C master5 master5 should work when master is checked out' '
+ git checkout master &&
+ git branch master5 &&
+ git branch -C master5 master5
+'
+
+test_expect_success 'git branch -C ab cd should overwrite existing config for cd' '
+ git branch -l cd &&
+ git reflog exists refs/heads/cd &&
+ git config branch.cd.dummy CD &&
+ git branch -l ab &&
+ git reflog exists refs/heads/ab &&
+ git config branch.ab.dummy AB &&
+ git branch -C ab cd &&
+ git reflog exists refs/heads/ab &&
+ git reflog exists refs/heads/cd &&
+ test $(git config branch.ab.dummy) = AB &&
+ test $(git config branch.cd.dummy) = AB
+'
+
+test_expect_success 'git branch -c correctly copies multiple config sections' '
+ FOO=1 &&
+ export FOO &&
+ test_when_finished "git checkout master" &&
+ git checkout -b source2 master &&
+
+ # Assert that a config file with multiple config sections has
+ # those sections preserved...
+ cat >expect <<-\EOF &&
+ branch.source2.key1=value1
+ branch.dest2.key1=value1
+ more.gar.b=age
+ branch.source2.key2=value2
+ branch.dest2.key2=value2
+ EOF
+ cat >config.branch <<\EOF &&
+;; Note the lack of -\EOF above & mixed indenting here. This is
+;; intentional, we are also testing that the formatting of copied
+;; sections is preserved.
+
+;; Comment for source2. Tabs
+[branch "source2"]
+ ;; Comment for the source2 value
+ key1 = value1
+;; Comment for more.gar. Spaces
+[more "gar"]
+ ;; Comment for the more.gar value
+ b = age
+;; Comment for source2, again. Mixed tabs/spaces.
+[branch "source2"]
+ ;; Comment for the source2 value, again
+ key2 = value2
+EOF
+ cat config.branch >>.git/config &&
+ git branch -c source2 dest2 &&
+ git config -f .git/config -l | grep -F -e source2 -e dest2 -e more.gar >actual &&
+ test_cmp expect actual &&
+
+ # ...and that the comments and formatting for those sections
+ # is also preserved.
+ cat >expect <<\EOF &&
+;; Comment for source2. Tabs
+[branch "source2"]
+ ;; Comment for the source2 value
+ key1 = value1
+;; Comment for more.gar. Spaces
+[branch "dest2"]
+ ;; Comment for the source2 value
+ key1 = value1
+;; Comment for more.gar. Spaces
+[more "gar"]
+ ;; Comment for the more.gar value
+ b = age
+;; Comment for source2, again. Mixed tabs/spaces.
+[branch "source2"]
+ ;; Comment for the source2 value, again
+ key2 = value2
+[branch "dest2"]
+ ;; Comment for the source2 value, again
+ key2 = value2
+EOF
+ sed -n -e "/Comment for source2/,\$p" .git/config >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'deleting a symref' '
git branch target &&
git symbolic-ref refs/heads/symref refs/heads/target &&
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index d2aec0f38b..ee6787614c 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -253,13 +253,7 @@ test_expect_success '%(color) omitted without tty' '
'
test_expect_success TTY '%(color) present with tty' '
- test_terminal env TERM=vt100 git branch $color_args >actual.raw &&
- test_decode_color <actual.raw >actual &&
- test_cmp expect.color actual
-'
-
-test_expect_success 'color.branch=always overrides auto-color' '
- git -c color.branch=always branch $color_args >actual.raw &&
+ test_terminal git branch $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
diff --git a/t/t3205-branch-color.sh b/t/t3205-branch-color.sh
index 9343550f50..4f1e16bb44 100755
--- a/t/t3205-branch-color.sh
+++ b/t/t3205-branch-color.sh
@@ -12,7 +12,6 @@ test_expect_success 'set up some sample branches' '
# choose non-default colors to make sure config
# is taking effect
test_expect_success 'set up some color config' '
- git config color.branch always &&
git config color.branch.local blue &&
git config color.branch.remote yellow &&
git config color.branch.current cyan
@@ -24,7 +23,7 @@ test_expect_success 'regular output shows colors' '
<BLUE>other<RESET>
<YELLOW>remotes/origin/master<RESET>
EOF
- git branch -a >actual.raw &&
+ git branch --color -a >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect actual
'
@@ -36,7 +35,7 @@ test_expect_success 'verbose output shows colors' '
<BLUE>other <RESET> $oid foo
<YELLOW>remotes/origin/master<RESET> $oid foo
EOF
- git branch -v -a >actual.raw &&
+ git branch --color -v -a >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect actual
'
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 37821d2454..3704dbb2ec 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1249,20 +1249,13 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
'
-cat >expect <<EOF
-Warning: the command isn't recognized in the following line:
- - badcmd $(git rev-list --oneline -1 master~1)
-
-You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
-Or you can abort the rebase with 'git rebase --abort'.
-EOF
-
test_expect_success 'static check of bad command' '
rebase_setup_and_clean bad-cmd &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
git rebase -i --root 2>actual &&
- test_i18ncmp expect actual &&
+ test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual &&
+ test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
git rebase --continue &&
test E = $(git cat-file commit HEAD | sed -ne \$p) &&
@@ -1284,20 +1277,13 @@ test_expect_success 'tabs and spaces are accepted in the todolist' '
test E = $(git cat-file commit HEAD | sed -ne \$p)
'
-cat >expect <<EOF
-Warning: the SHA-1 is missing or isn't a commit in the following line:
- - edit XXXXXXX False commit
-
-You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
-Or you can abort the rebase with 'git rebase --abort'.
-EOF
-
test_expect_success 'static check of bad SHA-1' '
rebase_setup_and_clean bad-sha &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
git rebase -i --root 2>actual &&
- test_i18ncmp expect actual &&
+ test_i18ngrep "edit XXXXXXX False commit" actual &&
+ test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
git rebase --continue &&
test E = $(git cat-file commit HEAD | sed -ne \$p)
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index 5848949ec3..e364c12622 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -271,6 +271,18 @@ test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' '
test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l)
'
+test_expect_success 'autosquash with empty custom instructionFormat' '
+ git reset --hard base &&
+ test_commit empty-instructionFormat-test &&
+ (
+ set_cat_todo_editor &&
+ test_must_fail git -c rebase.instructionFormat= \
+ rebase --autosquash --force -i HEAD^ >actual &&
+ git log -1 --format="pick %h %s" >expect &&
+ test_cmp expect actual
+ )
+'
+
set_backup_editor () {
write_script backup-editor.sh <<-\EOF
cp "$1" .git/backup-"$(basename "$1")"
@@ -278,7 +290,7 @@ set_backup_editor () {
test_set_editor "$PWD/backup-editor.sh"
}
-test_expect_failure 'autosquash with multiple empty patches' '
+test_expect_success 'autosquash with multiple empty patches' '
test_tick &&
git commit --allow-empty -m "empty" &&
test_tick &&
@@ -304,4 +316,18 @@ test_expect_success 'extra spaces after fixup!' '
test $base = $parent
'
+test_expect_success 'wrapped original subject' '
+ if test -d .git/rebase-merge; then git rebase --abort; fi &&
+ base=$(git rev-parse HEAD) &&
+ echo "wrapped subject" >wrapped &&
+ git add wrapped &&
+ test_tick &&
+ git commit --allow-empty -m "$(printf "To\nfixup")" &&
+ test_tick &&
+ git commit --allow-empty -m "fixup! To fixup" &&
+ git rebase -i --autosquash --keep-empty HEAD~2 &&
+ parent=$(git rev-parse HEAD^) &&
+ test $base = $parent
+'
+
test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 2f3e7cea64..a49c12c79b 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -2,6 +2,7 @@
test_description='add -i basic tests'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
if ! test_have_prereq PERL
then
@@ -380,14 +381,11 @@ test_expect_success 'patch mode ignores unmerged entries' '
test_cmp expected diff
'
-test_expect_success 'diffs can be colorized' '
+test_expect_success TTY 'diffs can be colorized' '
git reset --hard &&
- # force color even though the test script has no terminal
- test_config color.ui always &&
-
echo content >test &&
- printf y | git add -p >output 2>&1 &&
+ printf y | test_terminal git add -p >output 2>&1 &&
# We do not want to depend on the exact coloring scheme
# git uses for diffs, so just check that we saw some kind of color.
@@ -485,4 +483,14 @@ test_expect_success 'hunk-editing handles custom comment char' '
git diff --exit-code
'
+test_expect_success 'add -p works even with color.ui=always' '
+ git reset --hard &&
+ echo change >>file &&
+ test_config color.ui always &&
+ echo y | git add -p &&
+ echo file >expect &&
+ git diff --cached --name-only >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index d09acfe48e..c515e3e53f 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -90,6 +90,14 @@ test_expect_success setup '
git commit -m "Rearranged lines in dir/sub" &&
git checkout master &&
+ GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+ git checkout -b mode initial &&
+ git update-index --chmod=+x file0 &&
+ git commit -m "update mode" &&
+ git checkout -f master &&
+
git config diff.renames false &&
git show-branch
@@ -192,6 +200,10 @@ diff-tree --pretty side
diff-tree --pretty -p side
diff-tree --pretty --patch-with-stat side
+diff-tree initial mode
+diff-tree --stat initial mode
+diff-tree --summary initial mode
+
diff-tree master
diff-tree -p master
diff-tree -p -m master
diff --git a/t/t4013/diff.diff-tree_--stat_initial_mode b/t/t4013/diff.diff-tree_--stat_initial_mode
new file mode 100644
index 0000000000..0e5943c2c6
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--stat_initial_mode
@@ -0,0 +1,4 @@
+$ git diff-tree --stat initial mode
+ file0 | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--summary_initial_mode b/t/t4013/diff.diff-tree_--summary_initial_mode
new file mode 100644
index 0000000000..25846b6af8
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--summary_initial_mode
@@ -0,0 +1,3 @@
+$ git diff-tree --summary initial mode
+ mode change 100644 => 100755 file0
+$
diff --git a/t/t4013/diff.diff-tree_initial_mode b/t/t4013/diff.diff-tree_initial_mode
new file mode 100644
index 0000000000..c47c09423e
--- /dev/null
+++ b/t/t4013/diff.diff-tree_initial_mode
@@ -0,0 +1,3 @@
+$ git diff-tree initial mode
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M file0
+$
diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all
index b345b2ebfa..2afe91f116 100644
--- a/t/t4013/diff.log_--decorate=full_--all
+++ b/t/t4013/diff.log_--decorate=full_--all
@@ -1,4 +1,10 @@
$ git log --decorate=full --all
+commit b7e0bc69303b488b47deca799a7d723971dfa6cd (refs/heads/mode)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ update mode
+
commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange)
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:06:00 2006 +0000
diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all
index 3aa16a9e42..d0f308ab2b 100644
--- a/t/t4013/diff.log_--decorate_--all
+++ b/t/t4013/diff.log_--decorate_--all
@@ -1,4 +1,10 @@
$ git log --decorate --all
+commit b7e0bc69303b488b47deca799a7d723971dfa6cd (mode)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ update mode
+
commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange)
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:06:00 2006 +0000
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 12d182dc1b..3bca958863 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -802,7 +802,6 @@ test_expect_success 'combined diff with autocrlf conversion' '
# Start testing the colored format for whitespace checks
test_expect_success 'setup diff colors' '
- git config color.diff always &&
git config color.diff.plain normal &&
git config color.diff.meta bold &&
git config color.diff.frag cyan &&
@@ -821,7 +820,7 @@ test_expect_success 'diff that introduces a line with only tabs' '
echo "test" >x &&
git commit -m "initial" x &&
echo "{NTN}" | tr "NT" "\n\t" >>x &&
- git -c color.diff=always diff | test_decode_color >current &&
+ git diff --color | test_decode_color >current &&
cat >expected <<-\EOF &&
<BOLD>diff --git a/x b/x<RESET>
@@ -851,7 +850,7 @@ test_expect_success 'diff that introduces and removes ws breakages' '
echo "2. and a new line "
} >x &&
- git -c color.diff=always diff |
+ git diff --color |
test_decode_color >current &&
cat >expected <<-\EOF &&
@@ -923,15 +922,15 @@ test_expect_success 'ws-error-highlight test setup' '
test_expect_success 'test --ws-error-highlight option' '
- git -c color.diff=always diff --ws-error-highlight=default,old |
+ git diff --color --ws-error-highlight=default,old |
test_decode_color >current &&
test_cmp expect.default-old current &&
- git -c color.diff=always diff --ws-error-highlight=all |
+ git diff --color --ws-error-highlight=all |
test_decode_color >current &&
test_cmp expect.all current &&
- git -c color.diff=always diff --ws-error-highlight=none |
+ git diff --color --ws-error-highlight=none |
test_decode_color >current &&
test_cmp expect.none current
@@ -939,15 +938,15 @@ test_expect_success 'test --ws-error-highlight option' '
test_expect_success 'test diff.wsErrorHighlight config' '
- git -c color.diff=always -c diff.wsErrorHighlight=default,old diff |
+ git -c diff.wsErrorHighlight=default,old diff --color |
test_decode_color >current &&
test_cmp expect.default-old current &&
- git -c color.diff=always -c diff.wsErrorHighlight=all diff |
+ git -c diff.wsErrorHighlight=all diff --color |
test_decode_color >current &&
test_cmp expect.all current &&
- git -c color.diff=always -c diff.wsErrorHighlight=none diff |
+ git -c diff.wsErrorHighlight=none diff --color |
test_decode_color >current &&
test_cmp expect.none current
@@ -955,18 +954,18 @@ test_expect_success 'test diff.wsErrorHighlight config' '
test_expect_success 'option overrides diff.wsErrorHighlight' '
- git -c color.diff=always -c diff.wsErrorHighlight=none \
- diff --ws-error-highlight=default,old |
+ git -c diff.wsErrorHighlight=none \
+ diff --color --ws-error-highlight=default,old |
test_decode_color >current &&
test_cmp expect.default-old current &&
- git -c color.diff=always -c diff.wsErrorHighlight=default \
- diff --ws-error-highlight=all |
+ git -c diff.wsErrorHighlight=default \
+ diff --color --ws-error-highlight=all |
test_decode_color >current &&
test_cmp expect.all current &&
- git -c color.diff=always -c diff.wsErrorHighlight=all \
- diff --ws-error-highlight=none |
+ git -c diff.wsErrorHighlight=all \
+ diff --color --ws-error-highlight=none |
test_decode_color >current &&
test_cmp expect.none current
@@ -986,7 +985,7 @@ test_expect_success 'detect moved code, complete file' '
git mv test.c main.c &&
test_config color.diff.oldMoved "normal red" &&
test_config color.diff.newMoved "normal green" &&
- git diff HEAD --color-moved=zebra --no-renames | test_decode_color >actual &&
+ git diff HEAD --color-moved=zebra --color --no-renames | test_decode_color >actual &&
cat >expected <<-\EOF &&
<BOLD>diff --git a/main.c b/main.c<RESET>
<BOLD>new file mode 100644<RESET>
@@ -1087,7 +1086,7 @@ test_expect_success 'detect malicious moved code, inside file' '
bar();
}
EOF
- git diff HEAD --no-renames --color-moved=zebra| test_decode_color >actual &&
+ git diff HEAD --no-renames --color-moved=zebra --color | test_decode_color >actual &&
cat <<-\EOF >expected &&
<BOLD>diff --git a/main.c b/main.c<RESET>
<BOLD>index 27a619c..7cf9336 100644<RESET>
@@ -1136,7 +1135,7 @@ test_expect_success 'plain moved code, inside file' '
test_config color.diff.oldMovedAlternative "blue" &&
test_config color.diff.newMovedAlternative "yellow" &&
# needs previous test as setup
- git diff HEAD --no-renames --color-moved=plain| test_decode_color >actual &&
+ git diff HEAD --no-renames --color-moved=plain --color | test_decode_color >actual &&
cat <<-\EOF >expected &&
<BOLD>diff --git a/main.c b/main.c<RESET>
<BOLD>index 27a619c..7cf9336 100644<RESET>
@@ -1227,7 +1226,7 @@ test_expect_success 'detect permutations inside moved code -- dimmed_zebra' '
test_config color.diff.newMovedDimmed "normal cyan" &&
test_config color.diff.oldMovedAlternativeDimmed "normal blue" &&
test_config color.diff.newMovedAlternativeDimmed "normal yellow" &&
- git diff HEAD --no-renames --color-moved=dimmed_zebra |
+ git diff HEAD --no-renames --color-moved=dimmed_zebra --color |
grep -v "index" |
test_decode_color >actual &&
cat <<-\EOF >expected &&
@@ -1271,7 +1270,7 @@ test_expect_success 'cmd option assumes configured colored-moved' '
test_config color.diff.oldMovedAlternativeDimmed "normal blue" &&
test_config color.diff.newMovedAlternativeDimmed "normal yellow" &&
test_config diff.colorMoved zebra &&
- git diff HEAD --no-renames --color-moved |
+ git diff HEAD --no-renames --color-moved --color |
grep -v "index" |
test_decode_color >actual &&
cat <<-\EOF >expected &&
@@ -1343,7 +1342,7 @@ line 4
EOF
test_config color.diff.oldMoved "magenta" &&
test_config color.diff.newMoved "cyan" &&
- git diff HEAD --no-renames --color-moved |
+ git diff HEAD --no-renames --color-moved --color |
grep -v "index" |
test_decode_color >actual &&
cat <<-\EOF >expected &&
@@ -1364,7 +1363,7 @@ EOF
EOF
test_cmp expected actual &&
- git diff HEAD --no-renames -w --color-moved |
+ git diff HEAD --no-renames -w --color-moved --color |
grep -v "index" |
test_decode_color >actual &&
cat <<-\EOF >expected &&
@@ -1403,7 +1402,7 @@ test_expect_success '--color-moved block at end of diff output respects MIN_ALNU
irrelevant_line
EOF
- git diff HEAD --color-moved=zebra --no-renames |
+ git diff HEAD --color-moved=zebra --color --no-renames |
grep -v "index" |
test_decode_color >actual &&
cat >expected <<-\EOF &&
@@ -1442,7 +1441,7 @@ test_expect_success '--color-moved respects MIN_ALNUM_COUNT' '
nineteen chars 456789
EOF
- git diff HEAD --color-moved=zebra --no-renames |
+ git diff HEAD --color-moved=zebra --color --no-renames |
grep -v "index" |
test_decode_color >actual &&
cat >expected <<-\EOF &&
@@ -1485,7 +1484,7 @@ test_expect_success '--color-moved treats adjacent blocks as separate for MIN_AL
7charsA
EOF
- git diff HEAD --color-moved=zebra --no-renames | grep -v "index" | test_decode_color >actual &&
+ git diff HEAD --color-moved=zebra --color --no-renames | grep -v "index" | test_decode_color >actual &&
cat >expected <<-\EOF &&
<BOLD>diff --git a/bar b/bar<RESET>
<BOLD>--- a/bar<RESET>
@@ -1519,7 +1518,7 @@ test_expect_success 'move detection with submodules' '
echo foul >bananas/recipe &&
echo ripe >fruit.t &&
- git diff --submodule=diff --color-moved >actual &&
+ git diff --submodule=diff --color-moved --color >actual &&
# no move detection as the moved line is across repository boundaries.
test_decode_color <actual >decoded_actual &&
@@ -1527,7 +1526,7 @@ test_expect_success 'move detection with submodules' '
! grep BRED decoded_actual &&
# nor did we mess with it another way
- git diff --submodule=diff | test_decode_color >expect &&
+ git diff --submodule=diff --color | test_decode_color >expect &&
test_cmp expect decoded_actual
'
diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh
index cd70fd5192..49bca7b48d 100755
--- a/t/t4059-diff-submodule-not-initialized.sh
+++ b/t/t4059-diff-submodule-not-initialized.sh
@@ -95,7 +95,7 @@ test_expect_success 'submodule not initialized in new clone' '
git clone . sm3 &&
git -C sm3 diff-tree -p --no-commit-id --submodule=log HEAD >actual &&
cat >expected <<-EOF &&
- Submodule sm1 $smhead1...$smhead2 (not initialized)
+ Submodule sm1 $smhead1...$smhead2 (commits not present)
EOF
test_cmp expected actual
'
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 36d120c969..8f155da7a5 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -750,7 +750,7 @@ test_expect_success 'log.decorate config parsing' '
'
test_expect_success TTY 'log output on a TTY' '
- git log --oneline --decorate >expect.short &&
+ git log --color --oneline --decorate >expect.short &&
test_terminal git log --oneline >actual &&
test_cmp expect.short actual
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index ec5f530102..591f35daaf 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -544,7 +544,7 @@ Signed-off-by: A U Thor
EOF
unfold () {
- perl -0pe 's/\n\s+/ /'
+ perl -0pe 's/\n\s+/ /g'
}
test_expect_success 'set up trailer tests' '
@@ -588,8 +588,8 @@ test_expect_success '%(trailers:unfold) unfolds trailers' '
'
test_expect_success ':only and :unfold work together' '
- git log --no-walk --pretty="%(trailers:only:unfold)" >actual &&
- git log --no-walk --pretty="%(trailers:unfold:only)" >reverse &&
+ git log --no-walk --pretty="%(trailers:only,unfold)" >actual &&
+ git log --no-walk --pretty="%(trailers:unfold,only)" >reverse &&
test_cmp actual reverse &&
{
grep -v patch.description <trailers | unfold &&
diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh
index 82c33b88e7..08c210f035 100755
--- a/t/t5150-request-pull.sh
+++ b/t/t5150-request-pull.sh
@@ -68,7 +68,7 @@ test_expect_success 'setup: two scripts for reading pull requests' '
cat <<-\EOT >read-request.sed &&
#!/bin/sed -nf
# Note that a request could ask for "tag $tagname"
- / in the git repository at:$/!d
+ / in the Git repository at:$/!d
n
/^$/ n
s/ tag \([^ ]*\)$/ tag--\1/
@@ -192,7 +192,7 @@ test_expect_success 'pull request format' '
SUBJECT (DATE)
- are available in the git repository at:
+ are available in the Git repository at:
URL BRANCH
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index b326d550f3..25a9c65dc5 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -208,29 +208,13 @@ do
has_no_color actual
'
- test_expect_success "$desc enables colors for color.diff" '
- git -c color.diff=always log --format=$color -1 >actual &&
- has_color actual
- '
-
- test_expect_success "$desc enables colors for color.ui" '
- git -c color.ui=always log --format=$color -1 >actual &&
- has_color actual
- '
-
test_expect_success "$desc respects --color" '
git log --format=$color -1 --color >actual &&
has_color actual
'
- test_expect_success "$desc respects --no-color" '
- git -c color.ui=always log --format=$color -1 --no-color >actual &&
- has_no_color actual
- '
-
test_expect_success TTY "$desc respects --color=auto (stdout is tty)" '
- test_terminal env TERM=vt100 \
- git log --format=$color -1 --color=auto >actual &&
+ test_terminal git log --format=$color -1 --color=auto >actual &&
has_color actual
'
@@ -241,6 +225,11 @@ do
has_no_color actual
)
'
+
+ test_expect_success TTY "$desc respects --no-color" '
+ test_terminal git log --format=$color -1 --no-color >actual &&
+ has_no_color actual
+ '
done
test_expect_success '%C(always,...) enables color even without tty' '
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 3d45dc2955..1c0e8659d9 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -182,10 +182,41 @@ check_describe "test2-lightweight-*" --tags --match="test2-*"
check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^
-check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^
+check_describe "test2-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^
check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^
+check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test3-*" HEAD
+
+check_describe "test1-lightweight-*" --long --tags --match="test3-*" --match="test1-*" HEAD
+
+test_expect_success 'set-up branches' '
+ git branch branch_A A &&
+ git branch branch_C c &&
+ git update-ref refs/remotes/origin/remote_branch_A "A^{commit}" &&
+ git update-ref refs/remotes/origin/remote_branch_C "c^{commit}" &&
+ git update-ref refs/original/original_branch_A test-annotated~2
+'
+
+check_describe "heads/branch_A*" --all --match="branch_*" --exclude="branch_C" HEAD
+
+check_describe "remotes/origin/remote_branch_A*" --all --match="origin/remote_branch_*" --exclude="origin/remote_branch_C" HEAD
+
+check_describe "original/original_branch_A*" --all test-annotated~1
+
+test_expect_success '--match does not work for other types' '
+ test_must_fail git describe --all --match="*original_branch_*" test-annotated~1
+'
+
+test_expect_success '--exclude does not work for other types' '
+ R=$(git describe --all --exclude="any_pattern_even_not_matching" test-annotated~1) &&
+ case "$R" in
+ *original_branch_A*) echo "fail: Found unknown reference $R with --exclude"
+ false;;
+ *) echo ok: Found some known type;;
+ esac
+'
+
test_expect_success 'name-rev with exact tags' '
echo A >expect &&
tag_object=$(git rev-parse refs/tags/A) &&
diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh
index 9dd5cde5fc..eb829fce97 100755
--- a/t/t6132-pathspec-exclude.sh
+++ b/t/t6132-pathspec-exclude.sh
@@ -25,7 +25,7 @@ EOF
test_cmp expect actual
'
-test_expect_success 'exclude only no longer errors out' '
+test_expect_success 'exclude only pathspec uses default implicit pathspec' '
git log --oneline --format=%s -- . ":(exclude)sub" >expect &&
git log --oneline --format=%s -- ":(exclude)sub" >actual &&
test_cmp expect actual
@@ -183,4 +183,15 @@ EOF
test_cmp expect actual
'
+test_expect_success 'multiple exclusions' '
+ git ls-files -- ":^*/file2" ":^sub2" >actual &&
+ cat <<-\EOF >expect &&
+ file
+ sub/file
+ sub/sub/file
+ sub/sub/sub/file
+ EOF
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 2274a4b733..416ff7d0b8 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -51,6 +51,7 @@ test_atom() {
}
test_atom head refname refs/heads/master
+test_atom head refname: refs/heads/master
test_atom head refname:short master
test_atom head refname:lstrip=1 heads/master
test_atom head refname:lstrip=2 master
@@ -425,8 +426,7 @@ test_expect_success 'set up color tests' '
'
test_expect_success TTY '%(color) shows color with a tty' '
- test_terminal env TERM=vt100 \
- git for-each-ref --format="$color_format" >actual.raw &&
+ test_terminal git for-each-ref --format="$color_format" >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expected.color actual
'
@@ -436,8 +436,8 @@ test_expect_success '%(color) does not show color without tty' '
test_cmp expected.bare actual
'
-test_expect_success 'color.ui=always can override tty check' '
- git -c color.ui=always for-each-ref --format="$color_format" >actual.raw &&
+test_expect_success '--color can override tty check' '
+ git for-each-ref --color --format="$color_format" >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expected.color actual
'
@@ -605,18 +605,104 @@ test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
cat >trailers <<EOF
Reviewed-by: A U Thor <author@example.com>
Signed-off-by: A U Thor <author@example.com>
+[ v2 updated patch description ]
+Acked-by: A U Thor
+ <author@example.com>
EOF
-test_expect_success 'basic atom: head contents:trailers' '
+unfold () {
+ perl -0pe 's/\n\s+/ /g'
+}
+
+test_expect_success 'set up trailers for next test' '
echo "Some contents" > two &&
git add two &&
- git commit -F - <<-EOF &&
+ git commit -F - <<-EOF
trailers: this commit message has trailers
Some message contents
$(cat trailers)
EOF
+'
+
+test_expect_success '%(trailers:unfold) unfolds trailers' '
+ git for-each-ref --format="%(trailers:unfold)" refs/heads/master >actual &&
+ {
+ unfold <trailers
+ echo
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(trailers:only) shows only "key: value" trailers' '
+ git for-each-ref --format="%(trailers:only)" refs/heads/master >actual &&
+ {
+ grep -v patch.description <trailers &&
+ echo
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(trailers:only) and %(trailers:unfold) work together' '
+ git for-each-ref --format="%(trailers:only,unfold)" refs/heads/master >actual &&
+ git for-each-ref --format="%(trailers:unfold,only)" refs/heads/master >reverse &&
+ test_cmp actual reverse &&
+ {
+ grep -v patch.description <trailers | unfold &&
+ echo
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(contents:trailers:unfold) unfolds trailers' '
+ git for-each-ref --format="%(contents:trailers:unfold)" refs/heads/master >actual &&
+ {
+ unfold <trailers
+ echo
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(contents:trailers:only) shows only "key: value" trailers' '
+ git for-each-ref --format="%(contents:trailers:only)" refs/heads/master >actual &&
+ {
+ grep -v patch.description <trailers &&
+ echo
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(contents:trailers:only) and %(contents:trailers:unfold) work together' '
+ git for-each-ref --format="%(contents:trailers:only,unfold)" refs/heads/master >actual &&
+ git for-each-ref --format="%(contents:trailers:unfold,only)" refs/heads/master >reverse &&
+ test_cmp actual reverse &&
+ {
+ grep -v patch.description <trailers | unfold &&
+ echo
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(trailers) rejects unknown trailers arguments' '
+ # error message cannot be checked under i18n
+ cat >expect <<-EOF &&
+ fatal: unknown %(trailers) argument: unsupported
+ EOF
+ test_must_fail git for-each-ref --format="%(trailers:unsupported)" 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '%(contents:trailers) rejects unknown trailers arguments' '
+ # error message cannot be checked under i18n
+ cat >expect <<-EOF &&
+ fatal: unknown %(trailers) argument: unsupported
+ EOF
+ test_must_fail git for-each-ref --format="%(contents:trailers:unsupported)" 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'basic atom: head contents:trailers' '
git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual &&
sanitize_pgp <actual >actual.clean &&
# git for-each-ref ends with a blank line
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index b545c33f83..4e62c505fc 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1907,13 +1907,13 @@ test_expect_success '%(color) omitted without tty' '
'
test_expect_success TTY '%(color) present with tty' '
- test_terminal env TERM=vt100 git tag $color_args >actual.raw &&
+ test_terminal git tag $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
-test_expect_success 'color.ui=always overrides auto-color' '
- git -c color.ui=always tag $color_args >actual.raw &&
+test_expect_success '--color overrides auto-color' '
+ git tag --color $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index 9128ec5acd..f0f1abd1c2 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -239,7 +239,7 @@ test_expect_success 'no color when stdout is a regular file' '
test_expect_success TTY 'color when writing to a pager' '
rm -f paginated.out &&
test_config color.ui auto &&
- test_terminal env TERM=vt100 git log &&
+ test_terminal git log &&
colorful paginated.out
'
@@ -247,7 +247,7 @@ test_expect_success TTY 'colors are suppressed by color.pager' '
rm -f paginated.out &&
test_config color.ui auto &&
test_config color.pager false &&
- test_terminal env TERM=vt100 git log &&
+ test_terminal git log &&
! colorful paginated.out
'
@@ -266,7 +266,7 @@ test_expect_success 'color when writing to a file intended for a pager' '
test_expect_success TTY 'colors are sent to pager for external commands' '
test_config alias.externallog "!git log" &&
test_config color.ui auto &&
- test_terminal env TERM=vt100 git -p externallog &&
+ test_terminal git -p externallog &&
colorful paginated.out
'
diff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh
index 556e1850e2..1bf9789c8a 100755
--- a/t/t7301-clean-interactive.sh
+++ b/t/t7301-clean-interactive.sh
@@ -3,6 +3,7 @@
test_description='git clean -i basic tests'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
test_expect_success 'setup' '
@@ -472,10 +473,10 @@ test_expect_success 'git clean -id with prefix and path (ask)' '
'
-test_expect_success 'git clean -i paints the header in HEADER color' '
+test_expect_success TTY 'git clean -i paints the header in HEADER color' '
>a.out &&
echo q |
- git -c color.ui=always clean -i |
+ test_terminal git clean -i |
test_decode_color |
head -n 1 >header &&
# not i18ngrep
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 034914a14f..6f083c4d68 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -406,6 +406,14 @@ test_expect_success 'submodule update - command in .git/config' '
)
'
+test_expect_success 'submodule update - command in .gitmodules is ignored' '
+ test_when_finished "git -C super reset --hard HEAD^" &&
+ git -C super config -f .gitmodules submodule.submodule.update "!false" &&
+ git -C super commit -a -m "add command to .gitmodules file" &&
+ git -C super/submodule reset --hard $submodulesha1^ &&
+ git -C super submodule update submodule
+'
+
cat << EOF >expect
Execution of 'false $submodulesha1' failed in submodule path 'submodule'
EOF
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index 725687d5d5..d33a3cb331 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -171,9 +171,9 @@ test_expect_success 'verbose' '
test_expect_success 'verbose respects diff config' '
- test_config color.diff always &&
+ test_config diff.noprefix true &&
git status -v >actual &&
- grep "\[1mdiff --git" actual
+ grep "diff --git negative negative" actual
'
mesg_with_comment_and_newlines='
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 43d19a9b22..50052e2872 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -6,6 +6,7 @@
test_description='git status'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
test_expect_success 'status -h in broken repository' '
git config --global advice.statusuoption false &&
@@ -667,7 +668,7 @@ test_expect_success 'setup unique colors' '
'
-test_expect_success 'status with color.ui' '
+test_expect_success TTY 'status with color.ui' '
cat >expect <<\EOF &&
On branch <GREEN>master<RESET>
Your branch and '\''upstream'\'' have diverged,
@@ -694,14 +695,14 @@ Untracked files:
<BLUE>untracked<RESET>
EOF
- test_config color.ui always &&
- git status | test_decode_color >output &&
+ test_config color.ui auto &&
+ test_terminal git status | test_decode_color >output &&
test_i18ncmp expect output
'
-test_expect_success 'status with color.status' '
- test_config color.status always &&
- git status | test_decode_color >output &&
+test_expect_success TTY 'status with color.status' '
+ test_config color.status auto &&
+ test_terminal git status | test_decode_color >output &&
test_i18ncmp expect output
'
@@ -714,19 +715,19 @@ cat >expect <<\EOF
<BLUE>??<RESET> untracked
EOF
-test_expect_success 'status -s with color.ui' '
+test_expect_success TTY 'status -s with color.ui' '
- git config color.ui always &&
- git status -s | test_decode_color >output &&
+ git config color.ui auto &&
+ test_terminal git status -s | test_decode_color >output &&
test_cmp expect output
'
-test_expect_success 'status -s with color.status' '
+test_expect_success TTY 'status -s with color.status' '
git config --unset color.ui &&
- git config color.status always &&
- git status -s | test_decode_color >output &&
+ git config color.status auto &&
+ test_terminal git status -s | test_decode_color >output &&
test_cmp expect output
'
@@ -741,9 +742,9 @@ cat >expect <<\EOF
<BLUE>??<RESET> untracked
EOF
-test_expect_success 'status -s -b with color.status' '
+test_expect_success TTY 'status -s -b with color.status' '
- git status -s -b | test_decode_color >output &&
+ test_terminal git status -s -b | test_decode_color >output &&
test_i18ncmp expect output
'
@@ -757,20 +758,20 @@ A dir2/added
?? untracked
EOF
-test_expect_success 'status --porcelain ignores color.ui' '
+test_expect_success TTY 'status --porcelain ignores color.ui' '
git config --unset color.status &&
- git config color.ui always &&
- git status --porcelain | test_decode_color >output &&
+ git config color.ui auto &&
+ test_terminal git status --porcelain | test_decode_color >output &&
test_cmp expect output
'
-test_expect_success 'status --porcelain ignores color.status' '
+test_expect_success TTY 'status --porcelain ignores color.status' '
git config --unset color.ui &&
- git config color.status always &&
- git status --porcelain | test_decode_color >output &&
+ git config color.status auto &&
+ test_terminal git status --porcelain | test_decode_color >output &&
test_cmp expect output
'
@@ -1670,4 +1671,14 @@ test_expect_success '"Initial commit" should not be noted in commit template' '
test_i18ngrep ! "Initial commit" output
'
+test_expect_success '--no-optional-locks prevents index update' '
+ test-chmtime =1234567890 .git/index &&
+ git --no-optional-locks status &&
+ test-chmtime -v +0 .git/index >out &&
+ grep ^1234567890 out &&
+ git status &&
+ test-chmtime -v +0 .git/index >out &&
+ ! grep ^1234567890 out
+'
+
test_done
diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh
index d8242e467e..0f86c19174 100755
--- a/t/t8010-cat-file-filters.sh
+++ b/t/t8010-cat-file-filters.sh
@@ -51,6 +51,11 @@ test_expect_success '--path=<path> complains without --textconv/--filters' '
grep "path.*needs.*filters" err
'
+test_expect_success '--textconv/--filters complain without path' '
+ test_must_fail git cat-file --textconv HEAD &&
+ test_must_fail git cat-file --filters HEAD
+'
+
test_expect_success 'cat-file --textconv --batch works' '
sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" &&
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 67b8c50a5a..d47560b634 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -3120,4 +3120,146 @@ test_expect_success 'U: validate root delete result' '
compare_diff_raw expect actual
'
+###
+### series V (checkpoint)
+###
+
+# The commands in input_file should not produce any output on the file
+# descriptor set with --cat-blob-fd (or stdout if unspecified).
+#
+# To make sure you're observing the side effects of checkpoint *before*
+# fast-import terminates (and thus writes out its state), check that the
+# fast-import process is still running using background_import_still_running
+# *after* evaluating the test conditions.
+background_import_then_checkpoint () {
+ options=$1
+ input_file=$2
+
+ mkfifo V.input
+ exec 8<>V.input
+ rm V.input
+
+ mkfifo V.output
+ exec 9<>V.output
+ rm V.output
+
+ git fast-import $options <&8 >&9 &
+ echo $! >V.pid
+ # We don't mind if fast-import has already died by the time the test
+ # ends.
+ test_when_finished "exec 8>&-; exec 9>&-; kill $(cat V.pid) || true"
+
+ # Start in the background to ensure we adhere strictly to (blocking)
+ # pipes writing sequence. We want to assume that the write below could
+ # block, e.g. if fast-import blocks writing its own output to &9
+ # because there is no reader on &9 yet.
+ (
+ cat "$input_file"
+ echo "checkpoint"
+ echo "progress checkpoint"
+ ) >&8 &
+
+ error=1 ;# assume the worst
+ while read output <&9
+ do
+ if test "$output" = "progress checkpoint"
+ then
+ error=0
+ break
+ fi
+ # otherwise ignore cruft
+ echo >&2 "cruft: $output"
+ done
+
+ if test $error -eq 1
+ then
+ false
+ fi
+}
+
+background_import_still_running () {
+ if ! kill -0 "$(cat V.pid)"
+ then
+ echo >&2 "background fast-import terminated too early"
+ false
+ fi
+}
+
+test_expect_success PIPE 'V: checkpoint helper does not get stuck with extra output' '
+ cat >input <<-INPUT_END &&
+ progress foo
+ progress bar
+
+ INPUT_END
+
+ background_import_then_checkpoint "" input &&
+ background_import_still_running
+'
+
+test_expect_success PIPE 'V: checkpoint updates refs after reset' '
+ cat >input <<-\INPUT_END &&
+ reset refs/heads/V
+ from refs/heads/U
+
+ INPUT_END
+
+ background_import_then_checkpoint "" input &&
+ test "$(git rev-parse --verify V)" = "$(git rev-parse --verify U)" &&
+ background_import_still_running
+'
+
+test_expect_success PIPE 'V: checkpoint updates refs and marks after commit' '
+ cat >input <<-INPUT_END &&
+ commit refs/heads/V
+ mark :1
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data 0
+ from refs/heads/U
+
+ INPUT_END
+
+ background_import_then_checkpoint "--export-marks=marks.actual" input &&
+
+ echo ":1 $(git rev-parse --verify V)" >marks.expected &&
+
+ test "$(git rev-parse --verify V^)" = "$(git rev-parse --verify U)" &&
+ test_cmp marks.expected marks.actual &&
+ background_import_still_running
+'
+
+# Re-create the exact same commit, but on a different branch: no new object is
+# created in the database, but the refs and marks still need to be updated.
+test_expect_success PIPE 'V: checkpoint updates refs and marks after commit (no new objects)' '
+ cat >input <<-INPUT_END &&
+ commit refs/heads/V2
+ mark :2
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data 0
+ from refs/heads/U
+
+ INPUT_END
+
+ background_import_then_checkpoint "--export-marks=marks.actual" input &&
+
+ echo ":2 $(git rev-parse --verify V2)" >marks.expected &&
+
+ test "$(git rev-parse --verify V2)" = "$(git rev-parse --verify V)" &&
+ test_cmp marks.expected marks.actual &&
+ background_import_still_running
+'
+
+test_expect_success PIPE 'V: checkpoint updates tags after tag' '
+ cat >input <<-INPUT_END &&
+ tag Vtag
+ from refs/heads/V
+ tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data 0
+
+ INPUT_END
+
+ background_import_then_checkpoint "" input &&
+ git show-ref -d Vtag &&
+ background_import_still_running
+'
+
test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 8dcb05c4a5..866ddf6058 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -234,7 +234,7 @@ test_expect_success 'fast-export -C -C | fast-import' '
mkdir new &&
git --git-dir=new/.git init &&
git fast-export -C -C --signed-tags=strip --all > output &&
- grep "^C file6 file7\$" output &&
+ grep "^C file2 file4\$" output &&
cat output |
(cd new &&
git fast-import &&
@@ -522,4 +522,22 @@ test_expect_success 'delete refspec' '
test_cmp expected actual
'
+test_expect_success 'when using -C, do not declare copy when source of copy is also modified' '
+ test_create_repo src &&
+ echo a_line >src/file.txt &&
+ git -C src add file.txt &&
+ git -C src commit -m 1st_commit &&
+
+ cp src/file.txt src/file2.txt &&
+ echo another_line >>src/file.txt &&
+ git -C src add file.txt file2.txt &&
+ git -C src commit -m 2nd_commit &&
+
+ test_create_repo dst &&
+ git -C src fast-export --all -C | git -C dst fast-import &&
+ git -C src show >expected &&
+ git -C dst show >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 432c61d246..c30660d606 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -588,4 +588,52 @@ test_expect_success 'cvs annotate' '
test_cmp ../expect ../actual
'
+#------------
+# running via git-shell
+#------------
+
+cd "$WORKDIR"
+
+test_expect_success 'create remote-cvs helper' '
+ write_script remote-cvs <<-\EOF
+ exec git shell -c "cvs server"
+ EOF
+'
+
+test_expect_success 'cvs server does not run with vanilla git-shell' '
+ (
+ cd cvswork &&
+ CVS_SERVER=$WORKDIR/remote-cvs &&
+ export CVS_SERVER &&
+ test_must_fail cvs log merge
+ )
+'
+
+test_expect_success 'configure git shell to run cvs server' '
+ mkdir "$HOME"/git-shell-commands &&
+
+ write_script "$HOME"/git-shell-commands/cvs <<-\EOF &&
+ if ! test $# = 1 && test "$1" = "server"
+ then
+ echo >&2 "git-cvsserver only handles \"server\""
+ exit 1
+ fi
+ exec git cvsserver server
+ EOF
+
+ # Should not be used, but part of the recommended setup
+ write_script "$HOME"/git-shell-commands/no-interactive-login <<-\EOF
+ echo Interactive login forbidden
+ EOF
+'
+
+test_expect_success 'cvs server can run with recommended config' '
+ (
+ cd cvswork &&
+ CVS_SERVER=$WORKDIR/remote-cvs &&
+ export CVS_SERVER &&
+ cvs log merge
+ )
+'
+
test_done
diff --git a/t/test-terminal.perl b/t/test-terminal.perl
index 96b6a03e1c..46bf618479 100755
--- a/t/test-terminal.perl
+++ b/t/test-terminal.perl
@@ -80,6 +80,7 @@ sub copy_stdio {
if ($#ARGV < 1) {
die "usage: test-terminal program args";
}
+$ENV{TERM} = 'vt100';
my $master_in = new IO::Pty;
my $master_out = new IO::Pty;
my $master_err = new IO::Pty;
diff --git a/tag.c b/tag.c
index 7e10acfb6e..fcbe012f7a 100644
--- a/tag.c
+++ b/tag.c
@@ -142,13 +142,13 @@ int parse_tag_buffer(struct tag *item, const void *data, unsigned long size)
bufptr = nl + 1;
if (!strcmp(type, blob_type)) {
- item->tagged = &lookup_blob(&oid)->object;
+ item->tagged = (struct object *)lookup_blob(&oid);
} else if (!strcmp(type, tree_type)) {
- item->tagged = &lookup_tree(&oid)->object;
+ item->tagged = (struct object *)lookup_tree(&oid);
} else if (!strcmp(type, commit_type)) {
- item->tagged = &lookup_commit(&oid)->object;
+ item->tagged = (struct object *)lookup_commit(&oid);
} else if (!strcmp(type, tag_type)) {
- item->tagged = &lookup_tag(&oid)->object;
+ item->tagged = (struct object *)lookup_tag(&oid);
} else {
error("Unknown type %s", type);
item->tagged = NULL;
diff --git a/transport.c b/transport.c
index d75ff0514d..f1e2f61991 100644
--- a/transport.c
+++ b/transport.c
@@ -26,7 +26,6 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
const char *localname;
const char *tmp;
const char *remotename;
- unsigned char sha[20];
int flag = 0;
/*
* Check suitability for tracking. Must be successful /
@@ -44,7 +43,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
localname = ref->peer_ref->name;
remotename = ref->name;
tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING,
- sha, &flag);
+ NULL, &flag);
if (tmp && flag & REF_ISSYMREF &&
starts_with(tmp, "refs/heads/"))
localname = tmp;
@@ -472,11 +471,10 @@ void transport_print_push_status(const char *dest, struct ref *refs,
{
struct ref *ref;
int n = 0;
- struct object_id head_oid;
char *head;
int summary_width = transport_summary_width(refs);
- head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL);
+ head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
if (verbose) {
for (ref = refs; ref; ref = ref->next)
diff --git a/tree-walk.c b/tree-walk.c
index c99309069a..684f0e3373 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -582,12 +582,11 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
int retval = MISSING_OBJECT;
struct dir_state *parents = NULL;
size_t parents_alloc = 0;
- ssize_t parents_nr = 0;
+ size_t i, parents_nr = 0;
unsigned char current_tree_sha1[20];
struct strbuf namebuf = STRBUF_INIT;
struct tree_desc t;
int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS;
- int i;
init_tree_desc(&t, NULL, 0UL);
strbuf_addstr(&namebuf, name);
diff --git a/upload-pack.c b/upload-pack.c
index 7efff2fbfd..e25f725c0f 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -888,7 +888,7 @@ static void receive_needs(void)
}
shallow_nr += shallows.nr;
- free(shallows.objects);
+ object_array_clear(&shallows);
}
/* return non-zero if the ref is hidden, otherwise 0 */
@@ -965,11 +965,10 @@ static int find_symref(const char *refname, const struct object_id *oid,
{
const char *symref_target;
struct string_list_item *item;
- struct object_id unused;
if ((flag & REF_ISSYMREF) == 0)
return 0;
- symref_target = resolve_ref_unsafe(refname, 0, unused.hash, &flag);
+ symref_target = resolve_ref_unsafe(refname, 0, NULL, &flag);
if (!symref_target || (flag & REF_ISSYMREF) == 0)
die("'%s' is a symref but it is not?", refname);
item = string_list_append(cb_data, refname);
diff --git a/userdiff.c b/userdiff.c
index 6321103ce2..dbfb4e13cd 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -38,7 +38,7 @@ IPATTERN("fortran",
"|//|\\*\\*|::|[/<>=]="),
IPATTERN("fountain", "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$",
"[^ \t-]+"),
-PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
+PATTERNS("html", "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$",
"[^<>= \t]+"),
PATTERNS("java",
"!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
diff --git a/worktree.c b/worktree.c
index 8aaeea0377..70015629dc 100644
--- a/worktree.c
+++ b/worktree.c
@@ -307,7 +307,6 @@ const struct worktree *find_shared_symref(const char *symref,
for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];
const char *symref_target;
- unsigned char sha1[20];
struct ref_store *refs;
int flags;
@@ -327,7 +326,7 @@ const struct worktree *find_shared_symref(const char *symref,
refs = get_worktree_ref_store(wt);
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
- sha1, &flags);
+ NULL, &flags);
if ((flags & REF_ISSYMREF) && !strcmp(symref_target, target)) {
existing = wt;
break;
diff --git a/wt-status.c b/wt-status.c
index 6f730ee8f2..29bc64cc02 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -121,15 +121,13 @@ static void status_printf_more(struct wt_status *s, const char *color,
void wt_status_prepare(struct wt_status *s)
{
- struct object_id oid;
-
memset(s, 0, sizeof(*s));
memcpy(s->color_palette, default_wt_status_colors,
sizeof(default_wt_status_colors));
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
s->use_color = -1;
s->relative_paths = 1;
- s->branch = resolve_refdup("HEAD", 0, oid.hash, NULL);
+ s->branch = resolve_refdup("HEAD", 0, NULL, NULL);
s->reference = "HEAD";
s->fp = stdout;
s->index_file = get_index_file();