"git config -h" did not describe the "--type" option correctly.
* mf/fix-type-in-config-h:
config: correct "--type" option in "git config -h" output
/git-grep
/git-hash-object
/git-help
+/git-hook
/git-http-backend
/git-http-fetch
/git-http-push
David S. Miller <davem@davemloft.net>
David Turner <novalis@novalis.org> <dturner@twopensource.com>
David Turner <novalis@novalis.org> <dturner@twosigma.com>
-Derrick Stolee <dstolee@microsoft.com> <stolee@gmail.com>
-Derrick Stolee <dstolee@microsoft.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
+Derrick Stolee <derrickstolee@github.com> <stolee@gmail.com>
+Derrick Stolee <derrickstolee@github.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
+Derrick Stolee <derrickstolee@github.com> <dstolee@microsoft.com>
Deskin Miller <deskinm@umich.edu>
Đoàn Trần Công Danh <congdanhqx@gmail.com> Doan Tran Cong Danh
Dirk Süsserott <newsletter@dirk.my1.cc>
- Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- Christian Couder <christian.couder@gmail.com>
- - Jeff King <peff@peff.net>
- Junio C Hamano <gitster@pobox.com>
+ - Taylor Blau <me@ttaylorr.com>
All complaints will be reviewed and investigated promptly and fairly.
go and fix it up."
Cf. http://lkml.iu.edu/hypermail/linux/kernel/1001.3/01069.html
+ - Log messages to explain your changes are as important as the
+ changes themselves. Clearly written code and in-code comments
+ explain how the code works and what is assumed from the surrounding
+ context. The log messages explain what the changes wanted to
+ achieve and why the changes were necessary (more on this in the
+ accompanying SubmittingPatches document).
+
Make your code readable and sensible, and don't try to be clever.
As for more concrete guidelines, just imitate the existing code
. since mid 2017 with 512f41cf, we have been using designated
initializers for array (e.g. "int array[10] = { [5] = 2 }").
+ . since early 2021 with 765dc168882, we have been using variadic
+ macros, mostly for printf-like trace and debug macros.
+
These used to be forbidden, but we have not heard any breakage
report, and they are assumed to be safe.
+# Import tree-wide shared Makefile behavior and libraries
+include ../shared.mak
+
# Guard against environment variables
MAN1_TXT =
MAN5_TXT =
ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)'
endif
-QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
-QUIET_SUBDIR1 =
-
-ifneq ($(findstring $(MAKEFLAGS),w),w)
-PRINT_DIR = --no-print-directory
-else # "make -w"
-NO_SUBDIR = :
-endif
-
-ifneq ($(findstring $(MAKEFLAGS),s),s)
-ifndef V
- QUIET = @
- QUIET_ASCIIDOC = @echo ' ' ASCIIDOC $@;
- QUIET_XMLTO = @echo ' ' XMLTO $@;
- QUIET_DB2TEXI = @echo ' ' DB2TEXI $@;
- QUIET_MAKEINFO = @echo ' ' MAKEINFO $@;
- QUIET_DBLATEX = @echo ' ' DBLATEX $@;
- QUIET_XSLTPROC = @echo ' ' XSLTPROC $@;
- QUIET_GEN = @echo ' ' GEN $@;
- QUIET_STDERR = 2> /dev/null
- QUIET_SUBDIR0 = +@subdir=
- QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
- $(MAKE) $(PRINT_DIR) -C $$subdir
-
- QUIET_LINT_GITLINK = @echo ' ' LINT GITLINK $<;
- QUIET_LINT_MANSEC = @echo ' ' LINT MAN SEC $<;
- QUIET_LINT_MANEND = @echo ' ' LINT MAN END $<;
-
- export V
-endif
-endif
-
all: html man
html: $(DOC_HTML)
print-man1:
@for i in $(MAN1_TXT); do echo $$i; done
-## Lint: Common
-.build:
- $(QUIET)mkdir $@
-.build/lint-docs: | .build
- $(QUIET)mkdir $@
-
## Lint: gitlink
-.build/lint-docs/gitlink: | .build/lint-docs
- $(QUIET)mkdir $@
-.build/lint-docs/gitlink/howto: | .build/lint-docs/gitlink
- $(QUIET)mkdir $@
-.build/lint-docs/gitlink/config: | .build/lint-docs/gitlink
- $(QUIET)mkdir $@
LINT_DOCS_GITLINK = $(patsubst %.txt,.build/lint-docs/gitlink/%.ok,$(HOWTO_TXT) $(DOC_DEP_TXT))
-$(LINT_DOCS_GITLINK): | .build/lint-docs/gitlink
-$(LINT_DOCS_GITLINK): | .build/lint-docs/gitlink/howto
-$(LINT_DOCS_GITLINK): | .build/lint-docs/gitlink/config
$(LINT_DOCS_GITLINK): lint-gitlink.perl
$(LINT_DOCS_GITLINK): .build/lint-docs/gitlink/%.ok: %.txt
+ $(call mkdir_p_parent_template)
$(QUIET_LINT_GITLINK)$(PERL_PATH) lint-gitlink.perl \
$< \
$(HOWTO_TXT) $(DOC_DEP_TXT) \
lint-docs-gitlink: $(LINT_DOCS_GITLINK)
## Lint: man-end-blurb
-.build/lint-docs/man-end-blurb: | .build/lint-docs
- $(QUIET)mkdir $@
LINT_DOCS_MAN_END_BLURB = $(patsubst %.txt,.build/lint-docs/man-end-blurb/%.ok,$(MAN_TXT))
-$(LINT_DOCS_MAN_END_BLURB): | .build/lint-docs/man-end-blurb
$(LINT_DOCS_MAN_END_BLURB): lint-man-end-blurb.perl
$(LINT_DOCS_MAN_END_BLURB): .build/lint-docs/man-end-blurb/%.ok: %.txt
+ $(call mkdir_p_parent_template)
$(QUIET_LINT_MANEND)$(PERL_PATH) lint-man-end-blurb.perl $< >$@
.PHONY: lint-docs-man-end-blurb
-lint-docs-man-end-blurb: $(LINT_DOCS_MAN_END_BLURB)
## Lint: man-section-order
-.build/lint-docs/man-section-order: | .build/lint-docs
- $(QUIET)mkdir $@
LINT_DOCS_MAN_SECTION_ORDER = $(patsubst %.txt,.build/lint-docs/man-section-order/%.ok,$(MAN_TXT))
-$(LINT_DOCS_MAN_SECTION_ORDER): | .build/lint-docs/man-section-order
$(LINT_DOCS_MAN_SECTION_ORDER): lint-man-section-order.perl
$(LINT_DOCS_MAN_SECTION_ORDER): .build/lint-docs/man-section-order/%.ok: %.txt
+ $(call mkdir_p_parent_template)
$(QUIET_LINT_MANSEC)$(PERL_PATH) lint-man-section-order.perl $< >$@
.PHONY: lint-docs-man-section-order
lint-docs-man-section-order: $(LINT_DOCS_MAN_SECTION_ORDER)
$(MAKE) -C po $@
endif
-# Delete the target file on error
-.DELETE_ON_ERROR:
-
.PHONY: FORCE
--- /dev/null
+Git 2.36 Release Notes
+======================
+
+Updates since Git 2.35
+----------------------
+
+Backward compatibility warts
+
+ * "git name-rev --stdin" has been deprecated and issues a warning
+ when used; use "git name-rev --annotate-stdin" instead.
+
+ * "git clone --filter=... --recurse-submodules" only makes the
+ top-level a partial clone, while submodules are fully cloned. This
+ behaviour is changed to pass the same filter down to the submodules.
+
+
+Note to those who build from the source
+
+ * Since Git 2.31, our source assumed that the compiler you use to
+ build Git supports variadic macros, with an easy-to-use escape
+ hatch to allow compilation without variadic macros with an request
+ to report that you had to use the escape hatch to the list.
+ Because we haven't heard from anybody who actually needed to use
+ the escape hatch, it has been removed, making support of variadic
+ macros a hard requirement.
+
+
+UI, Workflows & Features
+
+ * Assorted updates to "git cat-file", especially "-h".
+
+ * The command line completion (in contrib/) learns to complete
+ arguments to give to "git sparse-checkout" command.
+
+ * "git log --remerge-diff" shows the difference from mechanical merge
+ result and the result that is actually recorded in a merge commit.
+
+ * "git log" and friends learned an option --exclude-first-parent-only
+ to propagate UNINTERESTING bit down only along the first-parent
+ chain, just like --first-parent option shows commits that lack the
+ UNINTERESTING bit only along the first-parent chain.
+
+ * The command line completion script (in contrib/) learned to
+ complete all Git subcommands, including the ones that are normally
+ hidden, when GIT_COMPLETION_SHOW_ALL_COMMANDS is used.
+
+ * "git branch" learned the "--recurse-submodules" option.
+
+ * A not-so-common mistake is to write a script to feed "git bisect
+ run" without making it executable, in which case all tests will
+ exit with 126 or 127 error codes, even on revisions that are marked
+ as good. Try to recognize this situation and stop iteration early.
+
+ * When "index-pack" dies due to incoming data exceeding the maximum
+ allowed input size, include the value of the limit in the error
+ message.
+
+ * The error message given by "git switch HEAD~4" has been clarified
+ to suggest the "--detach" option that is required.
+
+ * In sparse-checkouts, files mis-marked as missing from the working tree
+ could lead to later problems. Such files were hard to discover, and
+ harder to correct. Automatically detecting and correcting the marking
+ of such files has been added to avoid these problems.
+
+ * "git cat-file" learns "--batch-command" mode, which is a more
+ flexible interface than the existing "--batch" or "--batch-check"
+ modes, to allow different kinds of inquiries made.
+
+ * The level of verbose output from the ort backend during inner merge
+ has been aligned to that of the recursive backend.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * "git apply" (ab)used the util pointer of the string-list to keep
+ track of how each symbolic link needs to be handled, which has been
+ simplified by using strset.
+
+ * Fix a hand-rolled alloca() imitation that may have violated
+ alignment requirement of data being sorted in compatibility
+ implementation of qsort_s() and stable qsort().
+
+ * Use the parse-options API in "git reflog" command.
+
+ * The conditional inclusion mechanism of configuration files using
+ "[includeIf <condition>]" learns to base its decision on the
+ URL of the remote repository the repository interacts with.
+ (merge 399b198489 jt/conditional-config-on-remote-url later to maint).
+
+ * "git name-rev --stdin" does not behave like usual "--stdin" at
+ all. Start the process of renaming it to "--annotate-stdin".
+ (merge a2585719b3 jc/name-rev-stdin later to maint).
+
+ * "git update-index", "git checkout-index", and "git clean" are
+ taught to work better with the sparse checkout feature.
+
+ * Use an internal call to reset_head() helper function instead of
+ spawning "git checkout" in "rebase", and update code paths that are
+ involved in the change.
+
+ * Messages "ort" merge backend prepares while dealing with conflicted
+ paths were unnecessarily confusing since it did not differentiate
+ inner merges and outer merges.
+
+ * Small modernization of the rerere-train script (in contrib/).
+
+ * Use designated initializers we started using in mid 2017 in more
+ parts of the codebase that are relatively quiescent.
+
+ * Improve failure case behaviour of xdiff library when memory
+ allocation fails.
+
+ * General clean-up in reftable implementation, including
+ clarification of the API documentation, tightening the code to
+ honor documented length limit, etc.
+
+ * Remove the escape hatch we added when we introduced the weather
+ balloon to use variadic macros unconditionally, to make it official
+ that we now have a hard dependency on the feature.
+
+ * Makefile refactoring with a bit of suffixes rule stripping to
+ optimize the runtime overhead.
+
+
+Fixes since v2.35
+-----------------
+
+ * "rebase" and "stash" in secondary worktrees are broken in
+ Git 2.35.0, which has been corrected.
+
+ * "git pull --rebase" ignored the rebase.autostash configuration
+ variable when the remote history is a descendant of our history,
+ which has been corrected.
+ (merge 3013d98d7a pb/pull-rebase-autostash-fix later to maint).
+
+ * "git update-index --refresh" has been taught to deal better with
+ racy timestamps (just like "git status" already does).
+ (merge 2ede073fd2 ms/update-index-racy later to maint).
+
+ * Avoid tests that are run under GIT_TRACE2 set from failing
+ unnecessarily.
+ (merge 944d808e42 js/test-unset-trace2-parents later to maint).
+
+ * The merge-ort misbehaved when merge.renameLimit configuration is
+ set too low and failed to find all renames.
+ (merge 9ae39fef7f en/merge-ort-restart-optim-fix later to maint).
+
+ * We explain that revs come first before the pathspec among command
+ line arguments, but did not spell out that dashed options come
+ before other args, which has been corrected.
+ (merge c11f95010c tl/doc-cli-options-first later to maint).
+
+ * "git add -p" rewritten in C regressed hunk splitting in some cases,
+ which has been corrected.
+ (merge 7008ddc645 pw/add-p-hunk-split-fix later to maint).
+
+ * "git fetch --negotiate-only" is an internal command used by "git
+ push" to figure out which part of our history is missing from the
+ other side. It should never recurse into submodules even when
+ fetch.recursesubmodules configuration variable is set, nor it
+ should trigger "gc". The code has been tightened up to ensure it
+ only does common ancestry discovery and nothing else.
+ (merge de4eaae63a gc/fetch-negotiate-only-early-return later to maint).
+
+ * The code path that verifies signatures made with ssh were made to
+ work better on a system with CRLF line endings.
+ (merge caeef01ea7 fs/ssh-signing-crlf later to maint).
+
+ * "git sparse-checkout init" failed to write into $GIT_DIR/info
+ directory when the repository was created without one, which has
+ been corrected to auto-create it.
+ (merge 7f44842ac1 jt/sparse-checkout-leading-dir-fix later to maint).
+
+ * Cloning from a repository that does not yet have any branches or
+ tags but has other refs resulted in a "remote transport reported
+ error", which has been corrected.
+ (merge dccea605b6 jt/clone-not-quite-empty later to maint).
+
+ * Mark in various places in the code that the sparse index and the
+ split index features are mutually incompatible.
+ (merge 451b66c533 js/sparse-vs-split-index later to maint).
+
+ * Update the logic to compute alignment requirement for our mem-pool.
+ (merge e38bcc66d8 jc/mem-pool-alignment later to maint).
+
+ * Pick a better random number generator and use it when we prepare
+ temporary filenames.
+ (merge 47efda967c bc/csprng-mktemps later to maint).
+
+ * Update the contributor-facing documents on proposed log messages.
+ (merge cdba0295b0 jc/doc-log-messages later to maint).
+
+ * When "git fetch --prune" failed to prune the refs it wanted to
+ prune, the command issued error messages but exited with exit
+ status 0, which has been corrected.
+ (merge c9e04d905e tg/fetch-prune-exit-code-fix later to maint).
+
+ * Problems identified by Coverity in the reftable code have been
+ corrected.
+ (merge 01033de49f hn/reftable-coverity-fixes later to maint).
+
+ * A bug that made multi-pack bitmap and the object order out-of-sync,
+ making the .midx data corrupt, has been fixed.
+ (merge f8b60cf99b tb/midx-bitmap-corruption-fix later to maint).
+
+ * The build procedure has been taught to notice older version of zlib
+ and enable our replacement uncompress2() automatically.
+ (merge 07564773c2 ab/auto-detect-zlib-compress2 later to maint).
+
+ * Interaction between fetch.negotiationAlgorithm and
+ feature.experimental configuration variables has been corrected.
+ (merge 714edc620c en/fetch-negotiation-default-fix later to maint).
+
+ * "git diff --diff-filter=aR" is now parsed correctly.
+ (merge 75408ca949 js/diff-filter-negation-fix later to maint).
+
+ * When "git subtree" wants to create a merge, it used "git merge" and
+ let it be affected by end-user's "merge.ff" configuration, which
+ has been corrected.
+ (merge 9158a3564a tk/subtree-merge-not-ff-only later to maint).
+
+ * Unlike "git apply", "git patch-id" did not handle patches with
+ hunks that has only 1 line in either preimage or postimage, which
+ has been corrected.
+ (merge 757e75c81e jz/patch-id-hunk-header-parsing-fix later to maint).
+
+ * "receive-pack" checks if it will do any ref updates (various
+ conditions could reject a push) before received objects are taken
+ out of the temporary directory used for quarantine purposes, so
+ that a push that is known-to-fail will not leave crufts that a
+ future "gc" needs to clean up.
+ (merge 5407764069 cb/clear-quarantine-early-on-all-ref-update-errors later to maint).
+
+ * Because a deletion of ref would need to remove it from both the
+ loose ref store and the packed ref store, a delete-ref operation
+ that logically removes one ref may end up invoking ref-transaction
+ hook twice, which has been corrected.
+ (merge 2ed1b64ebd ps/avoid-unnecessary-hook-invocation-with-packed-refs later to maint).
+
+ * When there is no object to write .bitmap file for, "git
+ multi-pack-index" triggered an error, instead of just skipping,
+ which has been corrected.
+ (merge eb57277ba3 tb/midx-no-bitmap-for-no-objects later to maint).
+
+ * "git cmd -h" outside a repository should error out cleanly for many
+ commands, but instead it hit a BUG(), which has been corrected.
+ (merge 87ad07d735 js/short-help-outside-repo-fix later to maint).
+
+ * "working tree" and "per-worktree ref" were in glossary, but
+ "worktree" itself wasn't, which has been corrected.
+ (merge 2df5387ed0 jc/glossary-worktree later to maint).
+
+ * L10n support for a few error messages.
+ (merge 3d3c23b3a7 bs/forbid-i18n-of-protocol-token-in-fetch-pack later to maint).
+
+ * Test modernization.
+ (merge d4fe066e4b sy/t0001-use-path-is-helper later to maint).
+
+ * "git log --graph --graph" used to leak a graph structure, and there
+ was no way to countermand "--graph" that appear earlier on the
+ command line. A "--no-graph" option has been added and resource
+ leakage has been plugged.
+
+ * Error output given in response to an ambiguous object name has been
+ improved.
+ (merge 3a73c1dfaf ab/ambiguous-object-name later to maint).
+
+ * "git sparse-checkout" wants to work with per-worktree configuration,
+ but did not work well in a worktree attached to a bare repository.
+ (merge 3ce1138272 ds/sparse-checkout-requires-per-worktree-config later to maint).
+
+ * Setting core.untrackedCache to true failed to add the untracked
+ cache extension to the index.
+
+ * Workaround we have for versions of PCRE2 before their version 10.36
+ were in effect only for their versions newer than 10.36 by mistake,
+ which has been corrected.
+ (merge 97169fc361 rs/pcre-invalid-utf8-fix-fix later to maint).
+
+ * Document Taylor as a new member of Git PLC at SFC. Welcome.
+ (merge e8d56ca863 tb/coc-plc-update later to maint).
+
+ * "git checkout -b branch/with/multi/level/name && git stash" only
+ recorded the last level component of the branch name, which has
+ been corrected.
+
+ * "git fetch" can make two separate fetches, but ref updates coming
+ from them were in two separate ref transactions under "--atomic",
+ which has been corrected.
+
+ * Check the return value from parse_tree_indirect() to turn segfaults
+ into calls to die().
+ (merge 8d2eaf649a gc/parse-tree-indirect-errors later to maint).
+
+ * Newer version of GPGSM changed its output in a backward
+ incompatible way to break our code that parses its output. It also
+ added more processes our tests need to kill when cleaning up.
+ Adjustments have been made to accommodate these changes.
+ (merge b0b70d54c4 fs/gpgsm-update later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge cfc5cf428b jc/find-header later to maint).
+ (merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint).
+ (merge 727e6ea350 jh/p4-spawning-external-commands-cleanup later to maint).
+ (merge 0a6adc26e2 rs/grep-expr-cleanup later to maint).
+ (merge 4ed7dfa713 po/readme-mention-contributor-hints later to maint).
+ (merge 6046f7a91c en/plug-leaks-in-merge later to maint).
+ (merge 8c591dbfce bc/clarify-eol-attr later to maint).
+ (merge 518e15db74 rs/parse-options-lithelp-help later to maint).
+ (merge cbac0076ef gh/doc-typos later to maint).
+ (merge ce14de03db ab/no-errno-from-resolve-ref-unsafe later to maint).
+ (merge 2826ffad8c rc/negotiate-only-typofix later to maint).
+ (merge 0f03f04c5c en/sparse-checkout-leakfix later to maint).
+ (merge 74f3390dde sy/diff-usage-typofix later to maint).
+ (merge 45d0212a71 ll/doc-mktree-typofix later to maint).
+ (merge e9b272e4c1 js/no-more-legacy-stash later to maint).
+ (merge 6798b08e84 ab/do-not-hide-failures-in-git-dot-pm later to maint).
+ (merge 9325285df4 po/doc-check-ignore-markup-fix later to maint).
+ (merge cd26cd6c7c sy/modernize-t-lib-read-tree-m-3way later to maint).
+ (merge d17294a05e ab/hash-object-leakfix later to maint).
+ (merge b8403129d3 jd/t0015-modernize later to maint).
+ (merge 332acc248d ds/mailmap later to maint).
+ (merge 04bf052eef ab/grep-patterntype later to maint).
+ (merge 6ee36364eb ab/diff-free-more later to maint).
+ (merge 63a36017fe nj/read-tree-doc-reffix later to maint).
[[describe-changes]]
=== Describe your changes well.
+The log message that explains your changes is just as important as the
+changes themselves. Your code may be clearly written with in-code
+comment to sufficiently explain how it works with the surrounding
+code, but those who need to fix or enhance your code in the future
+will need to know _why_ your code does what it does, for a few
+reasons:
+
+. Your code may be doing something differently from what you wanted it
+ to do. Writing down what you actually wanted to achieve will help
+ them fix your code and make it do what it should have been doing
+ (also, you often discover your own bugs yourself, while writing the
+ log message to summarize the thought behind it).
+
+. Your code may be doing things that were only necessary for your
+ immediate needs (e.g. "do X to directories" without implementing or
+ even designing what is to be done on files). Writing down why you
+ excluded what the code does not do will help guide future developers.
+ Writing down "we do X to directories, because directories have
+ characteristic Y" would help them infer "oh, files also have the same
+ characteristic Y, so perhaps doing X to them would also make sense?".
+ Saying "we don't do the same X to files, because ..." will help them
+ decide if the reasoning is sound (in which case they do not waste
+ time extending your code to cover files), or reason differently (in
+ which case, they can explain why they extend your code to cover
+ files, too).
+
+The goal of your log message is to convey the _why_ behind your
+change to help future developers.
+
The first line of the commit message should be a short description (50
characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]),
and should skip the full stop. It is also conventional in most cases to
. alternate solutions considered but discarded, if any.
+[[present-tense]]
+The problem statement that describes the status quo is written in the
+present tense. Write "The code does X when it is given input Y",
+instead of "The code used to do Y when given input X". You do not
+have to say "Currently"---the status quo in the problem statement is
+about the code _without_ your change, by project convention.
+
[[imperative-mood]]
Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
organized hierarchically and you would like to apply a configuration to
all the branches in that hierarchy.
+`hasconfig:remote.*.url:`::
+ The data that follows this keyword is taken to
+ be a pattern with standard globbing wildcards and two
+ additional ones, `**/` and `/**`, that can match multiple
+ components. The first time this keyword is seen, the rest of
+ the config files will be scanned for remote URLs (without
+ applying any values). If there exists at least one remote URL
+ that matches this pattern, the include condition is met.
++
+Files included by this option (directly or indirectly) are not allowed
+to contain remote URLs.
++
+Note that unlike other includeIf conditions, resolving this condition
+relies on information that is not yet known at the point of reading the
+condition. A typical use case is this option being present as a
+system-level or global-level config, and the remote URL being in a
+local-level config; hence the need to scan ahead when resolving this
+condition. In order to avoid the chicken-and-egg problem in which
+potentially-included files can affect whether such files are potentially
+included, Git breaks the cycle by prohibiting these files from affecting
+the resolution of these conditions (thus, prohibiting them from
+declaring remote URLs).
++
+As for the naming of this keyword, it is for forwards compatibiliy with
+a naming scheme that supports more variable-based include conditions,
+but currently Git only supports the exact keyword described above.
+
A few more notes on matching via `gitdir` and `gitdir/i`:
* Symlinks in `$GIT_DIR` are not resolved before matching.
; currently checked out
[includeIf "onbranch:foo-branch"]
path = foo.inc
+
+; include only if a remote with the given URL exists (note
+; that such a URL may be provided later in a file or in a
+; file read after this file is read, as seen in this example)
+[includeIf "hasconfig:remote.*.url:https://example.com/**"]
+ path = foo.inc
+[remote "origin"]
+ url = https://example.com/git
----
Values
include::config/showbranch.txt[]
+include::config/sparse.txt[]
+
include::config/splitindex.txt[]
include::config/ssh.txt[]
linkgit:git-switch[1] or linkgit:git-checkout[1]
to move to the detach HEAD state, to instruct how to
create a local branch after the fact.
+ suggestDetachingHead::
+ Advice shown when linkgit:git-switch[1] refuses to detach HEAD
+ without the explicit `--detach` option.
checkoutAmbiguousRemoteBranchName::
Advice shown when the argument to
linkgit:git-checkout[1] and linkgit:git-switch[1]
submoduleAlternateErrorStrategyDie::
Advice shown when a submodule.alternateErrorStrategy option
configured to "die" causes a fatal error.
+ submodulesNotUpdated::
+ Advice shown when a user runs a submodule command that fails
+ because `git submodule update --init` was not run.
addIgnoredFile::
Advice shown if a user attempts to add an ignored file to
the index.
clone.rejectShallow::
Reject to clone a repository if it is a shallow one, can be overridden by
passing option `--reject-shallow` in command line. See linkgit:git-clone[1]
+
+clone.filterSubmodules::
+ If a partial clone filter is provided (see `--filter` in
+ linkgit:git-rev-list[1]) and `--recurse-submodules` is used, also apply
+ the filter to submodules.
Note that this setting should only be set by linkgit:git-init[1] or
linkgit:git-clone[1]. Trying to change it after initialization will not
work and will produce hard-to-diagnose issues.
+
+extensions.worktreeConfig::
+ If enabled, then worktrees will load config settings from the
+ `$GIT_DIR/config.worktree` file in addition to the
+ `$GIT_COMMON_DIR/config` file. Note that `$GIT_COMMON_DIR` and
+ `$GIT_DIR` are the same for the main working tree, while other
+ working trees have `$GIT_DIR` equal to
+ `$GIT_COMMON_DIR/worktrees/<id>/`. The settings in the
+ `config.worktree` file will override settings from any other
+ config files.
++
+When enabling `extensions.worktreeConfig`, you must be careful to move
+certain values from the common config file to the main working tree's
+`config.worktree` file, if present:
++
+* `core.worktree` must be moved from `$GIT_COMMON_DIR/config` to
+ `$GIT_COMMON_DIR/config.worktree`.
+* If `core.bare` is true, then it must be moved from `$GIT_COMMON_DIR/config`
+ to `$GIT_COMMON_DIR/config.worktree`.
++
+It may also be beneficial to adjust the locations of `core.sparseCheckout`
+and `core.sparseCheckoutCone` depending on your desire for customizable
+sparse-checkout settings for each worktree. By default, the `git
+sparse-checkout` builtin enables `extensions.worktreeConfig`, assigns
+these config values on a per-worktree basis, and uses the
+`$GIT_DIR/info/sparse-checkout` file to specify the sparsity for each
+worktree independently. See linkgit:git-sparse-checkout[1] for more
+details.
++
+For historical reasons, `extensions.worktreeConfig` is respected
+regardless of the `core.repositoryFormatVersion` setting.
OUTPUT in linkgit:git-fetch[1] for detail.
fetch.negotiationAlgorithm::
- Control how information about the commits in the local repository is
- sent when negotiating the contents of the packfile to be sent by the
- server. Set to "skipping" to use an algorithm that skips commits in an
- effort to converge faster, but may result in a larger-than-necessary
- packfile; or set to "noop" to not send any information at all, which
- will almost certainly result in a larger-than-necessary packfile, but
- will skip the negotiation step.
- The default is "default" which instructs Git to use the default algorithm
- that never skips commits (unless the server has acknowledged it or one
- of its descendants). If `feature.experimental` is enabled, then this
- setting defaults to "skipping".
- Unknown values will cause 'git fetch' to error out.
+ Control how information about the commits in the local repository
+ is sent when negotiating the contents of the packfile to be sent by
+ the server. Set to "consecutive" to use an algorithm that walks
+ over consecutive commits checking each one. Set to "skipping" to
+ use an algorithm that skips commits in an effort to converge
+ faster, but may result in a larger-than-necessary packfile; or set
+ to "noop" to not send any information at all, which will almost
+ certainly result in a larger-than-necessary packfile, but will skip
+ the negotiation step. Set to "default" to override settings made
+ previously and use the default behaviour. The default is normally
+ "consecutive", but if `feature.experimental` is true, then the
+ default is "skipping". Unknown values will cause 'git fetch' to
+ error out.
+
See also the `--negotiate-only` and `--negotiation-tip` options to
linkgit:git-fetch[1].
gpg.ssh.defaultKeyCommand::
This command that will be run when user.signingkey is not set and a ssh
signature is requested. On successful exit a valid ssh public key is
- expected in the first line of its output. To automatically use the first
+ expected in the first line of its output. To automatically use the first
available key from your ssh-agent set this to "ssh-add -L".
gpg.ssh.allowedSignersFile::
+
Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &
valid-before options. Git will mark signatures as valid if the signing key was
-valid at the time of the signatures creation. This allows users to change a
+valid at the time of the signature's creation. This allows users to change a
signing key without invalidating all previously made signatures.
+
Using a SSH CA key with the cert-authority option
--- /dev/null
+sparse.expectFilesOutsideOfPatterns::
+ Typically with sparse checkouts, files not matching any
+ sparsity patterns are marked with a SKIP_WORKTREE bit in the
+ index and are missing from the working tree. Accordingly, Git
+ will ordinarily check whether files with the SKIP_WORKTREE bit
+ are in fact present in the working tree contrary to
+ expectations. If Git finds any, it marks those paths as
+ present by clearing the relevant SKIP_WORKTREE bits. This
+ option can be used to tell Git that such
+ present-despite-skipped files are expected and to stop
+ checking for them.
++
+The default is `false`, which allows Git to automatically recover
+from the list of files in the index and working tree falling out of
+sync.
++
+Set this to `true` if you are in a setup where some external factor
+relieves Git of the responsibility for maintaining the consistency
+between the presence of working tree files and sparsity patterns. For
+example, if you have a Git-aware virtual file system that has a robust
+mechanism for keeping the working tree and the sparsity patterns up to
+date based on access patterns.
++
+Regardless of this setting, Git does not check for
+present-despite-skipped files unless sparse checkout is enabled, so
+this config option has no effect unless `core.sparseCheckout` is
+`true`.
-stash.useBuiltin::
- Unused configuration variable. Used in Git versions 2.22 to
- 2.26 as an escape hatch to enable the legacy shellscript
- implementation of stash. Now the built-in rewrite of it in C
- is always used. Setting this will emit a warning, to alert any
- remaining users that setting this now does nothing.
-
stash.showIncludeUntracked::
If this is set to true, the `git stash show` command will show
the untracked files of a stash entry. Defaults to false. See
submodule.recurse::
A boolean indicating if commands should enable the `--recurse-submodules`
- option by default.
- Applies to all commands that support this option
- (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,
- `restore` and `switch`) except `clone` and `ls-files`.
+ option by default. Defaults to false.
++
+When set to true, it can be deactivated via the
+`--no-recurse-submodules` option. Note that some Git commands
+lacking this option may call some of the above commands affected by
+`submodule.recurse`; for instance `git remote update` will call
+`git fetch` but does not have a `--no-recurse-submodules` option.
+For these commands a workaround is to temporarily change the
+configuration value by using `git -c submodule.recurse=0`.
++
+The following list shows the commands that accept
+`--recurse-submodules` and whether they are supported by this
+setting.
+
+* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,
+`reset`, `restore` and `switch` are always supported.
+* `clone` and `ls-files` are not supported.
+* `branch` is supported only if `submodule.propagateBranches` is
+enabled
+
+submodule.propagateBranches::
+ [EXPERIMENTAL] A boolean that enables branching support when
+ using `--recurse-submodules` or `submodule.recurse=true`.
+ Enabling this will allow certain commands to accept
+ `--recurse-submodules` and certain commands that already accept
+ `--recurse-submodules` will now consider branches.
Defaults to false.
- When set to true, it can be deactivated via the
- `--no-recurse-submodules` option. Note that some Git commands
- lacking this option may call some of the above commands affected by
- `submodule.recurse`; for instance `git remote update` will call
- `git fetch` but does not have a `--no-recurse-submodules` option.
- For these commands a workaround is to temporarily change the
- configuration value by using `git -c submodule.recurse=0`.
submodule.fetchJobs::
Specifies how many submodules are fetched/cloned at the same time.
endif::git-format-patch[]
ifdef::git-log[]
---diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc)::
+--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r)::
--no-diff-merges::
Specify diff format to be used for merge commits. Default is
{diff-merges-default} unless `--first-parent` is in use, in which case
each of the parents. Separate log entry and diff is generated
for each parent.
+
+--diff-merges=remerge:::
+--diff-merges=r:::
+--remerge-diff:::
+ With this option, two-parent merge commits are remerged to
+ create a temporary tree object -- potentially containing files
+ with conflict markers and such. A diff is then shown between
+ that temporary tree and the actual merge commit.
++
+The output emitted when this option is used is subject to change, and
+so is its interaction with other options (unless explicitly
+documented).
++
--diff-merges=combined:::
--diff-merges=c:::
-c:::
Also, these upper-case letters can be downcased to exclude. E.g.
`--diff-filter=ad` excludes added and deleted paths.
+
-Note that not all diffs can feature all types. For instance, diffs
-from the index to the working tree can never have Added entries
-(because the set of paths included in the diff is limited by what is in
-the index). Similarly, copied and renamed entries cannot appear if
-detection for those types is disabled.
+Note that not all diffs can feature all types. For instance, copied and
+renamed entries cannot appear if detection for those types is disabled.
-S<string>::
Look for differences that change the number of occurrences of
ancestors of the provided `--negotiation-tip=*` arguments,
which we have in common with the server.
+
+This is incompatible with `--recurse-submodules=[yes|on-demand]`.
Internally this is used to implement the `push.negotiate` option, see
linkgit:git-config[1].
[--points-at <object>] [--format=<format>]
[(-r | --remotes) | (-a | --all)]
[--list] [<pattern>...]
-'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track[=(direct|inherit)] | --no-track] [-f]
+ [--recurse-submodules] <branchname> [<start-point>]
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
'git branch' --unset-upstream [<branchname>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
Do not set up "upstream" configuration, even if the
branch.autoSetupMerge configuration variable is set.
+--recurse-submodules::
+ THIS OPTION IS EXPERIMENTAL! Causes the current command to
+ recurse into submodules if `submodule.propagateBranches` is
+ enabled. See `submodule.propagateBranches` in
+ linkgit:git-config[1]. Currently, only branch creation is
+ supported.
++
+When used in branch creation, a new branch <branchname> will be created
+in the superproject and all of the submodules in the superproject's
+<start-point>. In submodules, the branch will point to the submodule
+commit in the superproject's <start-point> but the branch's tracking
+information will be set up based on the submodule's branches and remotes
+e.g. `git branch --recurse-submodules topic origin/main` will create the
+submodule branch "topic" that points to the submodule commit in the
+superproject's "origin/main", but tracks the submodule's "origin/main".
+
--set-upstream::
As this option had confusing syntax, it is no longer supported.
Please use `--track` or `--set-upstream-to` instead.
SYNOPSIS
--------
[verse]
-'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object>
-'git cat-file' (--batch[=<format>] | --batch-check[=<format>]) [ --textconv | --filters ] [--follow-symlinks]
+'git cat-file' <type> <object>
+'git cat-file' (-e | -p) <object>
+'git cat-file' (-t | -s) [--allow-unknown-type] <object>
+'git cat-file' (--batch | --batch-check) [--batch-all-objects]
+ [--buffer] [--follow-symlinks] [--unordered]
+ [--textconv | --filters]
+'git cat-file' (--textconv | --filters)
+ [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]
DESCRIPTION
-----------
need to specify the path, separated by whitespace. See the
section `BATCH OUTPUT` below for details.
+--batch-command::
+--batch-command=<format>::
+ Enter a command mode that reads commands and arguments from stdin. May
+ only be combined with `--buffer`, `--textconv` or `--filters`. In the
+ case of `--textconv` or `--filters`, the input lines also need to specify
+ the path, separated by whitespace. See the section `BATCH OUTPUT` below
+ for details.
++
+`--batch-command` recognizes the following commands:
++
+--
+contents <object>::
+ Print object contents for object reference `<object>`. This corresponds to
+ the output of `--batch`.
+
+info <object>::
+ Print object info for object reference `<object>`. This corresponds to the
+ output of `--batch-check`.
+
+flush::
+ Used with `--buffer` to execute all preceding commands that were issued
+ since the beginning or since the last flush was issued. When `--buffer`
+ is used, no output will come until a `flush` is issued. When `--buffer`
+ is not used, commands are flushed each time without issuing `flush`.
+--
++
+
--batch-all-objects::
Instead of reading a list of objects on stdin, perform the
requested batch operation on all objects in the repository and
that a process can interactively read and write from
`cat-file`. With this option, the output uses normal stdio
buffering; this is much more efficient when invoking
- `--batch-check` on a large number of objects.
+ `--batch-check` or `--batch-command` on a large number of objects.
--unordered::
When `--batch-all-objects` is in use, visit objects in an
the whole line is considered as an object, as if it were fed to
linkgit:git-rev-parse[1].
+When `--batch-command` is given, `cat-file` will read commands from stdin,
+one per line, and print information based on the command given. With
+`--batch-command`, the `info` command followed by an object will print
+information about the object the same way `--batch-check` would, and the
+`contents` command followed by an object prints contents in the same way
+`--batch` would.
+
You can specify the information shown for each object by using a custom
`<format>`. The `<format>` is copied literally to stdout for each
object, with placeholders of the form `%(atom)` expanded, followed by a
If no format is specified, the default format is `%(objectname)
%(objecttype) %(objectsize)`.
-If `--batch` is specified, the object information is followed by the
-object contents (consisting of `%(objectsize)` bytes), followed by a
-newline.
+If `--batch` is specified, or if `--batch-command` is used with the `contents`
+command, the object information is followed by the object contents (consisting
+of `%(objectsize)` bytes), followed by a newline.
For example, `--batch` without a custom format would produce:
Instead of printing the paths that are excluded, for each path
that matches an exclude pattern, print the exclude pattern
together with the path. (Matching an exclude pattern usually
- means the path is excluded, but if the pattern begins with '!'
+ means the path is excluded, but if the pattern begins with "`!`"
then it is a negated pattern and matching it means the path is
NOT excluded.)
+
<pathname> is the path of a file being queried, <pattern> is the
matching pattern, <source> is the pattern's source file, and <linenum>
is the line number of the pattern within that source. If the pattern
-contained a `!` prefix or `/` suffix, it will be preserved in the
+contained a "`!`" prefix or "`/`" suffix, it will be preserved in the
output. <source> will be an absolute path when referring to the file
configured by `core.excludesFile`, or relative to the repository root
when referring to `.git/info/exclude` or a per-directory exclude file.
'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
[--stage=<number>|all]
[--temp]
+ [--ignore-skip-worktree-bits]
[-z] [--stdin]
[--] [<file>...]
-a::
--all::
- checks out all files in the index. Cannot be used
- together with explicit filenames.
+ checks out all files in the index except for those with the
+ skip-worktree bit set (see `--ignore-skip-worktree-bits`).
+ Cannot be used together with explicit filenames.
-n::
--no-create::
write the content to temporary files. The temporary name
associations will be written to stdout.
+--ignore-skip-worktree-bits::
+ Check out all files, including those with the skip-worktree bit
+ set.
+
--stdin::
Instead of taking list of paths from the command line,
read list of paths from the standard input. Paths are
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
[--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
- [--filter=<filter>] [--] <repository>
+ [--filter=<filter> [--also-filter-submodules]] [--] <repository>
[<directory>]
DESCRIPTION
at least `<size>`. For more details on filter specifications, see
the `--filter` option in linkgit:git-rev-list[1].
+--also-filter-submodules::
+ Also apply the partial clone filter to any submodules in the repository.
+ Requires `--filter` and `--recurse-submodules`. This can be turned on by
+ default by setting the `clone.filterSubmodules` config option.
+
--mirror::
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the
See also <<FILES>>.
--worktree::
- Similar to `--local` except that `.git/config.worktree` is
+ Similar to `--local` except that `$GIT_DIR/config.worktree` is
read from or written to if `extensions.worktreeConfig` is
- present. If not it's the same as `--local`.
+ enabled. If not it's the same as `--local`. Note that `$GIT_DIR`
+ is equal to `$GIT_COMMON_DIR` for the main working tree, but is of
+ the form `$GIT_DIR/worktrees/<id>/` for other working trees. See
+ linkgit:git-worktree[1] to learn how to enable
+ `extensions.worktreeConfig`.
-f <config-file>::
--file <config-file>::
SYNOPSIS
--------
[verse]
-'git help' [-a|--all [--[no-]verbose]]
- [[-i|--info] [-m|--man] [-w|--web]] [<command>|<guide>]
+'git help' [-a|--all] [--[no-]verbose] [--[no-]external-commands] [--[no-]aliases]
+'git help' [[-i|--info] [-m|--man] [-w|--web]] [<command>|<guide>]
'git help' [-g|--guides]
'git help' [-c|--config]
-------
-a::
--all::
- Prints all the available commands on the standard output. This
- option overrides any given command or guide name.
+ Prints all the available commands on the standard output.
+
+--no-external-commands::
+ When used with `--all`, exclude the listing of external "git-*"
+ commands found in the `$PATH`.
+
+--no-aliases::
+ When used with `--all`, exclude the listing of configured
+ aliases.
--verbose::
When used with `--all` print description for all recognized
--- /dev/null
+git-hook(1)
+===========
+
+NAME
+----
+git-hook - Run git hooks
+
+SYNOPSIS
+--------
+[verse]
+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
+
+DESCRIPTION
+-----------
+
+A command interface to running git hooks (see linkgit:githooks[5]),
+for use by other scripted git commands.
+
+SUBCOMMANDS
+-----------
+
+run::
+ Run the `<hook-name>` hook. See linkgit:githooks[5] for
+ supported hook names.
++
+
+Any positional arguments to the hook should be passed after a
+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
+linkgit:githooks[5] for arguments hooks might expect (if any).
+
+OPTIONS
+-------
+
+--ignore-missing::
+ Ignore any missing hook by quietly returning zero. Used for
+ tools that want to do a blind one-shot run of a hook that may
+ or may not be present.
+
+SEE ALSO
+--------
+linkgit:githooks[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
--recurse-submodules::
Recursively calls ls-files on each active submodule in the repository.
- Currently there is only support for the --cached mode.
+ Currently there is only support for the --cached and --stage modes.
--abbrev[=<n>]::
Instead of showing the full 40-byte hexadecimal object
--batch::
Allow building of more than one tree object before exiting. Each
- tree is separated by as single blank line. The final new-line is
+ tree is separated by a single blank line. The final new-line is
optional. Note - if the `-z` option is used, lines are terminated
with NUL.
--all::
List all commits reachable from all refs
---stdin::
+--annotate-stdin::
Transform stdin by substituting all the 40-character SHA-1
hexes (say $hex) with "$hex ($rev_name)". When used with
--name-only, substitute with "$rev_name", omitting $hex
- altogether. Intended for the scripter's use.
+ altogether.
++
+For example:
++
+-----------
+$ cat sample.txt
+
+An abbreviated revision 2ae0a9cb82 will not be substituted.
+The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907,
+while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad
+
+$ git name-rev --annotate-stdin <sample.txt
+
+An abbreviated revision 2ae0a9cb82 will not be substituted.
+The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907 (master),
+while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad
+
+$ git name-rev --name-only --annotate-stdin <sample.txt
+
+An abbreviated revision 2ae0a9cb82 will not be substituted.
+The full name after substitution is master,
+while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad
+-----------
+
+--stdin::
+ This option is deprecated in favor of 'git name-rev --annotate-stdin'.
+ They are functionally equivalent.
--name-only::
Instead of printing both the SHA-1 and the name, print only
SPARSE CHECKOUT
---------------
+Note: The `update-index` and `read-tree` primitives for supporting the
+skip-worktree bit predated the introduction of
+linkgit:git-sparse-checkout[1]. Users are encouraged to use
+`sparse-checkout` in preference to these low-level primitives.
+
"Sparse checkout" allows populating the working directory sparsely.
-It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell
-Git whether a file in the working directory is worth looking at.
+It uses the skip-worktree bit (see linkgit:git-update-index[1]) to
+tell Git whether a file in the working directory is worth looking at.
'git read-tree' and other merge-based commands ('git merge', 'git
checkout'...) can help maintaining the skip-worktree bitmap and working
define the skip-worktree reference bitmap. When 'git read-tree' needs
to update the working directory, it resets the skip-worktree bit in the index
based on this file, which uses the same syntax as .gitignore files.
-If an entry matches a pattern in this file, skip-worktree will not be
+If an entry matches a pattern in this file, or the entry corresponds to
+a file present in the working tree, then skip-worktree will not be
set on that entry. Otherwise, skip-worktree will be set.
Then it compares the new skip-worktree value with the previous one. If
SEE ALSO
--------
-linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
-linkgit:gitignore[5]; linkgit:git-sparse-checkout[1];
+linkgit:git-write-tree[1], linkgit:git-ls-files[1],
+linkgit:gitignore[5], linkgit:git-sparse-checkout[1]
GIT
---
NAME
----
-git-sparse-checkout - Initialize and modify the sparse-checkout
-configuration, which reduces the checkout to a set of paths
-given by a list of patterns.
+git-sparse-checkout - Reduce your working tree to a subset of tracked files
SYNOPSIS
DESCRIPTION
-----------
-Initialize and modify the sparse-checkout configuration, which reduces
-the checkout to a set of paths given by a list of patterns.
+This command is used to create sparse checkouts, which means that it
+changes the working tree from having all tracked files present, to only
+have a subset of them. It can also switch which subset of files are
+present, or undo and go back to having all tracked files present in the
+working copy.
+
+The subset of files is chosen by providing a list of directories in
+cone mode (which is recommended), or by providing a list of patterns
+in non-cone mode.
+
+When in a sparse-checkout, other Git commands behave a bit differently.
+For example, switching branches will not update paths outside the
+sparse-checkout directories/patterns, and `git commit -a` will not record
+paths outside the sparse-checkout directories/patterns as deleted.
THIS COMMAND IS EXPERIMENTAL. ITS BEHAVIOR, AND THE BEHAVIOR OF OTHER
COMMANDS IN THE PRESENCE OF SPARSE-CHECKOUTS, WILL LIKELY CHANGE IN
COMMANDS
--------
'list'::
- Describe the patterns in the sparse-checkout file.
+ Describe the directories or patterns in the sparse-checkout file.
'set'::
- Enable the necessary config settings
- (extensions.worktreeConfig, core.sparseCheckout,
- core.sparseCheckoutCone) if they are not already enabled, and
- write a set of patterns to the sparse-checkout file from the
+ Enable the necessary sparse-checkout config settings
+ (`core.sparseCheckout`, `core.sparseCheckoutCone`, and
+ `index.sparse`) if they are not already set to the desired values,
+ and write a set of patterns to the sparse-checkout file from the
list of arguments following the 'set' subcommand. Update the
working directory to match the new patterns.
+
-When the `--stdin` option is provided, the patterns are read from
-standard in as a newline-delimited list instead of from the arguments.
+To ensure that adjusting the sparse-checkout settings within a worktree
+does not alter the sparse-checkout settings in other worktrees, the 'set'
+subcommand will upgrade your repository config to use worktree-specific
+config if not already present. The sparsity defined by the arguments to
+the 'set' subcommand are stored in the worktree-specific sparse-checkout
+file. See linkgit:git-worktree[1] and the documentation of
+`extensions.worktreeConfig` in linkgit:git-config[1] for more details.
++
+When the `--stdin` option is provided, the directories or patterns are
+read from standard in as a newline-delimited list instead of from the
+arguments.
+
When `--cone` is passed or `core.sparseCheckoutCone` is enabled, the
-input list is considered a list of directories instead of
-sparse-checkout patterns. This allows for better performance with a
-limited set of patterns (see 'CONE PATTERN SET' below). Note that the
-set command will write patterns to the sparse-checkout file to include
-all files contained in those directories (recursively) as well as
-files that are siblings of ancestor directories. The input format
-matches the output of `git ls-tree --name-only`. This includes
-interpreting pathnames that begin with a double quote (") as C-style
-quoted strings. This may become the default in the future; --no-cone
-can be passed to request non-cone mode.
+input list is considered a list of directories. This allows for
+better performance with a limited set of patterns (see 'CONE PATTERN
+SET' below). The input format matches the output of `git ls-tree
+--name-only`. This includes interpreting pathnames that begin with a
+double quote (") as C-style quoted strings. Note that the set command
+will write patterns to the sparse-checkout file to include all files
+contained in those directories (recursively) as well as files that are
+siblings of ancestor directories. This may become the default in the
+future; --no-cone can be passed to request non-cone mode.
++
+When `--no-cone` is passed or `core.sparseCheckoutCone` is not enabled,
+the input list is considered a list of patterns. This mode is harder
+to use and less performant, and is thus not recommended. See the
+"Sparse Checkout" section of linkgit:git-read-tree[1] and the "Pattern
+Set" sections below for more details.
+
Use the `--[no-]sparse-index` option to use a sparse index (the
default is to not use it). A sparse index reduces the size of the
interact with your repository until it is disabled.
'add'::
- Update the sparse-checkout file to include additional patterns.
- By default, these patterns are read from the command-line arguments,
- but they can be read from stdin using the `--stdin` option. When
- `core.sparseCheckoutCone` is enabled, the given patterns are interpreted
- as directory names as in the 'set' subcommand.
+ Update the sparse-checkout file to include additional directories
+ (in cone mode) or patterns (in non-cone mode). By default, these
+ directories or patterns are read from the command-line arguments,
+ but they can be read from stdin using the `--stdin` option.
'reapply'::
Reapply the sparsity pattern rules to paths in the working tree.
SPARSE CHECKOUT
---------------
-"Sparse checkout" allows populating the working directory sparsely.
-It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell
-Git whether a file in the working directory is worth looking at. If
-the skip-worktree bit is set, then the file is ignored in the working
-directory. Git will avoid populating the contents of those files, which
-makes a sparse checkout helpful when working in a repository with many
-files, but only a few are important to the current user.
+"Sparse checkout" allows populating the working directory sparsely. It
+uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell Git
+whether a file in the working directory is worth looking at. If the
+skip-worktree bit is set, and the file is not present in the working tree,
+then its absence is ignored. Git will avoid populating the contents of
+those files, which makes a sparse checkout helpful when working in a
+repository with many files, but only a few are important to the current
+user.
The `$GIT_DIR/info/sparse-checkout` file is used to define the
skip-worktree reference bitmap. When Git updates the working
that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
options.
-update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--] [<path>...]::
+update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter <filter spec>] [--] [<path>...]::
+
--
Update the registered submodules to match what the superproject
If `--recursive` is specified, this command will recurse into the
registered submodules, and update any nested submodules within.
+
+If `--filter <filter spec>` is specified, the given partial clone filter will be
+applied to the submodule. See linkgit:git-rev-list[1] for details on filter
+specifications.
--
set-branch (-b|--branch) <branch> [--] <path>::
set-branch (-d|--default) [--] <path>::
the index (use `git update-index --really-refresh` if you want
to mark them as "assume unchanged").
+Sometimes users confuse the assume-unchanged bit with the
+skip-worktree bit. See the final paragraph in the "Skip-worktree bit"
+section below for an explanation of the differences.
+
EXAMPLES
--------
SKIP-WORKTREE BIT
-----------------
-Skip-worktree bit can be defined in one (long) sentence: When reading
-an entry, if it is marked as skip-worktree, then Git pretends its
-working directory version is up to date and read the index version
-instead.
-
-To elaborate, "reading" means checking for file existence, reading
-file attributes or file content. The working directory version may be
-present or absent. If present, its content may match against the index
-version or not. Writing is not affected by this bit, content safety
-is still first priority. Note that Git _can_ update working directory
-file, that is marked skip-worktree, if it is safe to do so (i.e.
-working directory version matches index version)
+Skip-worktree bit can be defined in one (long) sentence: Tell git to
+avoid writing the file to the working directory when reasonably
+possible, and treat the file as unchanged when it is not
+present in the working directory.
+
+Note that not all git commands will pay attention to this bit, and
+some only partially support it.
+
+The update-index flags and the read-tree capabilities relating to the
+skip-worktree bit predated the introduction of the
+linkgit:git-sparse-checkout[1] command, which provides a much easier
+way to configure and handle the skip-worktree bits. If you want to
+reduce your working tree to only deal with a subset of the files in
+the repository, we strongly encourage the use of
+linkgit:git-sparse-checkout[1] in preference to the low-level
+update-index and read-tree primitives.
+
+The primary purpose of the skip-worktree bit is to enable sparse
+checkouts, i.e. to have working directories with only a subset of
+paths present. When the skip-worktree bit is set, Git commands (such
+as `switch`, `pull`, `merge`) will avoid writing these files.
+However, these commands will sometimes write these files anyway in
+important cases such as conflicts during a merge or rebase. Git
+commands will also avoid treating the lack of such files as an
+intentional deletion; for example `git add -u` will not not stage a
+deletion for these files and `git commit -a` will not make a commit
+deleting them either.
Although this bit looks similar to assume-unchanged bit, its goal is
-different from assume-unchanged bit's. Skip-worktree also takes
-precedence over assume-unchanged bit when both are set.
+different. The assume-unchanged bit is for leaving the file in the
+working tree but having Git omit checking it for changes and presuming
+that the file has not been changed (though if it can determine without
+stat'ing the file that it has changed, it is free to record the
+changes). skip-worktree tells Git to ignore the absence of the file,
+avoid updating it when possible with commands that normally update
+much of the working directory (e.g. `checkout`, `switch`, `pull`,
+etc.), and not have its absence be recorded in commits. Note that in
+sparse checkouts (setup by `git sparse-checkout` or by configuring
+core.sparseCheckout to true), if a file is marked as skip-worktree in
+the index but is found in the working tree, Git will clear the
+skip-worktree bit for that file.
SPLIT INDEX
-----------
A git repository can support multiple working trees, allowing you to check
out more than one branch at a time. With `git worktree add` a new working
-tree is associated with the repository. This new working tree is called a
-"linked working tree" as opposed to the "main working tree" prepared by
-linkgit:git-init[1] or linkgit:git-clone[1].
-A repository has one main working tree (if it's not a
-bare repository) and zero or more linked working trees. When you are done
-with a linked working tree, remove it with `git worktree remove`.
+tree is associated with the repository, along with additional metadata
+that differentiates that working tree from others in the same repository.
+The working tree, along with this metadata, is called a "worktree".
+
+This new worktree is called a "linked worktree" as opposed to the "main
+worktree" prepared by linkgit:git-init[1] or linkgit:git-clone[1].
+A repository has one main worktree (if it's not a bare repository) and
+zero or more linked worktrees. When you are done with a linked worktree,
+remove it with `git worktree remove`.
In its simplest form, `git worktree add <path>` automatically creates a
new branch whose name is the final component of `<path>`, which is
convenient if you plan to work on a new topic. For instance, `git
worktree add ../hotfix` creates new branch `hotfix` and checks it out at
-path `../hotfix`. To instead work on an existing branch in a new working
-tree, use `git worktree add <path> <branch>`. On the other hand, if you
-just plan to make some experimental changes or do testing without
-disturbing existing development, it is often convenient to create a
-'throwaway' working tree not associated with any branch. For instance,
-`git worktree add -d <path>` creates a new working tree with a detached
-`HEAD` at the same commit as the current branch.
+path `../hotfix`. To instead work on an existing branch in a new worktree,
+use `git worktree add <path> <branch>`. On the other hand, if you just
+plan to make some experimental changes or do testing without disturbing
+existing development, it is often convenient to create a 'throwaway'
+worktree not associated with any branch. For instance,
+`git worktree add -d <path>` creates a new worktree with a detached `HEAD`
+at the same commit as the current branch.
If a working tree is deleted without using `git worktree remove`, then
its associated administrative files, which reside in the repository
(see "DETAILS" below), will eventually be removed automatically (see
`gc.worktreePruneExpire` in linkgit:git-config[1]), or you can run
-`git worktree prune` in the main or any linked working tree to
-clean up any stale administrative files.
+`git worktree prune` in the main or any linked worktree to clean up any
+stale administrative files.
-If a linked working tree is stored on a portable device or network share
-which is not always mounted, you can prevent its administrative files from
-being pruned by issuing the `git worktree lock` command, optionally
-specifying `--reason` to explain why the working tree is locked.
+If the working tree for a linked worktree is stored on a portable device
+or network share which is not always mounted, you can prevent its
+administrative files from being pruned by issuing the `git worktree lock`
+command, optionally specifying `--reason` to explain why the worktree is
+locked.
COMMANDS
--------
add <path> [<commit-ish>]::
-Create `<path>` and checkout `<commit-ish>` into it. The new working directory
-is linked to the current repository, sharing everything except working
-directory specific files such as `HEAD`, `index`, etc. As a convenience,
-`<commit-ish>` may be a bare "`-`", which is synonymous with `@{-1}`.
+Create a worktree at `<path>` and checkout `<commit-ish>` into it. The new worktree
+is linked to the current repository, sharing everything except per-worktree
+files such as `HEAD`, `index`, etc. As a convenience, `<commit-ish>` may
+be a bare "`-`", which is synonymous with `@{-1}`.
+
If `<commit-ish>` is a branch name (call it `<branch>`) and is not found,
and neither `-b` nor `-B` nor `--detach` are used, but there does
linkgit:git-config[1].
+
If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
-then, as a convenience, the new working tree is associated with a branch
-(call it `<branch>`) named after `$(basename <path>)`. If `<branch>`
-doesn't exist, a new branch based on `HEAD` is automatically created as
-if `-b <branch>` was given. If `<branch>` does exist, it will be
-checked out in the new working tree, if it's not checked out anywhere
-else, otherwise the command will refuse to create the working tree (unless
-`--force` is used).
+then, as a convenience, the new worktree is associated with a branch (call
+it `<branch>`) named after `$(basename <path>)`. If `<branch>` doesn't
+exist, a new branch based on `HEAD` is automatically created as if
+`-b <branch>` was given. If `<branch>` does exist, it will be checked out
+in the new worktree, if it's not checked out anywhere else, otherwise the
+command will refuse to create the worktree (unless `--force` is used).
list::
-List details of each working tree. The main working tree is listed first,
-followed by each of the linked working trees. The output details include
-whether the working tree is bare, the revision currently checked out, the
+List details of each worktree. The main worktree is listed first,
+followed by each of the linked worktrees. The output details include
+whether the worktree is bare, the revision currently checked out, the
branch currently checked out (or "detached HEAD" if none), "locked" if
-the worktree is locked, "prunable" if the worktree can be pruned by `prune`
-command.
+the worktree is locked, "prunable" if the worktree can be pruned by the
+`prune` command.
lock::
-If a working tree is on a portable device or network share which
-is not always mounted, lock it to prevent its administrative
-files from being pruned automatically. This also prevents it from
-being moved or deleted. Optionally, specify a reason for the lock
-with `--reason`.
+If a worktree is on a portable device or network share which is not always
+mounted, lock it to prevent its administrative files from being pruned
+automatically. This also prevents it from being moved or deleted.
+Optionally, specify a reason for the lock with `--reason`.
move::
-Move a working tree to a new location. Note that the main working tree
-or linked working trees containing submodules cannot be moved with this
-command. (The `git worktree repair` command, however, can reestablish
-the connection with linked working trees if you move the main working
-tree manually.)
+Move a worktree to a new location. Note that the main worktree or linked
+worktrees containing submodules cannot be moved with this command. (The
+`git worktree repair` command, however, can reestablish the connection
+with linked worktrees if you move the main worktree manually.)
prune::
-Prune working tree information in `$GIT_DIR/worktrees`.
+Prune worktree information in `$GIT_DIR/worktrees`.
remove::
-Remove a working tree. Only clean working trees (no untracked files
-and no modification in tracked files) can be removed. Unclean working
-trees or ones with submodules can be removed with `--force`. The main
-working tree cannot be removed.
+Remove a worktree. Only clean worktrees (no untracked files and no
+modification in tracked files) can be removed. Unclean worktrees or ones
+with submodules can be removed with `--force`. The main worktree cannot be
+removed.
repair [<path>...]::
-Repair working tree administrative files, if possible, if they have
-become corrupted or outdated due to external factors.
+Repair worktree administrative files, if possible, if they have become
+corrupted or outdated due to external factors.
+
-For instance, if the main working tree (or bare repository) is moved,
-linked working trees will be unable to locate it. Running `repair` in
-the main working tree will reestablish the connection from linked
-working trees back to the main working tree.
+For instance, if the main worktree (or bare repository) is moved, linked
+worktrees will be unable to locate it. Running `repair` in the main
+worktree will reestablish the connection from linked worktrees back to the
+main worktree.
+
-Similarly, if a linked working tree is moved without using `git worktree
-move`, the main working tree (or bare repository) will be unable to
-locate it. Running `repair` within the recently-moved working tree will
-reestablish the connection. If multiple linked working trees are moved,
-running `repair` from any working tree with each tree's new `<path>` as
-an argument, will reestablish the connection to all the specified paths.
+Similarly, if the working tree for a linked worktree is moved without
+using `git worktree move`, the main worktree (or bare repository) will be
+unable to locate it. Running `repair` within the recently-moved worktree
+will reestablish the connection. If multiple linked worktrees are moved,
+running `repair` from any worktree with each tree's new `<path>` as an
+argument, will reestablish the connection to all the specified paths.
+
-If both the main working tree and linked working trees have been moved
-manually, then running `repair` in the main working tree and specifying the
-new `<path>` of each linked working tree will reestablish all connections
-in both directions.
+If both the main worktree and linked worktrees have been moved manually,
+then running `repair` in the main worktree and specifying the new `<path>`
+of each linked worktree will reestablish all connections in both
+directions.
unlock::
-Unlock a working tree, allowing it to be pruned, moved or deleted.
+Unlock a worktree, allowing it to be pruned, moved or deleted.
OPTIONS
-------
-f::
--force::
- By default, `add` refuses to create a new working tree when
+ By default, `add` refuses to create a new worktree when
`<commit-ish>` is a branch name and is already checked out by
- another working tree, or if `<path>` is already assigned to some
- working tree but is missing (for instance, if `<path>` was deleted
+ another worktree, or if `<path>` is already assigned to some
+ worktree but is missing (for instance, if `<path>` was deleted
manually). This option overrides these safeguards. To add a missing but
- locked working tree path, specify `--force` twice.
+ locked worktree path, specify `--force` twice.
+
-`move` refuses to move a locked working tree unless `--force` is specified
-twice. If the destination is already assigned to some other working tree but is
+`move` refuses to move a locked worktree unless `--force` is specified
+twice. If the destination is already assigned to some other worktree but is
missing (for instance, if `<new-path>` was deleted manually), then `--force`
allows the move to proceed; use `--force` twice if the destination is locked.
+
-`remove` refuses to remove an unclean working tree unless `--force` is used.
-To remove a locked working tree, specify `--force` twice.
+`remove` refuses to remove an unclean worktree unless `--force` is used.
+To remove a locked worktree, specify `--force` twice.
-b <new-branch>::
-B <new-branch>::
With `add`, create a new branch named `<new-branch>` starting at
- `<commit-ish>`, and check out `<new-branch>` into the new working tree.
+ `<commit-ish>`, and check out `<new-branch>` into the new worktree.
If `<commit-ish>` is omitted, it defaults to `HEAD`.
By default, `-b` refuses to create a new branch if it already
exists. `-B` overrides this safeguard, resetting `<new-branch>` to
-d::
--detach::
- With `add`, detach `HEAD` in the new working tree. See "DETACHED HEAD"
+ With `add`, detach `HEAD` in the new worktree. See "DETACHED HEAD"
in linkgit:git-checkout[1].
--[no-]checkout::
`--track` in linkgit:git-branch[1] for details.
--lock::
- Keep the working tree locked after creation. This is the
+ Keep the worktree locked after creation. This is the
equivalent of `git worktree lock` after `git worktree add`,
but without a race condition.
With `list`, output additional information about worktrees (see below).
--expire <time>::
- With `prune`, only expire unused working trees older than `<time>`.
+ With `prune`, only expire unused worktrees older than `<time>`.
+
-With `list`, annotate missing working trees as prunable if they are
-older than `<time>`.
+With `list`, annotate missing worktrees as prunable if they are older than
+`<time>`.
--reason <string>::
- With `lock` or with `add --lock`, an explanation why the working tree is locked.
+ With `lock` or with `add --lock`, an explanation why the worktree
+ is locked.
<worktree>::
- Working trees can be identified by path, either relative or
- absolute.
+ Worktrees can be identified by path, either relative or absolute.
+
-If the last path components in the working tree's path is unique among
-working trees, it can be used to identify a working tree. For example if
-you only have two working trees, at `/abc/def/ghi` and `/abc/def/ggg`,
-then `ghi` or `def/ghi` is enough to point to the former working tree.
+If the last path components in the worktree's path is unique among
+worktrees, it can be used to identify a worktree. For example if you only
+have two worktrees, at `/abc/def/ghi` and `/abc/def/ggg`, then `ghi` or
+`def/ghi` is enough to point to the former worktree.
REFS
----
-In multiple working trees, some refs may be shared between all working
-trees and some refs are local. One example is `HEAD` which is different for each
-working tree. This section is about the sharing rules and how to access
-refs of one working tree from another.
-
-In general, all pseudo refs are per working tree and all refs starting
-with `refs/` are shared. Pseudo refs are ones like `HEAD` which are
-directly under `$GIT_DIR` instead of inside `$GIT_DIR/refs`. There are
-exceptions, however: refs inside `refs/bisect` and `refs/worktree` are not
-shared.
-
-Refs that are per working tree can still be accessed from another
-working tree via two special paths, `main-worktree` and `worktrees`. The
-former gives access to per-working tree refs of the main working tree,
-while the latter to all linked working trees.
+When using multiple worktrees, some refs are shared between all worktrees,
+but others are specific to an individual worktree. One example is `HEAD`,
+which is different for each worktree. This section is about the sharing
+rules and how to access refs of one worktree from another.
+
+In general, all pseudo refs are per-worktree and all refs starting with
+`refs/` are shared. Pseudo refs are ones like `HEAD` which are directly
+under `$GIT_DIR` instead of inside `$GIT_DIR/refs`. There are exceptions,
+however: refs inside `refs/bisect` and `refs/worktree` are not shared.
+
+Refs that are per-worktree can still be accessed from another worktree via
+two special paths, `main-worktree` and `worktrees`. The former gives
+access to per-worktree refs of the main worktree, while the latter to all
+linked worktrees.
For example, `main-worktree/HEAD` or `main-worktree/refs/bisect/good`
-resolve to the same value as the main working tree's `HEAD` and
+resolve to the same value as the main worktree's `HEAD` and
`refs/bisect/good` respectively. Similarly, `worktrees/foo/HEAD` or
`worktrees/bar/refs/bisect/bad` are the same as
`$GIT_COMMON_DIR/worktrees/foo/HEAD` and
CONFIGURATION FILE
------------------
-By default, the repository `config` file is shared across all working
-trees. If the config variables `core.bare` or `core.worktree` are
-already present in the config file, they will be applied to the main
-working trees only.
+By default, the repository `config` file is shared across all worktrees.
+If the config variables `core.bare` or `core.worktree` are present in the
+common config file and `extensions.worktreeConfig` is disabled, then they
+will be applied to the main worktree only.
-In order to have configuration specific to working trees, you can turn
-on the `worktreeConfig` extension, e.g.:
+In order to have worktree-specific configuration, you can turn on the
+`worktreeConfig` extension, e.g.:
------------
$ git config extensions.worktreeConfig true
Note that in this file, the exception for `core.bare` and `core.worktree`
is gone. If they exist in `$GIT_DIR/config`, you must move
-them to the `config.worktree` of the main working tree. You may also
-take this opportunity to review and move other configuration that you
-do not want to share to all working trees:
+them to the `config.worktree` of the main worktree. You may also take this
+opportunity to review and move other configuration that you do not want to
+share to all worktrees:
+
+ - `core.worktree` should never be shared.
+
+ - `core.bare` should not be shared if the value is `core.bare=true`.
- - `core.worktree` and `core.bare` should never be shared
+ - `core.sparseCheckout` should not be shared, unless you are sure you
+ always use sparse checkout for all worktrees.
- - `core.sparseCheckout` is recommended per working tree, unless you
- are sure you always use sparse checkout for all working trees.
+See the documentation of `extensions.worktreeConfig` in
+linkgit:git-config[1] for more details.
DETAILS
-------
-Each linked working tree has a private sub-directory in the repository's
+Each linked worktree has a private sub-directory in the repository's
`$GIT_DIR/worktrees` directory. The private sub-directory's name is usually
-the base name of the linked working tree's path, possibly appended with a
+the base name of the linked worktree's path, possibly appended with a
number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
command `git worktree add /path/other/test-next next` creates the linked
-working tree in `/path/other/test-next` and also creates a
+worktree in `/path/other/test-next` and also creates a
`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
if `test-next` is already taken).
-Within a linked working tree, `$GIT_DIR` is set to point to this private
+Within a linked worktree, `$GIT_DIR` is set to point to this private
directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
-`$GIT_COMMON_DIR` is set to point back to the main working tree's `$GIT_DIR`
+`$GIT_COMMON_DIR` is set to point back to the main worktree's `$GIT_DIR`
(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
-the top directory of the linked working tree.
+the top directory of the linked worktree.
Path resolution via `git rev-parse --git-path` uses either
`$GIT_DIR` or `$GIT_COMMON_DIR` depending on the path. For example, in the
-linked working tree `git rev-parse --git-path HEAD` returns
+linked worktree `git rev-parse --git-path HEAD` returns
`/path/main/.git/worktrees/test-next/HEAD` (not
`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
rev-parse --git-path refs/heads/master` uses
`$GIT_COMMON_DIR` and returns `/path/main/.git/refs/heads/master`,
-since refs are shared across all working trees, except `refs/bisect` and
+since refs are shared across all worktrees, except `refs/bisect` and
`refs/worktree`.
See linkgit:gitrepository-layout[5] for more information. The rule of
`$GIT_DIR` or `$GIT_COMMON_DIR` when you need to directly access something
inside `$GIT_DIR`. Use `git rev-parse --git-path` to get the final path.
-If you manually move a linked working tree, you need to update the `gitdir` file
-in the entry's directory. For example, if a linked working tree is moved
+If you manually move a linked worktree, you need to update the `gitdir` file
+in the entry's directory. For example, if a linked worktree is moved
to `/newpath/test-next` and its `.git` file points to
`/path/main/.git/worktrees/test-next`, then update
`/path/main/.git/worktrees/test-next/gitdir` to reference `/newpath/test-next`
To prevent a `$GIT_DIR/worktrees` entry from being pruned (which
can be useful in some situations, such as when the
-entry's working tree is stored on a portable device), use the
+entry's worktree is stored on a portable device), use the
`git worktree lock` command, which adds a file named
`locked` to the entry's directory. The file contains the reason in
-plain text. For example, if a linked working tree's `.git` file points
+plain text. For example, if a linked worktree's `.git` file points
to `/path/main/.git/worktrees/test-next` then a file named
`/path/main/.git/worktrees/test-next/locked` will prevent the
`test-next` entry from being pruned. See
/path/to/other-linked-worktree 1234abc (detached HEAD)
------------
-The command also shows annotations for each working tree, according to its state.
+The command also shows annotations for each worktree, according to its state.
These annotations are:
- * `locked`, if the working tree is locked.
- * `prunable`, if the working tree can be pruned via `git worktree prune`.
+ * `locked`, if the worktree is locked.
+ * `prunable`, if the worktree can be pruned via `git worktree prune`.
------------
$ git worktree list
/path/to/linked-worktree abcd1234 [master]
/path/to/locked-worktree-no-reason abcd5678 (detached HEAD) locked
/path/to/locked-worktree-with-reason 1234abcd (brancha)
- locked: working tree path is mounted on a portable device
+ locked: worktree path is mounted on a portable device
/path/to/prunable-worktree 5678abc1 (detached HEAD)
prunable: gitdir file points to non-existent location
------------
Note that the annotation is moved to the next line if the additional
information is available, otherwise it stays on the same line as the
-working tree itself.
+worktree itself.
Porcelain Format
~~~~~~~~~~~~~~~~
and `detached`) are listed as a label only, and are present only
if the value is true. Some attributes (like `locked`) can be listed as a label
only or with a value depending upon whether a reason is available. The first
-attribute of a working tree is always `worktree`, an empty line indicates the
+attribute of a worktree is always `worktree`, an empty line indicates the
end of the record. For example:
------------
linkgit:git-stash[1] to store your changes away temporarily, however, your
working tree is in such a state of disarray (with new, moved, and removed
files, and other bits and pieces strewn around) that you don't want to risk
-disturbing any of it. Instead, you create a temporary linked working tree to
+disturbing any of it. Instead, you create a temporary linked worktree to
make the emergency fix, remove it when done, and then resume your earlier
refactoring session.
^^^^^
This attribute sets a specific line-ending style to be used in the
-working directory. It enables end-of-line conversion without any
-content checks, effectively setting the `text` attribute. Note that
-setting this attribute on paths which are in the index with CRLF line
-endings may make the paths to be considered dirty. Adding the path to
-the index again will normalize the line endings in the index.
+working directory. This attribute has effect only if the `text`
+attribute is set or unspecified, or if it is set to `auto`, the file is
+detected as text, and it is stored with LF endings in the index. Note
+that setting this attribute on paths which are in the index with CRLF
+line endings may make the paths to be considered dirty unless
+`text=auto` is set. Adding the path to the index again will normalize
+the line endings in the index.
Set to string value "crlf"::
"tree-ish", depending on the context and command) and paths as their
arguments. Here are the rules:
+ * Options come first and then args.
+ A subcommand may take dashed options (which may take their own
+ arguments, e.g. "--max-parents 2") and arguments. You SHOULD
+ give dashed options first and then arguments. Some commands may
+ accept dashed options after you have already gave non-option
+ arguments (which may make the command ambiguous), but you should
+ not rely on it (because eventually we may find a way to fix
+ these ambiguity by enforcing the "options then args" rule).
+
* Revisions come first and then paths.
E.g. in `git diff v1.0 v2.0 arch/x86 include/asm-x86`,
`v1.0` and `v2.0` are revisions and `arch/x86` and `include/asm-x86`
Here are the rules regarding the "flags" that you should follow when you are
scripting Git:
- * it's preferred to use the non-dashed form of Git commands, which means that
+ * It's preferred to use the non-dashed form of Git commands, which means that
you should prefer `git foo` to `git-foo`.
- * splitting short options to separate words (prefer `git foo -a -b`
+ * Splitting short options to separate words (prefer `git foo -a -b`
to `git foo -ab`, the latter may not even work).
- * when a command-line option takes an argument, use the 'stuck' form. In
+ * When a command-line option takes an argument, use the 'stuck' form. In
other words, write `git foo -oArg` instead of `git foo -o Arg` for short
options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg`
for long options. An option that takes optional option-argument must be
written in the 'stuck' form.
- * when you give a revision parameter to a command, make sure the parameter is
+ * When you give a revision parameter to a command, make sure the parameter is
not ambiguous with a name of a file in the work tree. E.g. do not write
`git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work
if you happen to have a file called `HEAD` in the work tree.
- * many commands allow a long option `--option` to be abbreviated
+ * Many commands allow a long option `--option` to be abbreviated
only to their unique prefix (e.g. if there is no other option
whose name begins with `opt`, you may be able to spell `--opt` to
invoke the `--option` flag), but you should fully spell them out
Only one parameter should be set to "1" when the hook runs. The hook
running passing "1", "1" should not be possible.
+SEE ALSO
+--------
+linkgit:git-hook[1]
+
GIT
---
Part of the linkgit:git[1] suite
ls-tree", "git add", "git grep", "git diff", "git checkout",
and many other commands to
limit the scope of operations to some subset of the tree or
-worktree. See the documentation of each command for whether
+working tree. See the documentation of each command for whether
paths are relative to the current directory or toplevel. The
pathspec syntax is as follows:
+
interface than the <<def_plumbing,plumbing>>.
[[def_per_worktree_ref]]per-worktree ref::
- Refs that are per-<<def_working_tree,worktree>>, rather than
+ Refs that are per-<<def_worktree,worktree>>, rather than
global. This is presently only <<def_HEAD,HEAD>> and any refs
that start with `refs/bisect/`, but might later include other
unusual refs.
The tree of actual checked out files. The working tree normally
contains the contents of the <<def_HEAD,HEAD>> commit's tree,
plus any local changes that you have made but not yet committed.
+
+[[def_worktree]]worktree::
+ A repository can have zero (i.e. bare repository) or one or
+ more worktrees attached to it. One "worktree" consists of a
+ "working tree" and repository metadata, most of which are
+ shared among other worktrees of a single repository, and
+ some of which are maintained separately per worktree
+ (e.g. the index, HEAD and pseudorefs like MERGE_HEAD,
+ per-worktree refs and per-worktree configuration file).
parents) and `--max-parents=-1` (negative numbers denote no upper limit).
--first-parent::
- Follow only the first parent commit upon seeing a merge
- commit. This option can give a better overview when
- viewing the evolution of a particular topic branch,
- because merges into a topic branch tend to be only about
- adjusting to updated upstream from time to time, and
- this option allows you to ignore the individual commits
- brought in to your history by such a merge.
+ When finding commits to include, follow only the first
+ parent commit upon seeing a merge commit. This option
+ can give a better overview when viewing the evolution of
+ a particular topic branch, because merges into a topic
+ branch tend to be only about adjusting to updated upstream
+ from time to time, and this option allows you to ignore
+ the individual commits brought in to your history by such
+ a merge.
ifdef::git-log[]
+
This option also changes default diff format for merge commits
to `first-parent`, see `--diff-merges=first-parent` for details.
endif::git-log[]
+--exclude-first-parent-only::
+ When finding commits to exclude (with a '{caret}'), follow only
+ the first parent commit upon seeing a merge commit.
+ This can be used to find the set of changes in a topic branch
+ from the point where it diverged from the remote branch, given
+ that arbitrary merges can be valid topic branch changes.
+
--not::
Reverses the meaning of the '{caret}' prefix (or lack thereof)
for all following revision specifiers, up to the next `--not`.
** An offset within the jth packfile for the object.
* If large offsets are required, we use another list of large
offsets similar to version 2 pack-indexes.
+- An optional list of objects in pseudo-pack order (used with MIDX bitmaps).
Thus, we can provide O(log N) lookup time for any number
of packfiles.
[Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
8-byte offsets into large packfiles.
+ [Optional] Bitmap pack order (ID: {'R', 'I', 'D', 'X'})
+ A list of MIDX positions (one per object in the MIDX, num_objects in
+ total, each a 4-byte unsigned integer in network byte order), sorted
+ according to their relative bitmap/pseudo-pack positions.
+
TRAILER:
Index checksum of the above contents.
objects in packs stored by the MIDX, laid out in pack order, and the
packs arranged in MIDX order (with the preferred pack coming first).
-Finally, note that the MIDX's reverse index is not stored as a chunk in
-the multi-pack-index itself. This is done because the reverse index
-includes the checksum of the pack or MIDX to which it belongs, which
-makes it impossible to write in the MIDX. To avoid races when rewriting
-the MIDX, a MIDX reverse index includes the MIDX's checksum in its
-filename (e.g., `multi-pack-index-xyz.rev`).
+The MIDX's reverse index is stored in the optional 'RIDX' chunk within
+the MIDX itself.
Object blocks are optional. Writers may choose to omit object blocks,
especially if readers will not use the object name to ref mapping.
-Object blocks use unique, abbreviated 2-32 object name keys, mapping to
+Object blocks use unique, abbreviated 2-31 byte object name keys, mapping to
ref blocks containing references pointing to that object directly, or as
the peeled value of an annotated tag. Like ref blocks, object blocks use
the file's standard block size. The abbreviation length is available in
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.35.1
+DEF_VER=v2.35.GIT
LF='
'
# The default target of this Makefile is...
all::
+# Import tree-wide shared Makefile behavior and libraries
+include shared.mak
+
# Define V=1 to have a more verbose compile.
#
# Define SHELL_PATH to a POSIX shell if your /bin/sh is broken.
# Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support
# the executable mode bit, but doesn't really do so.
#
+# Define CSPRNG_METHOD to "arc4random" if your system has arc4random and
+# arc4random_buf, "libbsd" if your system has those functions from libbsd,
+# "getrandom" if your system has getrandom, "getentropy" if your system has
+# getentropy, "rtlgenrandom" for RtlGenRandom (Windows only), or "openssl" if
+# you'd want to use the OpenSSL CSPRNG. You may set multiple options with
+# spaces, in which case a suitable option will be chosen. If unset or set to
+# anything else, defaults to using "/dev/urandom".
+#
# Define NEEDS_MODE_TRANSLATION if your OS strays from the typical file type
# bits in mode values (e.g. z/OS defines I_SFMT to 0xFF000000 as opposed to the
# usual 0xF000).
#
# Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
#
-# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.
-#
# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
#
TEST_BUILTINS_OBJS += test-chmtime.o
TEST_BUILTINS_OBJS += test-config.o
TEST_BUILTINS_OBJS += test-crontab.o
+TEST_BUILTINS_OBJS += test-csprng.o
TEST_BUILTINS_OBJS += test-ctype.o
TEST_BUILTINS_OBJS += test-date.o
TEST_BUILTINS_OBJS += test-delta.o
.PHONY: generated-hdrs
generated-hdrs: $(GENERATED_H)
-LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \
+## Exhaustive lists of our source files, either dynamically generated,
+## or hardcoded.
+SOURCES_CMD = ( \
+ git ls-files \
+ '*.[hcS]' \
+ '*.sh' \
+ ':!*[tp][0-9][0-9][0-9][0-9]*' \
+ ':!contrib' \
+ 2>/dev/null || \
$(FIND) . \
- -name .git -prune -o \
- -name t -prune -o \
- -name Documentation -prune -o \
- -name '*.h' -print)))
+ \( -name .git -type d -prune \) \
+ -o \( -name '[tp][0-9][0-9][0-9][0-9]*' -prune \) \
+ -o \( -name contrib -type d -prune \) \
+ -o \( -name build -type d -prune \) \
+ -o \( -name 'trash*' -type d -prune \) \
+ -o \( -name '*.[hcS]' -type f -print \) \
+ -o \( -name '*.sh' -type f -print \) \
+ | sed -e 's|^\./||' \
+ )
+FOUND_SOURCE_FILES := $(shell $(SOURCES_CMD))
+
+FOUND_C_SOURCES = $(filter %.c,$(FOUND_SOURCE_FILES))
+FOUND_H_SOURCES = $(filter %.h,$(FOUND_SOURCE_FILES))
+
+COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES),$(FOUND_C_SOURCES))
+
+LIB_H = $(FOUND_H_SOURCES)
LIB_OBJS += abspath.o
LIB_OBJS += add-interactive.o
LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o
LIB_OBJS += compat/terminal.o
+LIB_OBJS += compat/zlib-uncompress2.o
LIB_OBJS += config.o
LIB_OBJS += connect.o
LIB_OBJS += connected.o
BUILTIN_OBJS += builtin/grep.o
BUILTIN_OBJS += builtin/hash-object.o
BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/hook.o
BUILTIN_OBJS += builtin/index-pack.o
BUILTIN_OBJS += builtin/init-db.o
BUILTIN_OBJS += builtin/interpret-trailers.o
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
+# xdiff and reftable libs may in turn depend on what is in libgit.a
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
EXTLIBS =
GIT_USER_AGENT = git/$(GIT_VERSION)
ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS)
ALL_LDFLAGS = $(LDFLAGS)
-comma := ,
-empty :=
-space := $(empty) $(empty)
-
ifdef SANITIZE
SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag))
BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE)
BASIC_CFLAGS += -DNO_DEFLATE_BOUND
endif
-ifdef NO_UNCOMPRESS2
- BASIC_CFLAGS += -DNO_UNCOMPRESS2
- REFTABLE_OBJS += compat/zlib-uncompress2.o
-endif
-
ifdef NO_POSIX_GOODIES
BASIC_CFLAGS += -DNO_POSIX_GOODIES
endif
BASIC_CFLAGS += -DHAVE_GETDELIM
endif
+ifneq ($(findstring arc4random,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_ARC4RANDOM
+endif
+
+ifneq ($(findstring libbsd,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_ARC4RANDOM_LIBBSD
+ EXTLIBS += -lbsd
+endif
+
+ifneq ($(findstring getrandom,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_GETRANDOM
+endif
+
+ifneq ($(findstring getentropy,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_GETENTROPY
+endif
+
+ifneq ($(findstring rtlgenrandom,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_RTLGENRANDOM
+endif
+
+ifneq ($(findstring openssl,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_OPENSSL_CSPRNG
+endif
+
ifneq ($(PROCFS_EXECUTABLE_PATH),)
procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH))
BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"'
PAGER_ENV = LESS=FRX LV=-c
endif
-QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
-QUIET_SUBDIR1 =
-
-ifneq ($(findstring w,$(MAKEFLAGS)),w)
-PRINT_DIR = --no-print-directory
-else # "make -w"
-NO_SUBDIR = :
-endif
-
-ifneq ($(findstring s,$(MAKEFLAGS)),s)
-ifndef V
- QUIET_CC = @echo ' ' CC $@;
- QUIET_AR = @echo ' ' AR $@;
- QUIET_LINK = @echo ' ' LINK $@;
- QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
- QUIET_GEN = @echo ' ' GEN $@;
- QUIET_LNCP = @echo ' ' LN/CP $@;
- QUIET_XGETTEXT = @echo ' ' XGETTEXT $@;
- QUIET_MSGFMT = @echo ' ' MSGFMT $@;
- QUIET_GCOV = @echo ' ' GCOV $@;
- QUIET_SP = @echo ' ' SP $<;
- QUIET_HDR = @echo ' ' HDR $(<:hcc=h);
- QUIET_RC = @echo ' ' RC $@;
- QUIET_SPATCH = @echo ' ' SPATCH $<;
- QUIET_SUBDIR0 = +@subdir=
- QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
- $(MAKE) $(PRINT_DIR) -C $$subdir
- export V
- export QUIET_GEN
- export QUIET_BUILT_IN
-endif
-endif
-
ifdef NO_INSTALL_HARDLINKS
export NO_INSTALL_HARDLINKS
endif
strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $^
-### Flags affecting all rules
-
-# A GNU make extension since gmake 3.72 (released in late 1994) to
-# remove the target of rules if commands in those rules fail. The
-# default is to only do that if make itself receives a signal. Affects
-# all targets, see:
-#
-# info make --index-search=.DELETE_ON_ERROR
-.DELETE_ON_ERROR:
-
### Target-specific flags and dependencies
# The generic compilation pattern rule and automatically
ASM_OBJ := $(ASM_SRC:S=o)
C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS))
-.SUFFIXES:
-
$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
endif
po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
- $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
+ $(call mkdir_p_parent_template)
+ $(QUIET_MSGFMT)$(MSGFMT) -o $@ $<
LIB_PERL := $(wildcard perl/Git.pm perl/Git/*.pm perl/Git/*/*.pm perl/Git/*/*/*.pm)
LIB_PERL_GEN := $(patsubst perl/%.pm,perl/build/lib/%.pm,$(LIB_PERL))
endif
perl/build/lib/%.pm: perl/%.pm GIT-PERL-DEFINES
- $(QUIET_GEN)mkdir -p $(dir $@) && \
+ $(call mkdir_p_parent_template)
+ $(QUIET_GEN) \
sed -e 's|@@LOCALEDIR@@|$(perl_localedir_SQ)|g' \
-e 's|@@NO_GETTEXT@@|$(NO_GETTEXT_SQ)|g' \
-e 's|@@NO_PERL_CPAN_FALLBACKS@@|$(NO_PERL_CPAN_FALLBACKS_SQ)|g' \
< $< > $@
perl/build/man/man3/Git.3pm: perl/Git.pm
- $(QUIET_GEN)mkdir -p $(dir $@) && \
- pod2man $< $@
-
-FIND_SOURCE_FILES = ( \
- git ls-files \
- '*.[hcS]' \
- '*.sh' \
- ':!*[tp][0-9][0-9][0-9][0-9]*' \
- ':!contrib' \
- 2>/dev/null || \
- $(FIND) . \
- \( -name .git -type d -prune \) \
- -o \( -name '[tp][0-9][0-9][0-9][0-9]*' -prune \) \
- -o \( -name contrib -type d -prune \) \
- -o \( -name build -type d -prune \) \
- -o \( -name 'trash*' -type d -prune \) \
- -o \( -name '*.[hcS]' -type f -print \) \
- -o \( -name '*.sh' -type f -print \) \
- | sed -e 's|^\./||' \
- )
-
-FOUND_SOURCE_FILES = $(shell $(FIND_SOURCE_FILES))
+ $(call mkdir_p_parent_template)
+ $(QUIET_GEN)pod2man $< $@
$(ETAGS_TARGET): $(FOUND_SOURCE_FILES)
$(QUIET_GEN)$(RM) $@+ && \
all:: $(TEST_PROGRAMS) $(test_bindir_programs)
bin-wrappers/%: wrap-for-bin.sh
- @mkdir -p bin-wrappers
+ $(call mkdir_p_parent_template)
$(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@BUILD_DIR@@|$(shell pwd)|' \
-e 's|@@PROG@@|$(patsubst test-%,t/helper/test-%$(X),$(@F))$(patsubst git%,$(X),$(filter $(@F),$(BINDIR_PROGRAMS_NEED_X)))|' < $< > $@ && \
exit 1; \
fi
-FOUND_C_SOURCES = $(filter %.c,$(FOUND_SOURCE_FILES))
-COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES),$(FOUND_C_SOURCES))
-
%.cocci.patch: %.cocci $(COCCI_SOURCES)
$(QUIET_SPATCH) \
if test $(SPATCH_BATCH_SIZE) = 0; then \
The user discussion and development of Git take place on the Git
mailing list -- everyone is welcome to post bug reports, feature
requests, comments and patches to git@vger.kernel.org (read
-[Documentation/SubmittingPatches][] for instructions on patch submission).
+[Documentation/SubmittingPatches][] for instructions on patch submission
+and [Documentation/CodingGuidelines][]).
+
+Those wishing to help with error message, usage and informational message
+string translations (localization l10) should see [po/README.md][]
+(a `po` file is a Portable Object file that holds the translations).
+
To subscribe to the list, send an email with just "subscribe git" in
-the body to majordomo@vger.kernel.org. The mailing list archives are
-available at <https://lore.kernel.org/git/>,
+the body to majordomo@vger.kernel.org (not the Git list). The mailing
+list archives are available at <https://lore.kernel.org/git/>,
<http://marc.info/?l=git> and other archival sites.
Issues which are security relevant should be disclosed privately to
[Documentation/giteveryday.txt]: Documentation/giteveryday.txt
[Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt
[Documentation/SubmittingPatches]: Documentation/SubmittingPatches
+[Documentation/CodingGuidelines]: Documentation/CodingGuidelines
+[po/README.md]: po/README.md
-Documentation/RelNotes/2.35.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.36.0.txt
\ No newline at end of file
diffopt.flags.override_submodule_config = 1;
diffopt.repo = s->r;
- if (do_diff_cache(&oid, &diffopt))
+ if (do_diff_cache(&oid, &diffopt)) {
+ diff_free(&diffopt);
res = -1;
- else {
+ } else {
diffcore_std(&diffopt);
diff_flush(&diffopt);
}
free(paths);
- clear_pathspec(&diffopt.pathspec);
if (!res && write_locked_index(s->r->index, &index_lock,
COMMIT_LOCK) < 0)
return 1;
}
+static void complete_file(char marker, struct hunk *hunk)
+{
+ if (marker == '-' || marker == '+')
+ /*
+ * Last hunk ended in non-context line (i.e. it
+ * appended lines to the file, so there are no
+ * trailing context lines).
+ */
+ hunk->splittable_into++;
+}
+
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
{
struct strvec args = STRVEC_INIT;
eol = pend;
if (starts_with(p, "diff ")) {
+ complete_file(marker, hunk);
ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1,
file_diff_alloc);
file_diff = s->file_diff + s->file_diff_nr - 1;
file_diff->hunk->colored_end = hunk->colored_end;
}
}
-
- if (marker == '-' || marker == '+')
- /*
- * Last hunk ended in non-context line (i.e. it appended lines
- * to the file, so there are no trailing context lines).
- */
- hunk->splittable_into++;
+ complete_file(marker, hunk);
/* non-colored shorter than colored? */
if (colored_p != colored_pend) {
[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 },
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 },
[ADVICE_DETACHED_HEAD] = { "detachedHead", 1 },
+ [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead", 1 },
[ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 1 },
[ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated", 1 },
[ADVICE_IGNORED_HOOK] = { "ignoredHook", 1 },
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
+ [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 },
[ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 },
[ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
};
ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
ADVICE_COMMIT_BEFORE_MERGE,
ADVICE_DETACHED_HEAD,
+ ADVICE_SUGGEST_DETACHING_HEAD,
ADVICE_FETCH_SHOW_FORCED_UPDATES,
ADVICE_GRAFT_FILE_DEPRECATED,
ADVICE_IGNORED_HOOK,
ADVICE_STATUS_HINTS,
ADVICE_STATUS_U_OPTION,
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+ ADVICE_SUBMODULES_NOT_UPDATED,
ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
ADVICE_SKIPPED_CHERRY_PICKS,
state->linenr = 1;
string_list_init_nodup(&state->fn_table);
string_list_init_nodup(&state->limit_by_name);
- string_list_init_nodup(&state->symlink_changes);
+ strset_init(&state->removed_symlinks);
+ strset_init(&state->kept_symlinks);
strbuf_init(&state->root, 0);
git_apply_config();
void clear_apply_state(struct apply_state *state)
{
string_list_clear(&state->limit_by_name, 0);
- string_list_clear(&state->symlink_changes, 0);
+ strset_clear(&state->removed_symlinks);
+ strset_clear(&state->kept_symlinks);
strbuf_release(&state->root);
/* &state->fn_table is cleared at the end of apply_patch() */
}
}
-static void free_patch(struct patch *patch)
+void release_patch(struct patch *patch)
{
free_fragment_list(patch->fragments);
free(patch->def_name);
free(patch->old_name);
free(patch->new_name);
free(patch->result);
+}
+
+static void free_patch(struct patch *patch)
+{
+ release_patch(patch);
free(patch);
}
{
mmfile_t base_file, our_file, their_file;
mmbuffer_t result = { NULL };
- int status;
+ enum ll_merge_result status;
/* resolve trivial cases first */
if (oideq(base, ours))
&their_file, "theirs",
state->repo->index,
NULL);
+ if (status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ path, "ours", "theirs");
free(base_file.ptr);
free(our_file.ptr);
free(their_file.ptr);
return 0;
}
-static uintptr_t register_symlink_changes(struct apply_state *state,
- const char *path,
- uintptr_t what)
-{
- struct string_list_item *ent;
-
- ent = string_list_lookup(&state->symlink_changes, path);
- if (!ent) {
- ent = string_list_insert(&state->symlink_changes, path);
- ent->util = (void *)0;
- }
- ent->util = (void *)(what | ((uintptr_t)ent->util));
- return (uintptr_t)ent->util;
-}
-
-static uintptr_t check_symlink_changes(struct apply_state *state, const char *path)
-{
- struct string_list_item *ent;
-
- ent = string_list_lookup(&state->symlink_changes, path);
- if (!ent)
- return 0;
- return (uintptr_t)ent->util;
-}
-
static void prepare_symlink_changes(struct apply_state *state, struct patch *patch)
{
for ( ; patch; patch = patch->next) {
if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
(patch->is_rename || patch->is_delete))
/* the symlink at patch->old_name is removed */
- register_symlink_changes(state, patch->old_name, APPLY_SYMLINK_GOES_AWAY);
+ strset_add(&state->removed_symlinks, patch->old_name);
if (patch->new_name && S_ISLNK(patch->new_mode))
/* the symlink at patch->new_name is created or remains */
- register_symlink_changes(state, patch->new_name, APPLY_SYMLINK_IN_RESULT);
+ strset_add(&state->kept_symlinks, patch->new_name);
}
}
static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *name)
{
do {
- unsigned int change;
-
while (--name->len && name->buf[name->len] != '/')
; /* scan backwards */
if (!name->len)
break;
name->buf[name->len] = '\0';
- change = check_symlink_changes(state, name->buf);
- if (change & APPLY_SYMLINK_IN_RESULT)
+ if (strset_contains(&state->kept_symlinks, name->buf))
return 1;
- if (change & APPLY_SYMLINK_GOES_AWAY)
+ if (strset_contains(&state->removed_symlinks, name->buf))
/*
* This cannot be "return 0", because we may
* see a new one created at a higher level.
#include "hash.h"
#include "lockfile.h"
#include "string-list.h"
+#include "strmap.h"
struct repository;
verbosity_verbose = 1
};
-/*
- * We need to keep track of how symlinks in the preimage are
- * manipulated by the patches. A patch to add a/b/c where a/b
- * is a symlink should not be allowed to affect the directory
- * the symlink points at, but if the same patch removes a/b,
- * it is perfectly fine, as the patch removes a/b to make room
- * to create a directory a/b so that a/b/c can be created.
- *
- * See also "struct string_list symlink_changes" in "struct
- * apply_state".
- */
-#define APPLY_SYMLINK_GOES_AWAY 01
-#define APPLY_SYMLINK_IN_RESULT 02
-
struct apply_state {
const char *prefix;
/* Various "current state" */
int linenr; /* current line number */
- struct string_list symlink_changes; /* we have to track symlinks */
+ /*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches. A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ */
+ struct strset removed_symlinks;
+ struct strset kept_symlinks;
/*
* For "diff-stat" like behaviour, we keep track of the biggest change
unsigned int size,
struct patch *patch);
+void release_patch(struct patch *patch);
+
/*
* Some aspects of the apply behavior are controlled by the following
* bits in the "options" parameter passed to apply_all_patches().
}
static struct archiver tar_archiver = {
- "tar",
- write_tar_archive,
- ARCHIVER_REMOTE
+ .name = "tar",
+ .write_archive = write_tar_archive,
+ .flags = ARCHIVER_REMOTE,
};
void init_tar_archiver(void)
#include "object-store.h"
#include "userdiff.h"
#include "xdiff-interface.h"
+#include "date.h"
static int zip_date;
static int zip_time;
}
static struct archiver zip_archiver = {
- "zip",
- write_zip_archive,
- ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE
+ .name = "zip",
+ .write_archive = write_zip_archive,
+ .flags = ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE,
};
void init_zip_archiver(void)
static char const * const archive_usage[] = {
N_("git archive [<options>] <tree-ish> [<path>...]"),
- N_("git archive --list"),
+ "git archive --list",
N_("git archive --remote <repo> [--exec <cmd>] [<options>] <tree-ish> [<path>...]"),
N_("git archive --remote <repo> [--exec <cmd>] --list"),
NULL
#undef sprintf
#undef vsprintf
-#ifdef HAVE_VARIADIC_MACROS
#define sprintf(...) BANNED(sprintf)
#define vsprintf(...) BANNED(vsprintf)
-#else
-#define sprintf(buf,fmt,arg) BANNED(sprintf)
-#define vsprintf(buf,fmt,arg) BANNED(vsprintf)
-#endif
#undef gmtime
#define gmtime(t) BANNED(gmtime)
return res;
}
-static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
+enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
+ int no_checkout)
{
char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
struct commit *commit;
struct commit_list;
struct repository;
+struct object_id;
/*
* Find bisection. If something is found, `reaches` will be the number of
int bisect_clean_state(void);
+enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
+ int no_checkout);
+
#endif
}
}
diff_flush(&diff_opts);
- clear_pathspec(&diff_opts.pathspec);
return porigin;
}
}
}
diff_flush(&diff_opts);
- clear_pathspec(&diff_opts.pathspec);
return porigin;
}
} while (unblamed);
target->suspects = reverse_blame(leftover, NULL);
diff_flush(&diff_opts);
- clear_pathspec(&diff_opts.pathspec);
}
/*
else {
commit->object.flags |= UNINTERESTING;
if (commit->object.parsed)
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(sb->revs, commit);
}
/* treat root commit as boundary */
if (!commit->parents && !sb->show_root)
#include "sequencer.h"
#include "commit.h"
#include "worktree.h"
+#include "submodule-config.h"
+#include "run-command.h"
struct tracking {
struct refspec_item spec;
}
/*
- * This is called when new_ref is branched off of orig_ref, and tries
- * to infer the settings for branch.<new_ref>.{remote,merge} from the
- * config.
+ * Used internally to set the branch.<new_ref>.{remote,merge} config
+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike
+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. "origin/main"
+ * will not be expanded to "refs/remotes/origin/main", so it is not safe
+ * for 'orig_ref' to be raw user input.
*/
static void setup_tracking(const char *new_ref, const char *orig_ref,
enum branch_track track, int quiet)
if (track != BRANCH_TRACK_INHERIT)
for_each_remote(find_tracked_branch, &tracking);
else if (inherit_tracking(&tracking, orig_ref))
- return;
+ goto cleanup;
if (!tracking.matches)
switch (track) {
case BRANCH_TRACK_INHERIT:
break;
default:
- return;
+ goto cleanup;
}
if (tracking.matches > 1)
tracking.remote, tracking.srcs) < 0)
exit(-1);
- string_list_clear(tracking.srcs, 0);
+cleanup:
+ string_list_clear(&tracking_srcs, 0);
}
int read_branch_desc(struct strbuf *buf, const char *branch_name)
"will track its remote counterpart, you may want to use\n"
"\"git push -u\" to set the upstream config as you push.");
-void create_branch(struct repository *r,
- const char *name, const char *start_name,
- int force, int clobber_head_ok, int reflog,
- int quiet, enum branch_track track)
+/**
+ * DWIMs a user-provided ref to determine the starting point for a
+ * branch and validates it, where:
+ *
+ * - r is the repository to validate the branch for
+ *
+ * - start_name is the ref that we would like to test. This is
+ * expanded with DWIM and assigned to out_real_ref.
+ *
+ * - track is the tracking mode of the new branch. If tracking is
+ * explicitly requested, start_name must be a branch (because
+ * otherwise start_name cannot be tracked)
+ *
+ * - out_oid is an out parameter containing the object_id of start_name
+ *
+ * - out_real_ref is an out parameter containing the full, 'real' form
+ * of start_name e.g. refs/heads/main instead of main
+ *
+ */
+static void dwim_branch_start(struct repository *r, const char *start_name,
+ enum branch_track track, char **out_real_ref,
+ struct object_id *out_oid)
{
struct commit *commit;
struct object_id oid;
char *real_ref;
- struct strbuf ref = STRBUF_INIT;
- int forcing = 0;
- int dont_change_ref = 0;
int explicit_tracking = 0;
if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
explicit_tracking = 1;
- if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)
- ? validate_branchname(name, &ref)
- : validate_new_branchname(name, &ref, force)) {
- if (!force)
- dont_change_ref = 1;
- else
- forcing = 1;
- }
-
real_ref = NULL;
if (get_oid_mb(start_name, &oid)) {
if (explicit_tracking) {
if ((commit = lookup_commit_reference(r, &oid)) == NULL)
die(_("not a valid branch point: '%s'"), start_name);
- oidcpy(&oid, &commit->object.oid);
+ if (out_real_ref) {
+ *out_real_ref = real_ref;
+ real_ref = NULL;
+ }
+ if (out_oid)
+ oidcpy(out_oid, &commit->object.oid);
+
+ FREE_AND_NULL(real_ref);
+}
+
+void create_branch(struct repository *r,
+ const char *name, const char *start_name,
+ int force, int clobber_head_ok, int reflog,
+ int quiet, enum branch_track track, int dry_run)
+{
+ struct object_id oid;
+ char *real_ref;
+ struct strbuf ref = STRBUF_INIT;
+ int forcing = 0;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ char *msg;
+
+ if (track == BRANCH_TRACK_OVERRIDE)
+ BUG("'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?");
+ if (clobber_head_ok && !force)
+ BUG("'clobber_head_ok' can only be used with 'force'");
+
+ if (clobber_head_ok ?
+ validate_branchname(name, &ref) :
+ validate_new_branchname(name, &ref, force)) {
+ forcing = 1;
+ }
+
+ dwim_branch_start(r, start_name, track, &real_ref, &oid);
+ if (dry_run)
+ goto cleanup;
if (reflog)
log_all_ref_updates = LOG_REFS_NORMAL;
- if (!dont_change_ref) {
- struct ref_transaction *transaction;
- struct strbuf err = STRBUF_INIT;
- char *msg;
-
- if (forcing)
- msg = xstrfmt("branch: Reset to %s", start_name);
- else
- msg = xstrfmt("branch: Created from %s", start_name);
-
- transaction = ref_transaction_begin(&err);
- if (!transaction ||
- ref_transaction_update(transaction, ref.buf,
- &oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
- ref_transaction_commit(transaction, &err))
- die("%s", err.buf);
- ref_transaction_free(transaction);
- strbuf_release(&err);
- free(msg);
- }
+ if (forcing)
+ msg = xstrfmt("branch: Reset to %s", start_name);
+ else
+ msg = xstrfmt("branch: Created from %s", start_name);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf,
+ &oid, forcing ? NULL : null_oid(),
+ 0, msg, &err) ||
+ ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ free(msg);
if (real_ref && track)
setup_tracking(ref.buf + 11, real_ref, track, quiet);
+cleanup:
strbuf_release(&ref);
free(real_ref);
}
+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
+ const char *orig_ref, enum branch_track track,
+ int quiet)
+{
+ char *real_orig_ref;
+ dwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);
+ setup_tracking(new_ref, real_orig_ref, track, quiet);
+}
+
+/**
+ * Creates a branch in a submodule by calling
+ * create_branches_recursively() in a child process. The child process
+ * is necessary because install_branch_config_multiple_remotes() (which
+ * is called by setup_tracking()) does not support writing configs to
+ * submodules.
+ */
+static int submodule_create_branch(struct repository *r,
+ const struct submodule *submodule,
+ const char *name, const char *start_oid,
+ const char *tracking_name, int force,
+ int reflog, int quiet,
+ enum branch_track track, int dry_run)
+{
+ int ret = 0;
+ struct child_process child = CHILD_PROCESS_INIT;
+ struct strbuf child_err = STRBUF_INIT;
+ struct strbuf out_buf = STRBUF_INIT;
+ char *out_prefix = xstrfmt("submodule '%s': ", submodule->name);
+ child.git_cmd = 1;
+ child.err = -1;
+ child.stdout_to_stderr = 1;
+
+ prepare_other_repo_env(&child.env_array, r->gitdir);
+ /*
+ * submodule_create_branch() is indirectly invoked by "git
+ * branch", but we cannot invoke "git branch" in the child
+ * process. "git branch" accepts a branch name and start point,
+ * where the start point is assumed to provide both the OID
+ * (start_oid) and the branch to use for tracking
+ * (tracking_name). But when recursing through submodules,
+ * start_oid and tracking name need to be specified separately
+ * (see create_branches_recursively()).
+ */
+ strvec_pushl(&child.args, "submodule--helper", "create-branch", NULL);
+ if (dry_run)
+ strvec_push(&child.args, "--dry-run");
+ if (force)
+ strvec_push(&child.args, "--force");
+ if (quiet)
+ strvec_push(&child.args, "--quiet");
+ if (reflog)
+ strvec_push(&child.args, "--create-reflog");
+ if (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)
+ strvec_push(&child.args, "--track");
+
+ strvec_pushl(&child.args, name, start_oid, tracking_name, NULL);
+
+ if ((ret = start_command(&child)))
+ return ret;
+ ret = finish_command(&child);
+ strbuf_read(&child_err, child.err, 0);
+ strbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);
+
+ if (ret)
+ fprintf(stderr, "%s", out_buf.buf);
+ else
+ printf("%s", out_buf.buf);
+
+ strbuf_release(&child_err);
+ strbuf_release(&out_buf);
+ return ret;
+}
+
+void create_branches_recursively(struct repository *r, const char *name,
+ const char *start_commitish,
+ const char *tracking_name, int force,
+ int reflog, int quiet, enum branch_track track,
+ int dry_run)
+{
+ int i = 0;
+ char *branch_point = NULL;
+ struct object_id super_oid;
+ struct submodule_entry_list submodule_entry_list;
+
+ /* Perform dwim on start_commitish to get super_oid and branch_point. */
+ dwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER,
+ &branch_point, &super_oid);
+
+ /*
+ * If we were not given an explicit name to track, then assume we are at
+ * the top level and, just like the non-recursive case, the tracking
+ * name is the branch point.
+ */
+ if (!tracking_name)
+ tracking_name = branch_point;
+
+ submodules_of_tree(r, &super_oid, &submodule_entry_list);
+ /*
+ * Before creating any branches, first check that the branch can
+ * be created in every submodule.
+ */
+ for (i = 0; i < submodule_entry_list.entry_nr; i++) {
+ if (submodule_entry_list.entries[i].repo == NULL) {
+ if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))
+ advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"),
+ start_commitish);
+ die(_("submodule '%s': unable to find submodule"),
+ submodule_entry_list.entries[i].submodule->name);
+ }
+
+ if (submodule_create_branch(
+ submodule_entry_list.entries[i].repo,
+ submodule_entry_list.entries[i].submodule, name,
+ oid_to_hex(&submodule_entry_list.entries[i]
+ .name_entry->oid),
+ tracking_name, force, reflog, quiet, track, 1))
+ die(_("submodule '%s': cannot create branch '%s'"),
+ submodule_entry_list.entries[i].submodule->name,
+ name);
+ }
+
+ create_branch(the_repository, name, start_commitish, force, 0, reflog, quiet,
+ BRANCH_TRACK_NEVER, dry_run);
+ if (dry_run)
+ return;
+ /*
+ * NEEDSWORK If tracking was set up in the superproject but not the
+ * submodule, users might expect "git branch --recurse-submodules" to
+ * fail or give a warning, but this is not yet implemented because it is
+ * tedious to determine whether or not tracking was set up in the
+ * superproject.
+ */
+ setup_tracking(name, tracking_name, track, quiet);
+
+ for (i = 0; i < submodule_entry_list.entry_nr; i++) {
+ if (submodule_create_branch(
+ submodule_entry_list.entries[i].repo,
+ submodule_entry_list.entries[i].submodule, name,
+ oid_to_hex(&submodule_entry_list.entries[i]
+ .name_entry->oid),
+ tracking_name, force, reflog, quiet, track, 0))
+ die(_("submodule '%s': cannot create branch '%s'"),
+ submodule_entry_list.entries[i].submodule->name,
+ name);
+ repo_clear(submodule_entry_list.entries[i].repo);
+ }
+}
+
void remove_merge_branch_state(struct repository *r)
{
unlink(git_path_merge_head(r));
/* Functions for acting on the information about branches. */
+/**
+ * Sets branch.<new_ref>.{remote,merge} config settings such that
+ * new_ref tracks orig_ref according to the specified tracking mode.
+ *
+ * - new_ref is the name of the branch that we are setting tracking
+ * for.
+ *
+ * - orig_ref is the name of the ref that is 'upstream' of new_ref.
+ * orig_ref will be expanded with DWIM so that the config settings
+ * are in the correct format e.g. "refs/remotes/origin/main" instead
+ * of "origin/main".
+ *
+ * - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes
+ * new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT
+ * causes new_ref to track whatever orig_ref tracks.
+ *
+ * - quiet suppresses tracking information.
+ */
+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
+ const char *orig_ref, enum branch_track track,
+ int quiet);
+
/*
* Creates a new branch, where:
*
*
* - force enables overwriting an existing (non-head) branch
*
- * - clobber_head_ok allows the currently checked out (hence existing)
- * branch to be overwritten; without 'force', it has no effect.
+ * - clobber_head_ok, when enabled with 'force', allows the currently
+ * checked out (head) branch to be overwritten
*
* - reflog creates a reflog for the branch
*
* - track causes the new branch to be configured to merge the remote branch
* that start_name is a tracking branch for (if any).
*
+ * - dry_run causes the branch to be validated but not created.
+ *
*/
void create_branch(struct repository *r,
const char *name, const char *start_name,
int force, int clobber_head_ok,
- int reflog, int quiet, enum branch_track track);
+ int reflog, int quiet, enum branch_track track,
+ int dry_run);
+/*
+ * Creates a new branch in a repository and its submodules (and its
+ * submodules, recursively). The parameters are mostly analogous to
+ * those of create_branch() except for start_name, which is represented
+ * by two different parameters:
+ *
+ * - start_commitish is the commit-ish, in repository r, that determines
+ * which commits the branches will point to. The superproject branch
+ * will point to the commit of start_commitish and the submodule
+ * branches will point to the gitlink commit oids in start_commitish's
+ * tree.
+ *
+ * - tracking_name is the name of the ref, in repository r, that will be
+ * used to set up tracking information. This value is propagated to
+ * all submodules, which will evaluate the ref using their own ref
+ * stores. If NULL, this defaults to start_commitish.
+ *
+ * When this function is called on the superproject, start_commitish
+ * can be any user-provided ref and tracking_name can be NULL (similar
+ * to create_branches()). But when recursing through submodules,
+ * start_commitish is the plain gitlink commit oid. Since the oid cannot
+ * be used for tracking information, tracking_name is propagated and
+ * used for tracking instead.
+ */
+void create_branches_recursively(struct repository *r, const char *name,
+ const char *start_commitish,
+ const char *tracking_name, int force,
+ int reflog, int quiet, enum branch_track track,
+ int dry_run);
/*
* Check if 'name' can be a valid name for a branch; die otherwise.
* Return 1 if the named branch already exists; return 0 otherwise.
int cmd_grep(int argc, const char **argv, const char *prefix);
int cmd_hash_object(int argc, const char **argv, const char *prefix);
int cmd_help(int argc, const char **argv, const char *prefix);
+int cmd_hook(int argc, const char **argv, const char *prefix);
int cmd_index_pack(int argc, const char **argv, const char *prefix);
int cmd_init_db(int argc, const char **argv, const char *prefix);
int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
static int pathspec_file_nul;
static int include_sparse;
static const char *pathspec_from_file;
-static int legacy_stash_p; /* support for the scripted `git stash` */
struct update_callback_data {
int flags;
N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
N_("warn when adding an embedded repository")),
- OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p,
- N_("backend for `git stash -p`")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
exit(interactive_add(argv + 1, prefix, patch_interactive));
}
- if (legacy_stash_p) {
- struct pathspec pathspec;
-
- parse_pathspec(&pathspec, 0,
- PATHSPEC_PREFER_FULL |
- PATHSPEC_SYMLINK_LEADING_PATH |
- PATHSPEC_PREFIX_ORIGIN,
- prefix, argv);
-
- return run_add_interactive(NULL, "--patch=stash", &pathspec);
- }
if (edit_interactive) {
if (pathspec_from_file)
#include "string-list.h"
#include "packfile.h"
#include "repository.h"
+#include "pretty.h"
/**
* Returns the length of the first line of msg.
else if (!strcmp(arg, "keep"))
*opt_value = KEEP_EMPTY_COMMIT;
else
- return error(_("Invalid value for --empty: %s"), arg);
+ return error(_("invalid value for '%s': '%s'"), "--empty", arg);
return 0;
}
int ret;
assert(state->msg);
- ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+ ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL);
if (!ret) {
FREE_AND_NULL(state->msg);
const char *reflog_msg, *author, *committer = NULL;
struct strbuf sb = STRBUF_INIT;
- if (run_hook_le(NULL, "pre-applypatch", NULL))
+ if (run_hooks("pre-applypatch"))
exit(1);
if (write_cache_as_tree(&tree, 0, NULL))
fclose(fp);
}
- run_hook_le(NULL, "post-applypatch", NULL);
+ run_hooks("post-applypatch");
strbuf_release(&sb);
}
* when you add new options
*/
else
- return error(_("Invalid value for --patch-format: %s"), arg);
+ return error(_("invalid value for '%s': '%s'"),
+ "--patch-format", arg);
return 0;
}
break;
}
if (new_value >= ARRAY_SIZE(valid_modes))
- return error(_("Invalid value for --show-current-patch: %s"), arg);
+ return error(_("invalid value for '%s': '%s'"),
+ "--show-current-patch", arg);
}
if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode)
static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-reset [<commit>]"),
- N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
+ "git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]",
N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
- N_("git bisect--helper --bisect-next"),
+ "git bisect--helper --bisect-next",
N_("git bisect--helper --bisect-state (bad|new) [<rev>]"),
N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
N_("git bisect--helper --bisect-replay <filename>"),
N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"),
- N_("git bisect--helper --bisect-visualize"),
+ "git bisect--helper --bisect-visualize",
N_("git bisect--helper --bisect-run <cmd>..."),
NULL
};
return res;
}
+static int get_first_good(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
+{
+ oidcpy(cb_data, oid);
+ return 1;
+}
+
+static int verify_good(const struct bisect_terms *terms,
+ const char **quoted_argv)
+{
+ int rc;
+ enum bisect_error res;
+ struct object_id good_rev;
+ struct object_id current_rev;
+ char *good_glob = xstrfmt("%s-*", terms->term_good);
+ int no_checkout = ref_exists("BISECT_HEAD");
+
+ for_each_glob_ref_in(get_first_good, good_glob, "refs/bisect/",
+ &good_rev);
+ free(good_glob);
+
+ if (read_ref(no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_rev))
+ return -1;
+
+ res = bisect_checkout(&good_rev, no_checkout);
+ if (res != BISECT_OK)
+ return -1;
+
+ printf(_("running %s\n"), quoted_argv[0]);
+ rc = run_command_v_opt(quoted_argv, RUN_USING_SHELL);
+
+ res = bisect_checkout(¤t_rev, no_checkout);
+ if (res != BISECT_OK)
+ return -1;
+
+ return rc;
+}
+
static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
{
int res = BISECT_OK;
struct strbuf command = STRBUF_INIT;
- struct strvec args = STRVEC_INIT;
struct strvec run_args = STRVEC_INIT;
const char *new_state;
int temporary_stdout_fd, saved_stdout;
+ int is_first_run = 1;
if (bisect_next_check(terms, NULL))
return BISECT_FAILED;
strvec_push(&run_args, command.buf);
while (1) {
- strvec_clear(&args);
-
printf(_("running %s\n"), command.buf);
res = run_command_v_opt(run_args.v, RUN_USING_SHELL);
+ /*
+ * Exit code 126 and 127 can either come from the shell
+ * if it was unable to execute or even find the script,
+ * or from the script itself. Check with a known-good
+ * revision to avoid trashing the bisect run due to a
+ * missing or non-executable script.
+ */
+ if (is_first_run && (res == 126 || res == 127)) {
+ int rc = verify_good(terms, run_args.v);
+ is_first_run = 0;
+ if (rc < 0) {
+ error(_("unable to verify '%s' on good"
+ " revision"), command.buf);
+ res = BISECT_FAILED;
+ break;
+ }
+ if (rc == res) {
+ error(_("bogus exit code %d for good revision"),
+ rc);
+ res = BISECT_FAILED;
+ break;
+ }
+ }
+
if (res < 0 || 128 <= res) {
error(_("bisect run failed: exit code %d from"
" '%s' is < 0 or >= 128"), res, command.buf);
- strbuf_release(&command);
- return res;
+ break;
}
if (res == 125)
temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (temporary_stdout_fd < 0)
- return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
+ if (temporary_stdout_fd < 0) {
+ res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
+ break;
+ }
fflush(stdout);
saved_stdout = dup(1);
res = BISECT_OK;
} else if (res) {
error(_("bisect run failed: 'git bisect--helper --bisect-state"
- " %s' exited with error code %d"), args.v[0], res);
+ " %s' exited with error code %d"), new_state, res);
} else {
continue;
}
-
- strbuf_release(&command);
- strvec_clear(&args);
- strvec_clear(&run_args);
- return res;
+ break;
}
+
+ strbuf_release(&command);
+ strvec_clear(&run_args);
+ return res;
}
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
OPT_CMDMODE(0, "bisect-visualize", &cmdmode,
N_("visualize the bisection"), BISECT_VISUALIZE),
OPT_CMDMODE(0, "bisect-run", &cmdmode,
- N_("use <cmd>... to automatically bisect."), BISECT_RUN),
+ N_("use <cmd>... to automatically bisect"), BISECT_RUN),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
}
if (!strcmp(var, "color.blame.repeatedlines")) {
if (color_parse_mem(value, strlen(value), repeated_meta_color))
- warning(_("invalid color '%s' in color.blame.repeatedLines"),
- value);
+ warning(_("invalid value for '%s': '%s'"),
+ "color.blame.repeatedLines", value);
return 0;
}
if (!strcmp(var, "color.blame.highlightrecent")) {
coloring_mode &= ~(OUTPUT_COLOR_LINE |
OUTPUT_SHOW_AGE_WITH_COLOR);
} else {
- warning(_("invalid value for blame.coloring"));
+ warning(_("invalid value for '%s': '%s'"),
+ "blame.coloring", value);
return 0;
}
}
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
parse_done:
+ revision_opts_finish(&revs);
no_whole_file_rename = !revs.diffopt.flags.follow_renames;
xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
revs.diffopt.flags.follow_renames = 0;
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
- N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-l] [<pattern>...]"),
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>"),
static const char *head;
static struct object_id head_oid;
+static int recurse_submodules = 0;
+static int submodule_propagate_branches = 0;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
+ if (!strcmp(var, "submodule.recurse")) {
+ recurse_submodules = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcasecmp(var, "submodule.propagateBranches")) {
+ submodule_propagate_branches = git_config_bool(var, value);
+ return 0;
+ }
+
return git_color_default_config(var, value, cb);
}
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
- int show_current = 0;
- int reflog = 0, edit_description = 0;
- int quiet = 0, unset_upstream = 0;
+ /* possible actions */
+ int delete = 0, rename = 0, copy = 0, list = 0,
+ unset_upstream = 0, show_current = 0, edit_description = 0;
const char *new_upstream = NULL;
+ int noncreate_actions = 0;
+ /* possible options */
+ int reflog = 0, quiet = 0, icase = 0, force = 0,
+ recurse_submodules_explicit = 0;
enum branch_track track;
struct ref_filter filter;
- int icase = 0;
static struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), parse_opt_object_name),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;
- if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
- list + edit_description + unset_upstream > 1)
+ noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
+ !!show_current + !!list + !!edit_description +
+ !!unset_upstream;
+ if (noncreate_actions > 1)
usage_with_options(builtin_branch_usage, options);
+ if (recurse_submodules_explicit) {
+ if (!submodule_propagate_branches)
+ die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
+ if (noncreate_actions)
+ die(_("--recurse-submodules can only be used to create branches"));
+ }
+
+ recurse_submodules =
+ (recurse_submodules || recurse_submodules_explicit) &&
+ submodule_propagate_branches;
+
if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
filter.ignore_case = icase;
if (!ref_exists(branch->refname))
die(_("branch '%s' does not exist"), branch->name);
- /*
- * create_branch takes care of setting up the tracking
- * info and making sure new_upstream is correct
- */
- create_branch(the_repository, branch->name, new_upstream,
- 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
+ dwim_and_setup_tracking(the_repository, branch->name,
+ new_upstream, BRANCH_TRACK_OVERRIDE,
+ quiet);
} else if (unset_upstream) {
struct branch *branch = branch_get(argv[0]);
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf);
- } else if (argc > 0 && argc <= 2) {
+ } else if (!noncreate_actions && argc > 0 && argc <= 2) {
+ const char *branch_name = argv[0];
+ const char *start_name = argc == 2 ? argv[1] : head;
+
if (filter.kind != FILTER_REFS_BRANCHES)
die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n"
"Did you mean to use: -a|-r --list <pattern>?"));
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
- create_branch(the_repository,
- argv[0], (argc == 2) ? argv[1] : head,
- force, 0, reflog, quiet, track);
-
+ if (recurse_submodules) {
+ create_branches_recursively(the_repository, branch_name,
+ start_name, NULL, force,
+ reflog, quiet, track, 0);
+ return 0;
+ }
+ create_branch(the_repository, branch_name, start_name, force, 0,
+ reflog, quiet, track, 0);
} else
usage_with_options(builtin_branch_usage, options);
if (!startup_info->have_repository)
die(_("Need a repository to create a bundle."));
ret = !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version);
+ strvec_clear(&pack_opts);
free(bundle_file);
return ret;
}
#include "object-store.h"
#include "promisor-remote.h"
+enum batch_mode {
+ BATCH_MODE_CONTENTS,
+ BATCH_MODE_INFO,
+ BATCH_MODE_QUEUE_AND_DISPATCH,
+};
+
struct batch_options {
int enabled;
int follow_symlinks;
- int print_contents;
+ enum batch_mode batch_mode;
int buffer_output;
int all_objects;
int unordered;
- int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */
+ int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */
const char *format;
};
struct object_info oi = OBJECT_INFO_INIT;
struct strbuf sb = STRBUF_INIT;
unsigned flags = OBJECT_INFO_LOOKUP_REPLACE;
+ unsigned get_oid_flags = GET_OID_RECORD_PATH | GET_OID_ONLY_TO_DIE;
const char *path = force_path;
+ const int opt_cw = (opt == 'c' || opt == 'w');
+ if (!path && opt_cw)
+ get_oid_flags |= GET_OID_REQUIRE_PATH;
if (unknown_type)
flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
- if (get_oid_with_context(the_repository, obj_name,
- GET_OID_RECORD_PATH,
- &oid, &obj_context))
+ if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid,
+ &obj_context))
die("Not a valid object name %s", obj_name);
if (!path)
return !has_object_file(&oid);
case 'w':
- if (!path)
- die("git cat-file --filters %s: <object> must be "
- "<sha1:path>", obj_name);
if (filter_object(path, obj_context.mode,
&oid, &buf, &size))
break;
case 'c':
- if (!path)
- die("git cat-file --textconv %s: <object> must be <sha1:path>",
- obj_name);
-
if (textconv_object(the_repository, path, obj_context.mode,
&oid, 1, &buf, &size))
break;
if (data->type == OBJ_BLOB) {
if (opt->buffer_output)
fflush(stdout);
- if (opt->cmdmode) {
+ if (opt->transform_mode) {
char *contents;
unsigned long size;
if (!data->rest)
die("missing path for '%s'", oid_to_hex(oid));
- if (opt->cmdmode == 'w') {
+ if (opt->transform_mode == 'w') {
if (filter_object(data->rest, 0100644, oid,
&contents, &size))
die("could not convert '%s' %s",
oid_to_hex(oid), data->rest);
- } else if (opt->cmdmode == 'c') {
+ } else if (opt->transform_mode == 'c') {
enum object_type type;
if (!textconv_object(the_repository,
data->rest, 0100644, oid,
die("could not convert '%s' %s",
oid_to_hex(oid), data->rest);
} else
- BUG("invalid cmdmode: %c", opt->cmdmode);
+ BUG("invalid transform_mode: %c", opt->transform_mode);
batch_write(opt, contents, size);
free(contents);
} else {
strbuf_addch(scratch, '\n');
batch_write(opt, scratch->buf, scratch->len);
- if (opt->print_contents) {
+ if (opt->batch_mode == BATCH_MODE_CONTENTS) {
print_object_or_die(opt, data);
batch_write(opt, "\n", 1);
}
data);
}
+typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,
+ struct strbuf *, struct expand_data *);
+
+struct queued_cmd {
+ parse_cmd_fn_t fn;
+ char *line;
+};
+
+static void parse_cmd_contents(struct batch_options *opt,
+ const char *line,
+ struct strbuf *output,
+ struct expand_data *data)
+{
+ opt->batch_mode = BATCH_MODE_CONTENTS;
+ batch_one_object(line, output, opt, data);
+}
+
+static void parse_cmd_info(struct batch_options *opt,
+ const char *line,
+ struct strbuf *output,
+ struct expand_data *data)
+{
+ opt->batch_mode = BATCH_MODE_INFO;
+ batch_one_object(line, output, opt, data);
+}
+
+static void dispatch_calls(struct batch_options *opt,
+ struct strbuf *output,
+ struct expand_data *data,
+ struct queued_cmd *cmd,
+ int nr)
+{
+ int i;
+
+ if (!opt->buffer_output)
+ die(_("flush is only for --buffer mode"));
+
+ for (i = 0; i < nr; i++)
+ cmd[i].fn(opt, cmd[i].line, output, data);
+
+ fflush(stdout);
+}
+
+static void free_cmds(struct queued_cmd *cmd, size_t *nr)
+{
+ size_t i;
+
+ for (i = 0; i < *nr; i++)
+ FREE_AND_NULL(cmd[i].line);
+
+ *nr = 0;
+}
+
+
+static const struct parse_cmd {
+ const char *name;
+ parse_cmd_fn_t fn;
+ unsigned takes_args;
+} commands[] = {
+ { "contents", parse_cmd_contents, 1},
+ { "info", parse_cmd_info, 1},
+ { "flush", NULL, 0},
+};
+
+static void batch_objects_command(struct batch_options *opt,
+ struct strbuf *output,
+ struct expand_data *data)
+{
+ struct strbuf input = STRBUF_INIT;
+ struct queued_cmd *queued_cmd = NULL;
+ size_t alloc = 0, nr = 0;
+
+ while (!strbuf_getline(&input, stdin)) {
+ int i;
+ const struct parse_cmd *cmd = NULL;
+ const char *p = NULL, *cmd_end;
+ struct queued_cmd call = {0};
+
+ if (!input.len)
+ die(_("empty command in input"));
+ if (isspace(*input.buf))
+ die(_("whitespace before command: '%s'"), input.buf);
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ if (!skip_prefix(input.buf, commands[i].name, &cmd_end))
+ continue;
+
+ cmd = &commands[i];
+ if (cmd->takes_args) {
+ if (*cmd_end != ' ')
+ die(_("%s requires arguments"),
+ commands[i].name);
+
+ p = cmd_end + 1;
+ } else if (*cmd_end) {
+ die(_("%s takes no arguments"),
+ commands[i].name);
+ }
+
+ break;
+ }
+
+ if (!cmd)
+ die(_("unknown command: '%s'"), input.buf);
+
+ if (!strcmp(cmd->name, "flush")) {
+ dispatch_calls(opt, output, data, queued_cmd, nr);
+ free_cmds(queued_cmd, &nr);
+ } else if (!opt->buffer_output) {
+ cmd->fn(opt, p, output, data);
+ } else {
+ ALLOC_GROW(queued_cmd, nr + 1, alloc);
+ call.fn = cmd->fn;
+ call.line = xstrdup_or_null(p);
+ queued_cmd[nr++] = call;
+ }
+ }
+
+ if (opt->buffer_output &&
+ nr &&
+ !git_env_bool("GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT", 0)) {
+ dispatch_calls(opt, output, data, queued_cmd, nr);
+ free_cmds(queued_cmd, &nr);
+ }
+
+ free(queued_cmd);
+ strbuf_release(&input);
+}
+
static int batch_objects(struct batch_options *opt)
{
struct strbuf input = STRBUF_INIT;
strbuf_expand(&output, opt->format, expand_format, &data);
data.mark_query = 0;
strbuf_release(&output);
- if (opt->cmdmode)
+ if (opt->transform_mode)
data.split_on_whitespace = 1;
/*
* If we are printing out the object, then always fill in the type,
* since we will want to decide whether or not to stream.
*/
- if (opt->print_contents)
+ if (opt->batch_mode == BATCH_MODE_CONTENTS)
data.info.typep = &data.type;
if (opt->all_objects) {
save_warning = warn_on_object_refname_ambiguity;
warn_on_object_refname_ambiguity = 0;
+ if (opt->batch_mode == BATCH_MODE_QUEUE_AND_DISPATCH) {
+ batch_objects_command(opt, &output, &data);
+ goto cleanup;
+ }
+
while (strbuf_getline(&input, stdin) != EOF) {
if (data.split_on_whitespace) {
/*
batch_one_object(input.buf, &output, opt, &data);
}
+ cleanup:
strbuf_release(&input);
strbuf_release(&output);
warn_on_object_refname_ambiguity = save_warning;
return retval;
}
-static const char * const cat_file_usage[] = {
- N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"),
- N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"),
- NULL
-};
-
static int git_cat_file_config(const char *var, const char *value, void *cb)
{
if (userdiff_config(var, value) < 0)
}
bo->enabled = 1;
- bo->print_contents = !strcmp(opt->long_name, "batch");
+
+ if (!strcmp(opt->long_name, "batch"))
+ bo->batch_mode = BATCH_MODE_CONTENTS;
+ else if (!strcmp(opt->long_name, "batch-check"))
+ bo->batch_mode = BATCH_MODE_INFO;
+ else if (!strcmp(opt->long_name, "batch-command"))
+ bo->batch_mode = BATCH_MODE_QUEUE_AND_DISPATCH;
+ else
+ BUG("%s given to batch-option-callback", opt->long_name);
+
bo->format = arg;
return 0;
int cmd_cat_file(int argc, const char **argv, const char *prefix)
{
int opt = 0;
+ int opt_cw = 0;
+ int opt_epts = 0;
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
int unknown_type = 0;
+ const char * const usage[] = {
+ N_("git cat-file <type> <object>"),
+ N_("git cat-file (-e | -p) <object>"),
+ N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"),
+ N_("git cat-file (--batch | --batch-check | --batch-command) [--batch-all-objects]\n"
+ " [--buffer] [--follow-symlinks] [--unordered]\n"
+ " [--textconv | --filters]"),
+ N_("git cat-file (--textconv | --filters)\n"
+ " [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"),
+ NULL
+ };
const struct option options[] = {
- OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
- OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
- OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
+ /* Simple queries */
+ OPT_GROUP(N_("Check object existence or emit object contents")),
OPT_CMDMODE('e', NULL, &opt,
- N_("exit with zero when there's no error"), 'e'),
- OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
- OPT_CMDMODE(0, "textconv", &opt,
- N_("for blob objects, run textconv on object's content"), 'c'),
- OPT_CMDMODE(0, "filters", &opt,
- N_("for blob objects, run filters on object's content"), 'w'),
- OPT_STRING(0, "path", &force_path, N_("blob"),
- N_("use a specific path for --textconv/--filters")),
+ N_("check if <object> exists"), 'e'),
+ OPT_CMDMODE('p', NULL, &opt, N_("pretty-print <object> content"), 'p'),
+
+ OPT_GROUP(N_("Emit [broken] object attributes")),
+ OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'),
+ OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
OPT_BOOL(0, "allow-unknown-type", &unknown_type,
N_("allow -s and -t to work with broken/corrupt objects")),
- OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
- OPT_CALLBACK_F(0, "batch", &batch, "format",
- N_("show info and content of objects fed from the standard input"),
+ /* Batch mode */
+ OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")),
+ OPT_CALLBACK_F(0, "batch", &batch, N_("format"),
+ N_("show full <object> or <rev> contents"),
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+ batch_option_callback),
+ OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"),
+ N_("like --batch, but don't emit <contents>"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
batch_option_callback),
- OPT_CALLBACK_F(0, "batch-check", &batch, "format",
- N_("show info about objects fed from the standard input"),
+ OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"),
+ N_("read commands from stdin"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
batch_option_callback),
+ OPT_CMDMODE(0, "batch-all-objects", &opt,
+ N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'),
+ /* Batch-specific options */
+ OPT_GROUP(N_("Change or optimize batch output")),
+ OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
- N_("follow in-tree symlinks (used with --batch or --batch-check)")),
- OPT_BOOL(0, "batch-all-objects", &batch.all_objects,
- N_("show all objects with --batch or --batch-check")),
+ N_("follow in-tree symlinks")),
OPT_BOOL(0, "unordered", &batch.unordered,
- N_("do not order --batch-all-objects output")),
+ N_("do not order objects before emitting them")),
+ /* Textconv options, stand-ole*/
+ OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")),
+ OPT_CMDMODE(0, "textconv", &opt,
+ N_("run textconv on object's content"), 'c'),
+ OPT_CMDMODE(0, "filters", &opt,
+ N_("run filters on object's content"), 'w'),
+ OPT_STRING(0, "path", &force_path, N_("blob|tree"),
+ N_("use a <path> for (--textconv | --filters); Not with 'batch'")),
OPT_END()
};
git_config(git_cat_file_config, NULL);
batch.buffer_output = -1;
- argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
-
- if (opt) {
- if (batch.enabled && (opt == 'c' || opt == 'w'))
- batch.cmdmode = opt;
- else if (argc == 1)
- obj_name = argv[0];
- else
- usage_with_options(cat_file_usage, options);
- }
- if (!opt && !batch.enabled) {
- if (argc == 2) {
- exp_type = argv[0];
- obj_name = argv[1];
- } else
- usage_with_options(cat_file_usage, options);
- }
- if (batch.enabled) {
- if (batch.cmdmode != opt || argc)
- usage_with_options(cat_file_usage, options);
- if (batch.cmdmode && batch.all_objects)
- die("--batch-all-objects cannot be combined with "
- "--textconv nor with --filters");
- }
- if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
- usage_with_options(cat_file_usage, options);
- }
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+ opt_cw = (opt == 'c' || opt == 'w');
+ opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
- if (force_path && opt != 'c' && opt != 'w') {
- error("--path=<path> needs --textconv or --filters");
- usage_with_options(cat_file_usage, options);
- }
+ /* --batch-all-objects? */
+ if (opt == 'b')
+ batch.all_objects = 1;
- if (force_path && batch.enabled) {
- error("options '--path=<path>' and '--batch' cannot be used together");
- usage_with_options(cat_file_usage, options);
- }
+ /* Option compatibility */
+ if (force_path && !opt_cw)
+ usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"),
+ usage, options,
+ "--path", _("path|tree-ish"), "--filters",
+ "--textconv");
+ /* Option compatibility with batch mode */
+ if (batch.enabled)
+ ;
+ else if (batch.follow_symlinks)
+ usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+ "--follow-symlinks");
+ else if (batch.buffer_output >= 0)
+ usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+ "--buffer");
+ else if (batch.all_objects)
+ usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+ "--batch-all-objects");
+
+ /* Batch defaults */
if (batch.buffer_output < 0)
batch.buffer_output = batch.all_objects;
- if (batch.enabled)
+ /* Return early if we're in batch mode? */
+ if (batch.enabled) {
+ if (opt_cw)
+ batch.transform_mode = opt;
+ else if (opt && opt != 'b')
+ usage_msg_optf(_("'-%c' is incompatible with batch mode"),
+ usage, options, opt);
+ else if (argc)
+ usage_msg_opt(_("batch modes take no arguments"), usage,
+ options);
+
return batch_objects(&batch);
+ }
+
+ if (opt) {
+ if (!argc && opt == 'c')
+ usage_msg_optf(_("<rev> required with '%s'"),
+ usage, options, "--textconv");
+ else if (!argc && opt == 'w')
+ usage_msg_optf(_("<rev> required with '%s'"),
+ usage, options, "--filters");
+ else if (!argc && opt_epts)
+ usage_msg_optf(_("<object> required with '-%c'"),
+ usage, options, opt);
+ else if (argc == 1)
+ obj_name = argv[0];
+ else
+ usage_msg_opt(_("too many arguments"), usage, options);
+ } else if (!argc) {
+ usage_with_options(usage, options);
+ } else if (argc != 2) {
+ usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"),
+ usage, options, argc);
+ } else if (argc) {
+ exp_type = argv[0];
+ obj_name = argv[1];
+ }
if (unknown_type && opt != 't' && opt != 's')
die("git cat-file --allow-unknown-type: use with -s or -t");
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
+#include "dir.h"
#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#define CHECKOUT_ALL 4
static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */
+static int ignore_skip_worktree; /* default to 0 */
static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
int has_same_name = 0;
+ int is_file = 0;
+ int is_skipped = 1;
int did_checkout = 0;
int errs = 0;
break;
has_same_name = 1;
pos++;
+ if (S_ISSPARSEDIR(ce->ce_mode))
+ break;
+ is_file = 1;
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ break;
+ is_skipped = 0;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name)
fprintf(stderr, "is not in the cache");
+ else if (!is_file)
+ fprintf(stderr, "is a sparse directory");
+ else if (is_skipped)
+ fprintf(stderr, "has skip-worktree enabled; "
+ "use '--ignore-skip-worktree-bits' to checkout");
else if (checkout_stage)
fprintf(stderr, "does not exist at stage %d",
checkout_stage);
int i, errs = 0;
struct cache_entry *last_ce = NULL;
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];
+
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ if (!ce_skip_worktree(ce))
+ BUG("sparse directory '%s' does not have skip-worktree set", ce->name);
+
+ /*
+ * If the current entry is a sparse directory and skip-worktree
+ * entries are being checked out, expand the index and continue
+ * the loop on the current index position (now pointing to the
+ * first entry inside the expanded sparse directory).
+ */
+ if (ignore_skip_worktree) {
+ ensure_full_index(&the_index);
+ ce = active_cache[i];
+ }
+ }
+
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ continue;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
+ OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree,
+ N_("do not skip files with skip-worktree set")),
OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")),
git_config(git_default_config, NULL);
prefix_length = prefix ? strlen(prefix) : 0;
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (read_cache() < 0) {
die("invalid cache");
}
#include "config.h"
#include "diff.h"
#include "dir.h"
+#include "hook.h"
#include "ll-merge.h"
#include "lockfile.h"
#include "merge-recursive.h"
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
int changed)
{
- return run_hook_le(NULL, "post-checkout",
+ return run_hooks_l("post-checkout",
oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
changed ? "1" : "0", NULL);
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
mmfile_t ancestor, ours, theirs;
+ enum ll_merge_result merge_status;
int status;
struct object_id oid;
mmbuffer_t result_buf;
memset(&ll_opts, 0, sizeof(ll_opts));
git_config_get_bool("merge.renormalize", &renormalize);
ll_opts.renormalize = renormalize;
- status = ll_merge(&result_buf, path, &ancestor, "base",
- &ours, "ours", &theirs, "theirs",
- state->istate, &ll_opts);
+ merge_status = ll_merge(&result_buf, path, &ancestor, "base",
+ &ours, "ours", &theirs, "theirs",
+ state->istate, &ll_opts);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
- if (status < 0 || !result_buf.ptr) {
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ path, "ours", "theirs");
+ if (merge_status < 0 || !result_buf.ptr) {
free(result_buf.ptr);
return error(_("path '%s': cannot merge"), path);
}
struct tree_desc trees[2];
struct tree *tree;
struct unpack_trees_options topts;
+ const struct object_id *old_commit_oid;
memset(&topts, 0, sizeof(topts));
topts.head_idx = -1;
&new_branch_info->commit->object.oid :
&new_branch_info->oid, NULL);
topts.preserve_ignored = !opts->overwrite_ignore;
- tree = parse_tree_indirect(old_branch_info->commit ?
- &old_branch_info->commit->object.oid :
- the_hash_algo->empty_tree);
+
+ old_commit_oid = old_branch_info->commit ?
+ &old_branch_info->commit->object.oid :
+ the_hash_algo->empty_tree;
+ tree = parse_tree_indirect(old_commit_oid);
+ if (!tree)
+ die(_("unable to parse commit %s"),
+ oid_to_hex(old_commit_oid));
+
init_tree_desc(&trees[0], tree->buffer, tree->size);
parse_tree(new_tree);
tree = new_tree;
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
opts->quiet,
- opts->track);
+ opts->track,
+ 0);
free(new_branch_info->name);
free(new_branch_info->refname);
new_branch_info->name = xstrdup(opts->new_branch);
{
struct object_id oid;
char *to_free;
+ int code;
if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
const char *ref = to_free;
if (skip_prefix(ref, "refs/tags/", &ref))
- die(_("a branch is expected, got tag '%s'"), ref);
- if (skip_prefix(ref, "refs/remotes/", &ref))
- die(_("a branch is expected, got remote branch '%s'"), ref);
- die(_("a branch is expected, got '%s'"), ref);
+ code = die_message(_("a branch is expected, got tag '%s'"), ref);
+ else if (skip_prefix(ref, "refs/remotes/", &ref))
+ code = die_message(_("a branch is expected, got remote branch '%s'"), ref);
+ else
+ code = die_message(_("a branch is expected, got '%s'"), ref);
}
- if (branch_info->commit)
- die(_("a branch is expected, got commit '%s'"), branch_info->name);
- /*
- * This case should never happen because we already die() on
- * non-commit, but just in case.
- */
- die(_("a branch is expected, got '%s'"), branch_info->name);
+ else if (branch_info->commit)
+ code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
+ else
+ /*
+ * This case should never happen because we already die() on
+ * non-commit, but just in case.
+ */
+ code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
+
+ if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
+ advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
+
+ exit(code);
}
static void die_if_some_operation_in_progress(void)
opts->show_progress = -1;
git_config(git_checkout_config, opts);
-
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
opts->track = BRANCH_TRACK_UNSPECIFIED;
dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
}
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (read_cache() < 0)
die(_("index file corrupt"));
#include "connected.h"
#include "packfile.h"
#include "list-objects-filter-options.h"
+#include "hook.h"
/*
* Overall FIXMEs:
static int max_jobs = -1;
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
static struct list_objects_filter_options filter_options;
+static int option_filter_submodules = -1; /* unspecified */
+static int config_filter_submodules = -1; /* unspecified */
static struct string_list server_options = STRING_LIST_INIT_NODUP;
static int option_remote_submodules;
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "also-filter-submodules", &option_filter_submodules,
+ N_("apply partial clone filters to submodules")),
OPT_BOOL(0, "remote-submodules", &option_remote_submodules,
N_("any cloned submodules will use their remote-tracking branch")),
OPT_BOOL(0, "sparse", &option_sparse_checkout,
return result;
}
-static int checkout(int submodule_progress)
+static int checkout(int submodule_progress, int filter_submodules)
{
struct object_id oid;
char *head;
init_checkout_metadata(&opts.meta, head, &oid, NULL);
tree = parse_tree_indirect(&oid);
+ if (!tree)
+ die(_("unable to parse commit %s"), oid_to_hex(&oid));
parse_tree(tree);
init_tree_desc(&t, tree->buffer, tree->size);
if (unpack_trees(1, &t, &opts) < 0)
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
+ err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()),
oid_to_hex(&oid), "1", NULL);
if (!err && (option_recurse_submodules.nr > 0)) {
strvec_push(&args, "--no-fetch");
}
+ if (filter_submodules && filter_options.choice)
+ strvec_pushf(&args, "--filter=%s",
+ expand_list_objects_filter_spec(&filter_options));
+
if (option_single_branch >= 0)
strvec_push(&args, option_single_branch ?
"--single-branch" :
}
if (!strcmp(k, "clone.rejectshallow"))
config_reject_shallow = git_config_bool(k, v);
+ if (!strcmp(k, "clone.filtersubmodules"))
+ config_filter_submodules = git_config_bool(k, v);
return git_default_config(k, v, cb);
}
const struct ref *refs, *remote_head;
struct ref *remote_head_points_at = NULL;
const struct ref *our_head_points_at;
- struct ref *mapped_refs;
+ struct ref *mapped_refs = NULL;
const struct ref *ref;
struct strbuf key = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct remote *remote;
int err = 0, complete_refs_before_fetch = 1;
int submodule_progress;
+ int filter_submodules = 0;
struct transport_ls_refs_options transport_ls_refs_options =
TRANSPORT_LS_REFS_OPTIONS_INIT;
if (option_reject_shallow != -1)
reject_shallow = option_reject_shallow;
+ /*
+ * If option_filter_submodules is specified from CLI option,
+ * ignore config_filter_submodules from git_clone_config.
+ */
+ if (config_filter_submodules != -1)
+ filter_submodules = config_filter_submodules;
+ if (option_filter_submodules != -1)
+ filter_submodules = option_filter_submodules;
+
+ /*
+ * Exit if the user seems to be doing something silly with submodule
+ * filter flags (but not with filter configs, as those should be
+ * set-and-forget).
+ */
+ if (option_filter_submodules > 0 && !filter_options.choice)
+ die(_("the option '%s' requires '%s'"),
+ "--also-filter-submodules", "--filter");
+ if (option_filter_submodules > 0 && !option_recurse_submodules.nr)
+ die(_("the option '%s' requires '%s'"),
+ "--also-filter-submodules", "--recurse-submodules");
+
/*
* apply the remote name provided by --origin only after this second
* call to git_config, to ensure it overrides all config-based values.
refs = transport_get_remote_refs(transport, &transport_ls_refs_options);
- if (refs) {
+ if (refs)
+ mapped_refs = wanted_peer_refs(refs, &remote->fetch);
+
+ if (mapped_refs) {
int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
/*
*/
initialize_repository_version(hash_algo, 1);
repo_set_hash_algo(the_repository, hash_algo);
-
- mapped_refs = wanted_peer_refs(refs, &remote->fetch);
/*
* transport_get_remote_refs() may return refs with null sha-1
* in mapped_refs (see struct transport->get_refs_list
}
else {
const char *branch;
- char *ref;
+ const char *ref;
+ char *ref_free = NULL;
if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
option_branch, remote_name);
warning(_("You appear to have cloned an empty repository."));
- mapped_refs = NULL;
our_head_points_at = NULL;
remote_head_points_at = NULL;
remote_head = NULL;
skip_prefix(transport_ls_refs_options.unborn_head_target,
"refs/heads/", &branch)) {
ref = transport_ls_refs_options.unborn_head_target;
- transport_ls_refs_options.unborn_head_target = NULL;
create_symref("HEAD", ref, reflog_msg.buf);
} else {
branch = git_default_branch_name(0);
- ref = xstrfmt("refs/heads/%s", branch);
+ ref_free = xstrfmt("refs/heads/%s", branch);
+ ref = ref_free;
}
if (!option_bare)
install_branch_config(0, branch, remote_name, ref);
-
- free(ref);
+ free(ref_free);
}
write_refspec_config(src_ref_prefix, our_head_points_at,
if (is_local)
clone_local(path, git_dir);
- else if (refs && complete_refs_before_fetch) {
+ else if (mapped_refs && complete_refs_before_fetch) {
if (transport_fetch_refs(transport, mapped_refs))
die(_("remote transport reported error"));
}
}
junk_mode = JUNK_LEAVE_REPO;
- err = checkout(submodule_progress);
+ err = checkout(submodule_progress, filter_submodules);
free(remote_name);
strbuf_release(&reflog_msg);
UNLEAK(repo);
junk_mode = JUNK_LEAVE_ALL;
- strvec_clear(&transport_ls_refs_options.ref_prefixes);
- free(transport_ls_refs_options.unborn_head_target);
+ transport_ls_refs_options_release(&transport_ls_refs_options);
return err;
}
static int graph_write(int argc, const char **argv)
{
- struct string_list pack_indexes = STRING_LIST_INIT_NODUP;
+ struct string_list pack_indexes = STRING_LIST_INIT_DUP;
struct strbuf buf = STRBUF_INIT;
struct oidset commits = OIDSET_INIT;
struct object_directory *odb = NULL;
if (opts.stdin_packs) {
while (strbuf_getline(&buf, stdin) != EOF)
- string_list_append(&pack_indexes,
- strbuf_detach(&buf, NULL));
+ string_list_append_nodup(&pack_indexes,
+ strbuf_detach(&buf, NULL));
} else if (opts.stdin_commits) {
oidset_init(&commits, 0);
if (opts.progress)
#include "help.h"
#include "commit-reach.h"
#include "commit-graph.h"
+#include "pretty.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [<options>] [--] <pathspec>..."),
struct commit *current_head,
struct wt_status *s)
{
- int f = 0;
-
argc = parse_options(argc, argv, prefix, options, usage, 0);
finalize_deferred_config(s);
force_author = find_author_by_nickname(force_author);
if (force_author && renew_authorship)
- die(_("Using both --reset-author and --author does not make sense"));
+ die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
if (logfile || have_option_m || use_message)
use_editor = 0;
die(_("You are in the middle of a rebase -- cannot amend."));
}
if (fixup_message && squash_message)
- die(_("Options --squash and --fixup cannot be used together"));
- if (use_message)
- f++;
- if (edit_message)
- f++;
- if (fixup_message)
- f++;
- if (logfile)
- f++;
- if (f > 1)
- die(_("Only one of -c/-C/-F/--fixup can be used."));
- if (have_option_m && (edit_message || use_message || logfile))
- die((_("Option -m cannot be combined with -c/-C/-F.")));
- if (f || have_option_m)
+ die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup");
+ die_for_incompatible_opt4(!!use_message, "-C",
+ !!edit_message, "-c",
+ !!logfile, "-F",
+ !!fixup_message, "--fixup");
+ die_for_incompatible_opt4(have_option_m, "-m",
+ !!edit_message, "-c",
+ !!use_message, "-C",
+ !!logfile, "-F");
+ if (use_message || edit_message || logfile ||fixup_message || have_option_m)
template_file = NULL;
if (edit_message)
use_message = edit_message;
if (patch_interactive)
interactive = 1;
- if (also + only + all + interactive > 1)
- die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-
+ die_for_incompatible_opt4(also, "-i/--include",
+ only, "-o/--only",
+ all, "-a/--all",
+ interactive, "--interactive/-p/--patch");
if (fixup_message) {
/*
* We limit --fixup's suboptions to only alpha characters.
strbuf_release(&matched->value);
}
- string_list_clear(&config.vars, 1);
+ urlmatch_config_release(&config);
string_list_clear(&values, 1);
free(config.url.url);
}
static char const * const count_objects_usage[] = {
- N_("git count-objects [-v] [-H | --human-readable]"),
+ "git count-objects [-v] [-H | --human-readable]",
NULL
};
"git diff [<options>] [<commit>] [--] [<path>...]\n"
" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n"
" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n"
-" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
-" or: git diff [<options>] <blob> <blob>]\n"
-" or: git diff [<options>] --no-index [--] <path> <path>]\n"
+" or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n"
+" or: git diff [<options>] <blob> <blob>\n"
+" or: git diff [<options>] --no-index [--] <path> <path>\n"
COMMON_DIFF_OPTIONS_HELP;
static const char *blob_path(struct object_array_entry *entry)
} else if (dir_diff)
die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index");
- if (use_gui_tool + !!difftool_cmd + !!extcmd > 1)
- die(_("options '%s', '%s', and '%s' cannot be used together"), "--gui", "--tool", "--extcmd");
+ die_for_incompatible_opt3(use_gui_tool, "--gui",
+ !!difftool_cmd, "--tool",
+ !!extcmd, "--extcmd");
if (use_gui_tool)
setenv("GIT_MERGETOOL_GUI", "true", 1);
#include "commit-slab.h"
static const char *fast_export_usage[] = {
- N_("git fast-export [rev-list-opts]"),
+ N_("git fast-export [<rev-list-opts>]"),
NULL
};
#include "mem-pool.h"
#include "commit-reach.h"
#include "khash.h"
+#include "date.h"
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
static const char **global_argv;
/* Memory pools */
-static struct mem_pool fi_mem_pool = {NULL, 2*1024*1024 -
- sizeof(struct mp_block), 0 };
+static struct mem_pool fi_mem_pool = {
+ .block_alloc = 2*1024*1024 - sizeof(struct mp_block),
+};
/* Atom management */
static unsigned int atom_table_sz = 4451;
static int relative_marks_paths;
/* Our last blob */
-static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
+static struct last_object last_blob = {
+ .data = STRBUF_INIT,
+ };
/* Tree management */
static unsigned int tree_entry_alloc = 1000;
static whenspec_type whenspec = WHENSPEC_RAW;
static struct strbuf command_buf = STRBUF_INIT;
static int unread_command_buf;
-static struct recent_command cmd_hist = {&cmd_hist, &cmd_hist, NULL};
+static struct recent_command cmd_hist = {
+ .prev = &cmd_hist,
+ .next = &cmd_hist,
+};
static struct recent_command *cmd_tail = &cmd_hist;
static struct recent_command *rc_free;
static unsigned int cmd_save = 100;
static struct transport *gsecondary;
static const char *submodule_prefix = "";
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
static int shown_url = 0;
static struct refspec refmap = REFSPEC_INIT_FETCH;
N_("prune remote-tracking branches no longer on remote")),
OPT_BOOL('P', "prune-tags", &prune_tags,
N_("prune local tags no longer on remote and clobber changed tags")),
- OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
+ OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
N_("control recursive fetching of submodules"),
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
OPT_BOOL(0, "dry-run", &dry_run,
item->ignore = 1;
}
+
+static void add_already_queued_tags(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ void *cb_data)
+{
+ struct hashmap *queued_tags = cb_data;
+ if (starts_with(refname, "refs/tags/") && new_oid)
+ (void) refname_hash_add(queued_tags, refname, new_oid);
+}
+
static void find_non_local_tags(const struct ref *refs,
+ struct ref_transaction *transaction,
struct ref **head,
struct ref ***tail)
{
create_fetch_oidset(head, &fetch_oids);
for_each_ref(add_one_refname, &existing_refs);
+
+ /*
+ * If we already have a transaction, then we need to filter out all
+ * tags which have already been queued up.
+ */
+ if (transaction)
+ ref_transaction_for_each_queued_update(transaction,
+ add_already_queued_tags,
+ &existing_refs);
+
for (ref = refs; ref; ref = ref->next) {
if (!starts_with(ref->name, "refs/tags/"))
continue;
/* also fetch all tags */
get_fetch_map(remote_refs, tag_refspec, &tail, 0);
else if (tags == TAGS_DEFAULT && *autotags)
- find_non_local_tags(remote_refs, &ref_map, &tail);
+ find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
/* Now append any refs to be updated opportunistically: */
*tail = orefs;
else if (!strcasecmp(format, "compact"))
compact_format = 1;
else
- die(_("configuration fetch.output contains invalid value %s"),
- format);
+ die(_("invalid value for '%s': '%s'"),
+ "fetch.output", format);
for (rm = ref_map; rm; rm = rm->next) {
if (rm->status == REF_STATUS_REJECT_SHALLOW ||
"to avoid this check\n");
static int store_updated_refs(const char *raw_url, const char *remote_name,
- int connectivity_checked, struct ref *ref_map,
- struct worktree **worktrees)
+ int connectivity_checked,
+ struct ref_transaction *transaction, struct ref *ref_map,
+ struct fetch_head *fetch_head, struct worktree **worktrees)
{
- struct fetch_head fetch_head;
int url_len, i, rc = 0;
struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
- struct ref_transaction *transaction = NULL;
const char *what, *kind;
struct ref *rm;
char *url;
int want_status;
- int summary_width = transport_summary_width(ref_map);
+ int summary_width = 0;
- rc = open_fetch_head(&fetch_head);
- if (rc)
- return -1;
+ if (verbosity >= 0)
+ summary_width = transport_summary_width(ref_map);
if (raw_url)
url = transport_anonymize_url(raw_url);
}
}
- if (atomic_fetch) {
- transaction = ref_transaction_begin(&err);
- if (!transaction) {
- error("%s", err.buf);
- goto abort;
- }
- }
-
prepare_format_display(ref_map);
/*
want_status <= FETCH_HEAD_IGNORE;
want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
- struct commit *commit = NULL;
struct ref *ref = NULL;
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
}
/*
- * References in "refs/tags/" are often going to point
- * to annotated tags, which are not part of the
- * commit-graph. We thus only try to look up refs in
- * the graph which are not in that namespace to not
- * regress performance in repositories with many
- * annotated tags.
+ * When writing FETCH_HEAD we need to determine whether
+ * we already have the commit or not. If not, then the
+ * reference is not for merge and needs to be written
+ * to the reflog after other commits which we already
+ * have. We're not interested in this property though
+ * in case FETCH_HEAD is not to be updated, so we can
+ * skip the classification in that case.
*/
- if (!starts_with(rm->name, "refs/tags/"))
- commit = lookup_commit_in_graph(the_repository, &rm->old_oid);
- if (!commit) {
- commit = lookup_commit_reference_gently(the_repository,
- &rm->old_oid,
- 1);
- if (!commit)
- rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+ if (fetch_head->fp) {
+ struct commit *commit = NULL;
+
+ /*
+ * References in "refs/tags/" are often going to point
+ * to annotated tags, which are not part of the
+ * commit-graph. We thus only try to look up refs in
+ * the graph which are not in that namespace to not
+ * regress performance in repositories with many
+ * annotated tags.
+ */
+ if (!starts_with(rm->name, "refs/tags/"))
+ commit = lookup_commit_in_graph(the_repository, &rm->old_oid);
+ if (!commit) {
+ commit = lookup_commit_reference_gently(the_repository,
+ &rm->old_oid,
+ 1);
+ if (!commit)
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+ }
}
if (rm->fetch_head_status != want_status)
strbuf_addf(¬e, "'%s' of ", what);
}
- append_fetch_head(&fetch_head, &rm->old_oid,
+ append_fetch_head(fetch_head, &rm->old_oid,
rm->fetch_head_status,
note.buf, url, url_len);
}
}
- if (!rc && transaction) {
- rc = ref_transaction_commit(transaction, &err);
- if (rc) {
- error("%s", err.buf);
- goto abort;
- }
- }
-
- if (!rc)
- commit_fetch_head(&fetch_head);
-
if (rc & STORE_REF_ERROR_DF_CONFLICT)
error(_("some local refs could not be updated; try running\n"
" 'git remote prune %s' to remove any old, conflicting "
abort:
strbuf_release(¬e);
strbuf_release(&err);
- ref_transaction_free(transaction);
free(url);
- close_fetch_head(&fetch_head);
return rc;
}
}
static int fetch_and_consume_refs(struct transport *transport,
+ struct ref_transaction *transaction,
struct ref *ref_map,
+ struct fetch_head *fetch_head,
struct worktree **worktrees)
{
int connectivity_checked = 1;
trace2_region_enter("fetch", "consume_refs", the_repository);
ret = store_updated_refs(transport->url, transport->remote->name,
- connectivity_checked, ref_map, worktrees);
+ connectivity_checked, transaction, ref_map,
+ fetch_head, worktrees);
trace2_region_leave("fetch", "consume_refs", the_repository);
out:
return ret;
}
-static int prune_refs(struct refspec *rs, struct ref *ref_map,
+static int prune_refs(struct refspec *rs,
+ struct ref_transaction *transaction,
+ struct ref *ref_map,
const char *raw_url)
{
int url_len, i, result = 0;
struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);
+ struct strbuf err = STRBUF_INIT;
char *url;
- int summary_width = transport_summary_width(stale_refs);
const char *dangling_msg = dry_run
? _(" (%s will become dangling)")
: _(" (%s has become dangling)");
url_len = i - 3;
if (!dry_run) {
- struct string_list refnames = STRING_LIST_INIT_NODUP;
+ if (transaction) {
+ for (ref = stale_refs; ref; ref = ref->next) {
+ result = ref_transaction_delete(transaction, ref->name, NULL, 0,
+ "fetch: prune", &err);
+ if (result)
+ goto cleanup;
+ }
+ } else {
+ struct string_list refnames = STRING_LIST_INIT_NODUP;
- for (ref = stale_refs; ref; ref = ref->next)
- string_list_append(&refnames, ref->name);
+ for (ref = stale_refs; ref; ref = ref->next)
+ string_list_append(&refnames, ref->name);
- result = delete_refs("fetch: prune", &refnames, 0);
- string_list_clear(&refnames, 0);
+ result = delete_refs("fetch: prune", &refnames, 0);
+ string_list_clear(&refnames, 0);
+ }
}
if (verbosity >= 0) {
+ int summary_width = transport_summary_width(stale_refs);
+
for (ref = stale_refs; ref; ref = ref->next) {
struct strbuf sb = STRBUF_INIT;
if (!shown_url) {
}
}
+cleanup:
+ strbuf_release(&err);
free(url);
free_refs(stale_refs);
return result;
return transport;
}
-static void backfill_tags(struct transport *transport, struct ref *ref_map,
- struct worktree **worktrees)
+static int backfill_tags(struct transport *transport,
+ struct ref_transaction *transaction,
+ struct ref *ref_map,
+ struct fetch_head *fetch_head,
+ struct worktree **worktrees)
{
- int cannot_reuse;
+ int retcode, cannot_reuse;
/*
* Once we have set TRANS_OPT_DEEPEN_SINCE, we can't unset it
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
transport_set_option(transport, TRANS_OPT_DEPTH, "0");
transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
- fetch_and_consume_refs(transport, ref_map, worktrees);
+ retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees);
if (gsecondary) {
transport_disconnect(gsecondary);
gsecondary = NULL;
}
+
+ return retcode;
}
static int do_fetch(struct transport *transport,
struct refspec *rs)
{
- struct ref *ref_map;
+ struct ref_transaction *transaction = NULL;
+ struct ref *ref_map = NULL;
int autotags = (transport->remote->fetch_tags == 1);
int retcode = 0;
const struct ref *remote_refs;
TRANSPORT_LS_REFS_OPTIONS_INIT;
int must_list_refs = 1;
struct worktree **worktrees = get_worktrees();
+ struct fetch_head fetch_head = { 0 };
+ struct strbuf err = STRBUF_INIT;
if (tags == TAGS_DEFAULT) {
if (transport->remote->fetch_tags == 2)
} else
remote_refs = NULL;
- strvec_clear(&transport_ls_refs_options.ref_prefixes);
+ transport_ls_refs_options_release(&transport_ls_refs_options);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
if (!update_head_ok)
check_not_current_branch(ref_map, worktrees);
+ retcode = open_fetch_head(&fetch_head);
+ if (retcode)
+ goto cleanup;
+
+ if (atomic_fetch) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ retcode = error("%s", err.buf);
+ goto cleanup;
+ }
+ }
+
if (tags == TAGS_DEFAULT && autotags)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
if (prune) {
* don't care whether --tags was specified.
*/
if (rs->nr) {
- prune_refs(rs, ref_map, transport->url);
+ retcode = prune_refs(rs, transaction, ref_map, transport->url);
} else {
- prune_refs(&transport->remote->fetch,
- ref_map,
- transport->url);
+ retcode = prune_refs(&transport->remote->fetch,
+ transaction, ref_map,
+ transport->url);
}
+ if (retcode != 0)
+ retcode = 1;
}
- if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
- free_refs(ref_map);
+
+ if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) {
retcode = 1;
goto cleanup;
}
+ /*
+ * If neither --no-tags nor --tags was specified, do automated tag
+ * following.
+ */
+ if (tags == TAGS_DEFAULT && autotags) {
+ struct ref *tags_ref_map = NULL, **tail = &tags_ref_map;
+
+ find_non_local_tags(remote_refs, transaction, &tags_ref_map, &tail);
+ if (tags_ref_map) {
+ /*
+ * If backfilling of tags fails then we want to tell
+ * the user so, but we have to continue regardless to
+ * populate upstream information of the references we
+ * have already fetched above. The exception though is
+ * when `--atomic` is passed: in that case we'll abort
+ * the transaction and don't commit anything.
+ */
+ if (backfill_tags(transport, transaction, tags_ref_map,
+ &fetch_head, worktrees))
+ retcode = 1;
+ }
+
+ free_refs(tags_ref_map);
+ }
+
+ if (transaction) {
+ if (retcode)
+ goto cleanup;
+
+ retcode = ref_transaction_commit(transaction, &err);
+ if (retcode) {
+ error("%s", err.buf);
+ ref_transaction_free(transaction);
+ transaction = NULL;
+ goto cleanup;
+ }
+ }
+
+ commit_fetch_head(&fetch_head);
+
if (set_upstream) {
struct branch *branch = branch_get("HEAD");
struct ref *rm;
if (!rm->peer_ref) {
if (source_ref) {
warning(_("multiple branches detected, incompatible with --set-upstream"));
- goto skip;
+ goto cleanup;
} else {
source_ref = rm;
}
warning(_("could not set upstream of HEAD to '%s' from '%s' when "
"it does not point to any branch."),
shortname, transport->remote->name);
- goto skip;
+ goto cleanup;
}
if (!strcmp(source_ref->name, "HEAD") ||
"you need to specify exactly one branch with the --set-upstream option"));
}
}
-skip:
- free_refs(ref_map);
- /* if neither --no-tags nor --tags was specified, do automated tag
- * following ... */
- if (tags == TAGS_DEFAULT && autotags) {
- struct ref **tail = &ref_map;
- ref_map = NULL;
- find_non_local_tags(remote_refs, &ref_map, &tail);
- if (ref_map)
- backfill_tags(transport, ref_map, worktrees);
- free_refs(ref_map);
+cleanup:
+ if (retcode && transaction) {
+ ref_transaction_abort(transaction, &err);
+ error("%s", err.buf);
}
-cleanup:
+ close_fetch_head(&fetch_head);
+ strbuf_release(&err);
+ free_refs(ref_map);
free_worktrees(worktrees);
return retcode;
}
}
git_config(git_fetch_config, NULL);
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
+
+ if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
+ recurse_submodules = recurse_submodules_cli;
+
+ if (negotiate_only) {
+ switch (recurse_submodules_cli) {
+ case RECURSE_SUBMODULES_OFF:
+ case RECURSE_SUBMODULES_DEFAULT:
+ /*
+ * --negotiate-only should never recurse into
+ * submodules. Skip it by setting recurse_submodules to
+ * RECURSE_SUBMODULES_OFF.
+ */
+ recurse_submodules = RECURSE_SUBMODULES_OFF;
+ break;
+
+ default:
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--negotiate-only", "--recurse-submodules");
+ }
+ }
+
if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
int *sfjc = submodule_fetch_jobs_config == -1
? &submodule_fetch_jobs_config : NULL;
}
if (negotiate_only && !negotiation_tip.nr)
- die(_("--negotiate-only needs one or more --negotiate-tip=*"));
+ die(_("--negotiate-only needs one or more --negotiation-tip=*"));
if (deepen_relative) {
if (deepen_relative < 0)
gtransport->smart_options->acked_commits = &acked_commits;
} else {
warning(_("protocol does not support --negotiate-only, exiting"));
- return 1;
+ result = 1;
+ goto cleanup;
}
if (server_options.nr)
gtransport->server_options = &server_options;
strvec_clear(&options);
}
- string_list_clear(&list, 0);
+ /*
+ * Skip irrelevant tasks because we know objects were not
+ * fetched.
+ *
+ * NEEDSWORK: as a future optimization, we can return early
+ * whenever objects were not fetched e.g. if we already have all
+ * of them.
+ */
+ if (negotiate_only)
+ goto cleanup;
prepare_repo_settings(the_repository);
if (fetch_write_commit_graph > 0 ||
if (enable_auto_gc)
run_auto_maintenance(verbosity < 0);
+ cleanup:
+ string_list_clear(&list, 0);
return result;
}
#include "remote.h"
#include "object-store.h"
#include "exec-cmd.h"
+#include "hook.h"
#define FAILED_RUN "failed to run %s"
else
return 0;
- if (run_hook_le(NULL, "pre-auto-gc", NULL))
+ if (run_hooks("pre-auto-gc"))
return 0;
return 1;
}
#include "object-store.h"
#include "packfile.h"
+static const char *grep_prefix;
+
static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
NULL
static int grep_cmd_config(const char *var, const char *value, void *cb)
{
int st = grep_config(var, value, cb);
- if (git_color_default_config(var, value, cb) < 0)
+ if (git_color_default_config(var, value, NULL) < 0)
st = -1;
if (!strcmp(var, "grep.threads")) {
strbuf_reset(out);
if (opt->null_following_name) {
- if (opt->relative && opt->prefix_length) {
+ if (opt->relative && grep_prefix) {
struct strbuf rel_buf = STRBUF_INIT;
const char *rel_name =
relative_path(filename + tree_name_len,
- opt->prefix, &rel_buf);
+ grep_prefix, &rel_buf);
if (tree_name_len)
strbuf_add(out, filename, tree_name_len);
return;
}
- if (opt->relative && opt->prefix_length)
- quote_path(filename + tree_name_len, opt->prefix, out, 0);
+ if (opt->relative && grep_prefix)
+ quote_path(filename + tree_name_len, grep_prefix, out, 0);
else
quote_c_style(filename + tree_name_len, out, NULL, 0);
int i;
int dummy;
int use_index = 1;
- int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
int allow_revs;
struct option options[] = {
N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
NULL, 1 },
OPT_GROUP(""),
- OPT_SET_INT('E', "extended-regexp", &pattern_type_arg,
+ OPT_SET_INT('E', "extended-regexp", &opt.pattern_type_option,
N_("use extended POSIX regular expressions"),
GREP_PATTERN_TYPE_ERE),
- OPT_SET_INT('G', "basic-regexp", &pattern_type_arg,
+ OPT_SET_INT('G', "basic-regexp", &opt.pattern_type_option,
N_("use basic POSIX regular expressions (default)"),
GREP_PATTERN_TYPE_BRE),
- OPT_SET_INT('F', "fixed-strings", &pattern_type_arg,
+ OPT_SET_INT('F', "fixed-strings", &opt.pattern_type_option,
N_("interpret patterns as fixed strings"),
GREP_PATTERN_TYPE_FIXED),
- OPT_SET_INT('P', "perl-regexp", &pattern_type_arg,
+ OPT_SET_INT('P', "perl-regexp", &opt.pattern_type_option,
N_("use Perl-compatible regular expressions"),
GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
PARSE_OPT_NOCOMPLETE),
OPT_END()
};
+ grep_prefix = prefix;
- git_config(grep_cmd_config, NULL);
- grep_init(&opt, the_repository, prefix);
+ grep_init(&opt, the_repository);
+ git_config(grep_cmd_config, &opt);
/*
* If there is no -- then the paths must exist in the working
argc = parse_options(argc, argv, prefix, options, grep_usage,
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_STOP_AT_NON_OPTION);
- grep_commit_pattern_type(pattern_type_arg, &opt);
if (use_index && !startup_info->have_repository) {
int fallback = 0;
if (!show_in_pager && !opt.status_only)
setup_pager();
- if (!use_index && (untracked || cached))
- die(_("--cached or --untracked cannot be used with --no-index"));
-
- if (untracked && cached)
- die(_("--untracked cannot be used with --cached"));
+ die_for_incompatible_opt3(!use_index, "--no-index",
+ untracked, "--untracked",
+ cached, "--cached");
if (!use_index || untracked) {
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
{
static const char * const hash_object_usage[] = {
N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."),
- N_("git hash-object --stdin-paths"),
+ "git hash-object --stdin-paths",
NULL
};
const char *type = blob_type;
int nongit = 0;
unsigned flags = HASH_FORMAT_CHECK;
const char *vpath = NULL;
+ char *vpath_free = NULL;
const struct option hash_object_options[] = {
OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
OPT_BIT('w', NULL, &flags, N_("write the object into the object database"),
else
prefix = setup_git_directory_gently(&nongit);
- if (vpath && prefix)
- vpath = prefix_filename(prefix, vpath);
+ if (vpath && prefix) {
+ vpath_free = prefix_filename(prefix, vpath);
+ vpath = vpath_free;
+ }
git_config(git_default_config, NULL);
if (stdin_paths)
hash_stdin_paths(type, no_filters, flags, literally);
+ free(vpath_free);
+
return 0;
}
static int verbose = 1;
static enum help_format help_format = HELP_FORMAT_NONE;
static int exclude_guides;
+static int show_external_commands = -1;
+static int show_aliases = -1;
static struct option builtin_help_options[] = {
OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"),
HELP_ACTION_ALL),
+ OPT_BOOL(0, "external-commands", &show_external_commands,
+ N_("show external commands in --all")),
+ OPT_BOOL(0, "aliases", &show_aliases, N_("show aliases in --all")),
OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
};
static const char * const builtin_help_usage[] = {
- N_("git help [-a|--all] [--[no-]verbose]]\n"
- " [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
- N_("git help [-g|--guides]"),
- N_("git help [-c|--config]"),
+ "git help [-a|--all] [--[no-]verbose]] [--[no-]external-commands] [--[no-]aliases]",
+ N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
+ "git help [-g|--guides]",
+ "git help [-c|--config]",
NULL
};
return cmd;
}
-static void no_extra_argc(int argc)
+static void no_help_format(const char *opt_mode, enum help_format fmt)
+{
+ const char *opt_fmt;
+
+ switch (fmt) {
+ case HELP_FORMAT_NONE:
+ return;
+ case HELP_FORMAT_MAN:
+ opt_fmt = "--man";
+ break;
+ case HELP_FORMAT_INFO:
+ opt_fmt = "--info";
+ break;
+ case HELP_FORMAT_WEB:
+ opt_fmt = "--web";
+ break;
+ default:
+ BUG("unreachable");
+ }
+
+ usage_msg_optf(_("options '%s' and '%s' cannot be used together"),
+ builtin_help_usage, builtin_help_options, opt_mode,
+ opt_fmt);
+}
+
+static void opt_mode_usage(int argc, const char *opt_mode,
+ enum help_format fmt)
{
if (argc)
- usage_msg_opt(_("this option doesn't take any other arguments"),
- builtin_help_usage, builtin_help_options);
+ usage_msg_optf(_("the '%s' option doesn't take any non-option arguments"),
+ builtin_help_usage, builtin_help_options,
+ opt_mode);
+
+ no_help_format(opt_mode, fmt);
}
int cmd_help(int argc, const char **argv, const char *prefix)
builtin_help_usage, 0);
parsed_help_format = help_format;
+ if (cmd_mode != HELP_ACTION_ALL &&
+ (show_external_commands >= 0 ||
+ show_aliases >= 0))
+ usage_msg_opt(_("the '--no-[external-commands|aliases]' options can only be used with '--all'"),
+ builtin_help_usage, builtin_help_options);
+
switch (cmd_mode) {
case HELP_ACTION_ALL:
+ opt_mode_usage(argc, "--all", help_format);
if (verbose) {
setup_pager();
- list_all_cmds_help();
+ list_all_cmds_help(show_external_commands,
+ show_aliases);
return 0;
}
printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
printf("%s\n", _(git_more_info_string));
break;
case HELP_ACTION_GUIDES:
- no_extra_argc(argc);
+ opt_mode_usage(argc, "--guides", help_format);
list_guides_help();
printf("%s\n", _(git_more_info_string));
return 0;
case HELP_ACTION_CONFIG_FOR_COMPLETION:
- no_extra_argc(argc);
+ opt_mode_usage(argc, "--config-for-completion", help_format);
list_config_help(SHOW_CONFIG_VARS);
return 0;
case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION:
- no_extra_argc(argc);
+ opt_mode_usage(argc, "--config-sections-for-completion",
+ help_format);
list_config_help(SHOW_CONFIG_SECTIONS);
return 0;
case HELP_ACTION_CONFIG:
- no_extra_argc(argc);
+ opt_mode_usage(argc, "--config", help_format);
setup_pager();
list_config_help(SHOW_CONFIG_HUMAN);
printf("\n%s\n", _("'git help config' for more information"));
--- /dev/null
+#include "cache.h"
+#include "builtin.h"
+#include "config.h"
+#include "hook.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define BUILTIN_HOOK_RUN_USAGE \
+ N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
+
+static const char * const builtin_hook_usage[] = {
+ BUILTIN_HOOK_RUN_USAGE,
+ NULL
+};
+
+static const char * const builtin_hook_run_usage[] = {
+ BUILTIN_HOOK_RUN_USAGE,
+ NULL
+};
+
+static int run(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+ int ignore_missing = 0;
+ const char *hook_name;
+ struct option run_options[] = {
+ OPT_BOOL(0, "ignore-missing", &ignore_missing,
+ N_("silently ignore missing requested <hook-name>")),
+ OPT_END(),
+ };
+ int ret;
+
+ argc = parse_options(argc, argv, prefix, run_options,
+ builtin_hook_run_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+
+ if (!argc)
+ goto usage;
+
+ /*
+ * Having a -- for "run" when providing <hook-args> is
+ * mandatory.
+ */
+ if (argc > 1 && strcmp(argv[1], "--") &&
+ strcmp(argv[1], "--end-of-options"))
+ goto usage;
+
+ /* Add our arguments, start after -- */
+ for (i = 2 ; i < argc; i++)
+ strvec_push(&opt.args, argv[i]);
+
+ /* Need to take into account core.hooksPath */
+ git_config(git_default_config, NULL);
+
+ hook_name = argv[0];
+ if (!ignore_missing)
+ opt.error_if_missing = 1;
+ ret = run_hooks_opt(hook_name, &opt);
+ if (ret < 0) /* error() return */
+ ret = 1;
+ return ret;
+usage:
+ usage_with_options(builtin_hook_run_usage, run_options);
+}
+
+int cmd_hook(int argc, const char **argv, const char *prefix)
+{
+ struct option builtin_hook_options[] = {
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, NULL, builtin_hook_options,
+ builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc)
+ goto usage;
+
+ if (!strcmp(argv[0], "run"))
+ return run(argc, argv, prefix);
+
+usage:
+ usage_with_options(builtin_hook_usage, builtin_hook_options);
+}
if (signed_add_overflows(consumed_bytes, bytes))
die(_("pack too large for current definition of off_t"));
consumed_bytes += bytes;
- if (max_input_size && consumed_bytes > max_input_size)
- die(_("pack exceeds maximum allowed size"));
+ if (max_input_size && consumed_bytes > max_input_size) {
+ struct strbuf size_limit = STRBUF_INIT;
+ strbuf_humanise_bytes(&size_limit, max_input_size);
+ die(_("pack exceeds maximum allowed size (%s)"),
+ size_limit.buf);
+ }
}
static const char *open_pack_file(const char *pack_name)
list_add(&child->list, &work_head);
base_cache_used += child->size;
prune_base_data(NULL);
+ free_base_data(child);
} else {
/*
* This child does not have its own children. It may be
p = next_p;
}
+ FREE_AND_NULL(child);
}
work_unlock();
}
* object).
*/
append_obj_to_pack(f, d->oid.hash, data, size, type);
+ free(data);
threaded_second_pass(NULL);
display_progress(progress, nr_resolved_deltas);
i + 1,
chain_histogram[i]);
}
+ free(chain_histogram);
}
int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (do_fsck_object && fsck_finish(&fsck_options))
die(_("fsck error in pack objects"));
+ free(opts.anomaly);
free(objects);
strbuf_release(&index_name_buf);
strbuf_release(&rev_index_name_buf);
#include "repository.h"
#include "commit-reach.h"
#include "range-diff.h"
+#include "tmp-objdir.h"
#define MAIL_DEFAULT_WRAP 72
#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
int saved_nrl = 0;
int saved_dcctc = 0;
+ if (rev->remerge_diff) {
+ rev->remerge_objdir = tmp_objdir_create("remerge-diff");
+ if (!rev->remerge_objdir)
+ die(_("unable to create temporary object directory"));
+ tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1);
+ }
+
if (rev->early_output)
setup_early_output();
rev->diffopt.no_free = 0;
diff_free(&rev->diffopt);
+ if (rev->remerge_diff) {
+ tmp_objdir_destroy(rev->remerge_objdir);
+ rev->remerge_objdir = NULL;
+ }
+
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
rev->diffopt.flags.check_failed) {
return 02;
return 0;
}
- if (grep_config(var, value, cb) < 0)
- return -1;
if (git_gpg_config(var, value, cb) < 0)
return -1;
return git_diff_ui_config(var, value, cb);
git_config(git_log_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.diff = 1;
rev.simplify_history = 0;
memset(&opt, 0, sizeof(opt));
memset(&match_all, 0, sizeof(match_all));
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.diff = 1;
rev.always_show_header = 1;
rev.no_walk = 1;
repo_init_revisions(the_repository, &rev, prefix);
init_reflog_walk(&rev.reflog_info);
+ git_config(grep_config, &rev.grep_filter);
+
rev.verbose_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
git_config(git_log_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.always_show_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
extra_hdr.strdup_strings = 1;
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;
+
init_log_defaults();
init_display_notes(¬es_opt);
git_config(git_format_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.show_notes = show_notes;
memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt));
rev.commit_format = CMIT_FMT_EMAIL;
die(_("--name-status does not make sense"));
if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
die(_("--check does not make sense"));
+ if (rev.remerge_diff)
+ die(_("--remerge-diff does not make sense"));
if (!use_patch_format &&
(!rev.diffopt.output_format ||
if (rev.show_notes)
load_display_notes(&rev.notes_opt);
- if (use_stdout + rev.diffopt.close_file + !!output_directory > 1)
- die(_("options '%s', '%s', and '%s' cannot be used together"), "--stdout", "--output", "--output-directory");
+ die_for_incompatible_opt3(use_stdout, "--stdout",
+ rev.diffopt.close_file, "--output",
+ !!output_directory, "--output-directory");
if (use_stdout) {
setup_pager();
printf("%s%06o %s %d\t",
tag,
ce->ce_mode,
- find_unique_abbrev(&ce->oid, abbrev),
+ repo_find_unique_abbrev(repo, &ce->oid, abbrev),
ce_stage(ce));
}
write_eolinfo(repo->index, ce, fullname);
setup_work_tree();
if (recurse_submodules &&
- (show_stage || show_deleted || show_others || show_unmerged ||
+ (show_deleted || show_others || show_unmerged ||
show_killed || show_modified || show_resolve_undo || with_tree))
die("ls-files --recurse-submodules unsupported mode");
ref_array_clear(&ref_array);
if (transport_disconnect(transport))
- return 1;
+ status = 1;
+ transport_ls_refs_options_release(&transport_options);
return status;
}
git_config(git_default_config, NULL);
ls_tree_prefix = prefix;
- if (prefix && *prefix)
+ if (prefix)
chomp_prefix = strlen(prefix);
argc = parse_options(argc, argv, prefix, ls_tree_options,
int rev_nr = 0;
int show_all = 0;
int cmdmode = 0;
+ int ret;
struct option options[] = {
OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),
if (argc < 2)
usage_with_options(merge_base_usage, options);
if (show_all)
- die("--is-ancestor cannot be used with --all");
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--is-ancestor", "--all");
return handle_is_ancestor(argc, argv);
}
if (cmdmode == 'r' && show_all)
- die("--independent cannot be used with --all");
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--independent", "--all");
if (cmdmode == 'o')
return handle_octopus(argc, argv, show_all);
ALLOC_ARRAY(rev, argc);
while (argc-- > 0)
rev[rev_nr++] = get_commit_reference(*argv++);
- return show_merge_base(rev, rev_nr, show_all);
+ ret = show_merge_base(rev, rev_nr, show_all);
+ free(rev);
+ return ret;
}
}
/* Run a post-merge hook */
- run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+ run_hooks_l("post-merge", squash ? "1" : "0", NULL);
apply_autostash(git_path_merge_autostash(the_repository));
strbuf_release(&reflog_message);
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
struct commit_list *common = NULL;
const char *best_strategy = NULL, *wt_strategy = NULL;
- struct commit_list *remoteheads, *p;
+ struct commit_list *remoteheads = NULL, *p;
void *branch_to_free;
int orig_argc = argc;
if (autostash)
create_autostash(the_repository,
- git_path_merge_autostash(the_repository),
- "merge");
+ git_path_merge_autostash(the_repository));
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
if (autostash)
create_autostash(the_repository,
- git_path_merge_autostash(the_repository),
- "merge");
+ git_path_merge_autostash(the_repository));
/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
ret = suggest_conflicts();
done:
+ if (!automerge_was_ok) {
+ free_commit_list(common);
+ free_commit_list(remoteheads);
+ }
strbuf_release(&buf);
free(branch_to_free);
return ret;
#include "config.h"
static char const * const builtin_mktag_usage[] = {
- N_("git mktag"),
+ "git mktag",
NULL
};
static int option_strict = 1;
}
static const char *mktree_usage[] = {
- N_("git mktree [-z] [--missing] [--batch]"),
+ "git mktree [-z] [--missing] [--batch]",
NULL
};
static char const * const name_rev_usage[] = {
N_("git name-rev [<options>] <commit>..."),
N_("git name-rev [<options>] --all"),
- N_("git name-rev [<options>] --stdin"),
+ N_("git name-rev [<options>] --annotate-stdin"),
NULL
};
int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
struct object_array revs = OBJECT_ARRAY_INIT;
- int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
+ int all = 0, annotate_stdin = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
struct option opts[] = {
OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")),
N_("ignore refs matching <pattern>")),
OPT_GROUP(""),
OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
- OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
+ OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use annotate-stdin instead")),
+ OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")),
OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
OPT_BOOL(0, "always", &always,
N_("show abbreviated commit object as fallback")),
init_commit_rev_name(&rev_names);
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
- if (all + transform_stdin + !!argc > 1) {
+
+ if (transform_stdin) {
+ warning("--stdin is deprecated. Please use --annotate-stdin instead, "
+ "which is functionally equivalent.\n"
+ "This option will be removed in a future release.");
+ annotate_stdin = 1;
+ }
+
+ if (all + annotate_stdin + !!argc > 1) {
error("Specify either a list, or --all, not both!");
usage_with_options(name_rev_usage, opts);
}
- if (all || transform_stdin)
+ if (all || annotate_stdin)
cutoff = 0;
for (; argc; argc--, argv++) {
for_each_ref(name_ref, &data);
name_tips();
- if (transform_stdin) {
- char buffer[2048];
+ if (annotate_stdin) {
+ struct strbuf sb = STRBUF_INIT;
- while (!feof(stdin)) {
- char *p = fgets(buffer, sizeof(buffer), stdin);
- if (!p)
- break;
- name_rev_line(p, &data);
+ while (strbuf_getline(&sb, stdin) != EOF) {
+ strbuf_addch(&sb, '\n');
+ name_rev_line(sb.buf, &data);
}
+ strbuf_release(&sb);
} else if (all) {
int i, max;
N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
N_("git notes [--ref <notes-ref>] show [<object>]"),
N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
- N_("git notes merge --commit [-v | -q]"),
- N_("git notes merge --abort [-v | -q]"),
+ "git notes merge --commit [-v | -q]",
+ "git notes merge --abort [-v | -q]",
N_("git notes [--ref <notes-ref>] remove [<object>...]"),
N_("git notes [--ref <notes-ref>] prune [-n] [-v]"),
N_("git notes [--ref <notes-ref>] get-ref"),
};
static const char * const git_notes_get_ref_usage[] = {
- N_("git notes get-ref"),
+ "git notes get-ref",
NULL
};
return 0;
}
- die(_("invalid value for --missing"));
+ die(_("invalid value for '%s': '%s'"), "--missing", arg);
return 0;
}
read_replace_refs = 0;
sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
- prepare_repo_settings(the_repository);
- if (sparse < 0)
- sparse = the_repository->settings.pack_use_sparse;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ if (sparse < 0)
+ sparse = the_repository->settings.pack_use_sparse;
+ }
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
n = strspn(q, digits);
if (q[n] == ',') {
q += n + 1;
+ *p_before = atoi(q);
n = strspn(q, digits);
+ } else {
+ *p_before = 1;
}
+
if (n == 0 || q[n] != ' ' || q[n+1] != '+')
return 0;
n = strspn(r, digits);
if (r[n] == ',') {
r += n + 1;
+ *p_after = atoi(r);
n = strspn(r, digits);
+ } else {
+ *p_after = 1;
}
if (n == 0)
return 0;
- *p_before = atoi(q);
- *p_after = atoi(r);
return 1;
}
#include "prune-packed.h"
static const char * const prune_packed_usage[] = {
- N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
+ "git prune-packed [-n | --dry-run] [-q | --quiet]",
NULL
};
return v;
if (fatal)
- die(_("Invalid value for %s: %s"), key, value);
+ die(_("invalid value for '%s': '%s'"), key, value);
else
- error(_("Invalid value for %s: %s"), key, value);
+ error(_("invalid value for '%s': '%s'"), key, value);
return REBASE_INVALID;
}
if (!strcmp(value, "only"))
return "--ff-only";
- die(_("Invalid value for pull.ff: %s"), value);
+ die(_("invalid value for '%s': '%s'"), "pull.ff", value);
}
/**
set_reflog_message(argc, argv);
git_config(git_pull_config, NULL);
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
oidclr(&orig_head);
if (opt_rebase) {
- int autostash = config_autostash;
- if (opt_autostash != -1)
- autostash = opt_autostash;
+ if (opt_autostash == -1)
+ opt_autostash = config_autostash;
if (is_null_oid(&orig_head) && !is_cache_unborn())
die(_("Updating an unborn branch with changes added to the index."));
- if (!autostash)
+ if (!opt_autostash)
require_clean_work_tree(the_repository,
N_("pull with rebase"),
_("please commit or stash them."), 1, 0);
if (value && !strcasecmp(value, "if-asked"))
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED);
else
- return error("Invalid value for '%s'", k);
+ return error(_("invalid value for '%s'"), k);
}
}
} else if (!strcmp(k, "push.recursesubmodules")) {
#include "sequencer.h"
#include "rebase-interactive.h"
#include "reset.h"
+#include "hook.h"
#define DEFAULT_REFLOG_ACTION "rebase"
"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
"--root [<branch>]"),
- N_("git rebase --continue | --abort | --skip | --edit-todo"),
+ "git rebase --continue | --abort | --skip | --edit-todo",
NULL
};
static int move_to_original_branch(struct rebase_options *opts)
{
- struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ struct reset_head_opts ropts = { 0 };
int ret;
if (!opts->head_name)
if (!opts->onto)
BUG("move_to_original_branch without onto");
- strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
+ strbuf_addf(&branch_reflog, "rebase finished: %s onto %s",
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
- ret = reset_head(the_repository, NULL, "", opts->head_name,
- RESET_HEAD_REFS_ONLY,
- orig_head_reflog.buf, head_reflog.buf,
- DEFAULT_REFLOG_ACTION);
+ ropts.branch = opts->head_name;
+ ropts.flags = RESET_HEAD_REFS_ONLY;
+ ropts.branch_msg = branch_reflog.buf;
+ ropts.head_msg = head_reflog.buf;
+ ret = reset_head(the_repository, &ropts);
- strbuf_release(&orig_head_reflog);
+ strbuf_release(&branch_reflog);
strbuf_release(&head_reflog);
return ret;
}
status = run_command(&format_patch);
if (status) {
+ struct reset_head_opts ropts = { 0 };
unlink(rebased_patches);
free(rebased_patches);
strvec_clear(&am.args);
- reset_head(the_repository, &opts->orig_head, "checkout",
- opts->head_name, 0,
- "HEAD", NULL, DEFAULT_REFLOG_ACTION);
+ ropts.oid = &opts->orig_head;
+ ropts.branch = opts->head_name;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ reset_head(the_repository, &ropts);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
return git_default_config(var, value, data);
}
+static int checkout_up_to_date(struct rebase_options *options)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct reset_head_opts ropts = { 0 };
+ int ret = 0;
+
+ strbuf_addf(&buf, "%s: checkout %s",
+ getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
+ options->switch_to);
+ ropts.oid = &options->orig_head;
+ ropts.branch = options->head_name;
+ ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ ropts.head_msg = buf.buf;
+ if (reset_head(the_repository, &ropts) < 0)
+ ret = error(_("could not switch to %s"), options->switch_to);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
/*
* Determines whether the commits in from..to are linear, i.e. contain
* no merge commits. This function *expects* `from` to be an ancestor of
int reschedule_failed_exec = -1;
int allow_preemptive_ff = 1;
int preserve_merges_selected = 0;
+ struct reset_head_opts ropts = { 0 };
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
-
- if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
- NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
+ ropts.flags = RESET_HEAD_HARD;
+ if (reset_head(the_repository, &ropts) < 0)
die(_("could not discard worktree changes"));
remove_branch_state(the_repository, 0);
if (read_basic_state(&options))
if (read_basic_state(&options))
exit(1);
- if (reset_head(the_repository, &options.orig_head, "reset",
- options.head_name, RESET_HEAD_HARD,
- NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
+ ropts.oid = &options.orig_head;
+ ropts.branch = options.head_name;
+ ropts.flags = RESET_HEAD_HARD;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ if (reset_head(the_repository, &ropts) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
remove_branch_state(the_repository, 0);
if (repo_read_index(the_repository) < 0)
die(_("could not read index"));
- if (options.autostash) {
- create_autostash(the_repository, state_dir_path("autostash", &options),
- DEFAULT_REFLOG_ACTION);
- }
+ if (options.autostash)
+ create_autostash(the_repository,
+ state_dir_path("autostash", &options));
+
if (require_clean_work_tree(the_repository, "rebase",
_("Please commit or stash them."), 1, 1)) {
if (!(options.flags & REBASE_FORCE)) {
/* Lazily switch to the target branch if needed... */
if (options.switch_to) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s: checkout %s",
- getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
- options.switch_to);
- if (reset_head(the_repository,
- &options.orig_head, "checkout",
- options.head_name,
- RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, buf.buf,
- DEFAULT_REFLOG_ACTION) < 0) {
- ret = error(_("could not switch to "
- "%s"),
- options.switch_to);
+ ret = checkout_up_to_date(&options);
+ if (ret)
goto cleanup;
- }
}
if (!(options.flags & REBASE_NO_QUIET))
/* If a hook exists, give it a chance to interrupt*/
if (!ok_to_skip_pre_rebase &&
- run_hook_le(NULL, "pre-rebase", options.upstream_arg,
+ run_hooks_l("pre-rebase", options.upstream_arg,
argc ? argv[0] : NULL, NULL))
die(_("The pre-rebase hook refused to rebase."));
strbuf_addf(&msg, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
- if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
- RESET_HEAD_DETACH | RESET_ORIG_HEAD |
- RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, msg.buf, DEFAULT_REFLOG_ACTION))
+ ropts.oid = &options.onto->object.oid;
+ ropts.orig_head = &options.orig_head,
+ ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ ropts.head_msg = msg.buf;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ if (reset_head(the_repository, &ropts))
die(_("Could not detach HEAD"));
strbuf_release(&msg);
strbuf_addf(&msg, "rebase finished: %s onto %s",
options.head_name ? options.head_name : "detached HEAD",
oid_to_hex(&options.onto->object.oid));
- reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
- RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
- DEFAULT_REFLOG_ACTION);
+ memset(&ropts, 0, sizeof(ropts));
+ ropts.branch = options.head_name;
+ ropts.flags = RESET_HEAD_REFS_ONLY;
+ ropts.head_msg = msg.buf;
+ reset_head(the_repository, &ropts);
strbuf_release(&msg);
ret = finish_rebase(&options);
goto cleanup;
return strbuf_detach(&buf, NULL);
}
-/*
- * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing
- * after dropping "_commit" from its name and possibly moving it out
- * of commit.c
- */
static char *find_header(const char *msg, size_t len, const char *key,
const char **next_line)
{
- int key_len = strlen(key);
- const char *line = msg;
-
- while (line && line < msg + len) {
- const char *eol = strchrnul(line, '\n');
-
- if ((msg + len <= eol) || line == eol)
- return NULL;
- if (line + key_len < eol &&
- !memcmp(line, key, key_len) && line[key_len] == ' ') {
- int offset = key_len + 1;
- if (next_line)
- *next_line = *eol ? eol + 1 : eol;
- return xmemdupz(line + offset, (eol - line) - offset);
- }
- line = *eol ? eol + 1 : NULL;
- }
- return NULL;
+ size_t out_len;
+ const char *val = find_header_mem(msg, len, key, &out_len);
+
+ if (!val)
+ return NULL;
+
+ if (next_line)
+ *next_line = val + out_len + 1;
+
+ return xmemdupz(val, out_len);
}
/*
struct strvec *env,
const char *work_tree)
{
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
- if (run_hook_le(env->v, push_to_checkout_hook,
- hash_to_hex(hash), NULL))
+ strvec_pushv(&opt.env, env->v);
+ strvec_push(&opt.args, hash_to_hex(hash));
+ if (run_hooks_opt(push_to_checkout_hook, &opt))
return "push-to-checkout hook declined";
else
return NULL;
return;
}
+ /*
+ * If there is no command ready to run, should return directly to destroy
+ * temporary data in the quarantine area.
+ */
+ for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next)
+ ; /* nothing */
+ if (!cmd)
+ return;
+
/*
* Now we'll start writing out refs, which means the objects need
* to be in their final positions so that other processes can see them.
#include "reachable.h"
#include "worktree.h"
-/* NEEDSWORK: switch to using parse_options */
-static const char reflog_expire_usage[] =
-N_("git reflog expire [--expire=<time>] "
- "[--expire-unreachable=<time>] "
- "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] "
- "[--verbose] [--all] <refs>...");
-static const char reflog_delete_usage[] =
-N_("git reflog delete [--rewrite] [--updateref] "
- "[--dry-run | -n] [--verbose] <refs>...");
static const char reflog_exists_usage[] =
N_("git reflog exists <ref>");
struct cmd_reflog_expire_cb {
int stalefix;
+ int explicit_expiry;
timestamp_t expire_total;
timestamp_t expire_unreachable;
int recno;
return 0;
}
-static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
+static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref)
{
struct reflog_expire_cfg *ent;
- if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
+ if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH))
return; /* both given explicitly -- nothing to tweak */
for (ent = reflog_expire_cfg; ent; ent = ent->next) {
if (!wildmatch(ent->pattern, ref, 0)) {
- if (!(slot & EXPIRE_TOTAL))
+ if (!(cb->explicit_expiry & EXPIRE_TOTAL))
cb->expire_total = ent->expire_total;
- if (!(slot & EXPIRE_UNREACH))
+ if (!(cb->explicit_expiry & EXPIRE_UNREACH))
cb->expire_unreachable = ent->expire_unreachable;
return;
}
* If unconfigured, make stash never expire
*/
if (!strcmp(ref, "refs/stash")) {
- if (!(slot & EXPIRE_TOTAL))
+ if (!(cb->explicit_expiry & EXPIRE_TOTAL))
cb->expire_total = 0;
- if (!(slot & EXPIRE_UNREACH))
+ if (!(cb->explicit_expiry & EXPIRE_UNREACH))
cb->expire_unreachable = 0;
return;
}
/* Nothing matched -- use the default value */
- if (!(slot & EXPIRE_TOTAL))
+ if (!(cb->explicit_expiry & EXPIRE_TOTAL))
cb->expire_total = default_reflog_expire;
- if (!(slot & EXPIRE_UNREACH))
+ if (!(cb->explicit_expiry & EXPIRE_UNREACH))
cb->expire_unreachable = default_reflog_expire_unreachable;
}
+static const char * reflog_expire_usage[] = {
+ N_("git reflog expire [--expire=<time>] "
+ "[--expire-unreachable=<time>] "
+ "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] "
+ "[--verbose] [--all] <refs>..."),
+ NULL
+};
+
+static int expire_unreachable_callback(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ struct cmd_reflog_expire_cb *cmd = opt->value;
+
+ if (parse_expiry_date(arg, &cmd->expire_unreachable))
+ die(_("invalid timestamp '%s' given to '--%s'"),
+ arg, opt->long_name);
+
+ cmd->explicit_expiry |= EXPIRE_UNREACH;
+ return 0;
+}
+
+static int expire_total_callback(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ struct cmd_reflog_expire_cb *cmd = opt->value;
+
+ if (parse_expiry_date(arg, &cmd->expire_total))
+ die(_("invalid timestamp '%s' given to '--%s'"),
+ arg, opt->long_name);
+
+ cmd->explicit_expiry |= EXPIRE_TOTAL;
+ return 0;
+}
+
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
{
struct cmd_reflog_expire_cb cmd = { 0 };
timestamp_t now = time(NULL);
int i, status, do_all, all_worktrees = 1;
- int explicit_expiry = 0;
unsigned int flags = 0;
int verbose = 0;
reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
+ const struct option options[] = {
+ OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
+ EXPIRE_REFLOGS_DRY_RUN),
+ OPT_BIT(0, "rewrite", &flags,
+ N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
+ EXPIRE_REFLOGS_REWRITE),
+ OPT_BIT(0, "updateref", &flags,
+ N_("update the reference to the value of the top reflog entry"),
+ EXPIRE_REFLOGS_UPDATE_REF),
+ OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")),
+ OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"),
+ N_("prune entries older than the specified time"),
+ PARSE_OPT_NONEG,
+ expire_total_callback),
+ OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"),
+ N_("prune entries older than <time> that are not reachable from the current tip of the branch"),
+ PARSE_OPT_NONEG,
+ expire_unreachable_callback),
+ OPT_BOOL(0, "stale-fix", &cmd.stalefix,
+ N_("prune any reflog entries that point to broken commits")),
+ OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")),
+ OPT_BOOL(1, "single-worktree", &all_worktrees,
+ N_("limits processing to reflogs from the current worktree only")),
+ OPT_END()
+ };
default_reflog_expire_unreachable = now - 30 * 24 * 3600;
default_reflog_expire = now - 90 * 24 * 3600;
save_commit_buffer = 0;
do_all = status = 0;
+ cmd.explicit_expiry = 0;
cmd.expire_total = default_reflog_expire;
cmd.expire_unreachable = default_reflog_expire_unreachable;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
- flags |= EXPIRE_REFLOGS_DRY_RUN;
- else if (skip_prefix(arg, "--expire=", &arg)) {
- if (parse_expiry_date(arg, &cmd.expire_total))
- die(_("'%s' is not a valid timestamp"), arg);
- explicit_expiry |= EXPIRE_TOTAL;
- }
- else if (skip_prefix(arg, "--expire-unreachable=", &arg)) {
- if (parse_expiry_date(arg, &cmd.expire_unreachable))
- die(_("'%s' is not a valid timestamp"), arg);
- explicit_expiry |= EXPIRE_UNREACH;
- }
- else if (!strcmp(arg, "--stale-fix"))
- cmd.stalefix = 1;
- else if (!strcmp(arg, "--rewrite"))
- flags |= EXPIRE_REFLOGS_REWRITE;
- else if (!strcmp(arg, "--updateref"))
- flags |= EXPIRE_REFLOGS_UPDATE_REF;
- else if (!strcmp(arg, "--all"))
- do_all = 1;
- else if (!strcmp(arg, "--single-worktree"))
- all_worktrees = 0;
- else if (!strcmp(arg, "--verbose"))
- verbose = 1;
- else if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- else if (arg[0] == '-')
- usage(_(reflog_expire_usage));
- else
- break;
- }
+ argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0);
if (verbose)
should_prune_fn = should_expire_reflog_ent_verbose;
.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),
};
- set_reflog_expiry_param(&cb.cmd, explicit_expiry, item->string);
+ set_reflog_expiry_param(&cb.cmd, item->string);
status |= reflog_expire(item->string, flags,
reflog_expiry_prepare,
should_prune_fn,
string_list_clear(&collected.reflogs, 0);
}
- for (; i < argc; i++) {
+ for (i = 0; i < argc; i++) {
char *ref;
struct expire_reflog_policy_cb cb = { .cmd = cmd };
status |= error(_("%s points nowhere!"), argv[i]);
continue;
}
- set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
+ set_reflog_expiry_param(&cb.cmd, ref);
status |= reflog_expire(ref, flags,
reflog_expiry_prepare,
should_prune_fn,
return 0;
}
+static const char * reflog_delete_usage[] = {
+ N_("git reflog delete [--rewrite] [--updateref] "
+ "[--dry-run | -n] [--verbose] <refs>..."),
+ NULL
+};
+
static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
{
struct cmd_reflog_expire_cb cmd = { 0 };
unsigned int flags = 0;
int verbose = 0;
reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
- flags |= EXPIRE_REFLOGS_DRY_RUN;
- else if (!strcmp(arg, "--rewrite"))
- flags |= EXPIRE_REFLOGS_REWRITE;
- else if (!strcmp(arg, "--updateref"))
- flags |= EXPIRE_REFLOGS_UPDATE_REF;
- else if (!strcmp(arg, "--verbose"))
- verbose = 1;
- else if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- else if (arg[0] == '-')
- usage(_(reflog_delete_usage));
- else
- break;
- }
+ const struct option options[] = {
+ OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
+ EXPIRE_REFLOGS_DRY_RUN),
+ OPT_BIT(0, "rewrite", &flags,
+ N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
+ EXPIRE_REFLOGS_REWRITE),
+ OPT_BIT(0, "updateref", &flags,
+ N_("update the reference to the value of the top reflog entry"),
+ EXPIRE_REFLOGS_UPDATE_REF),
+ OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);
if (verbose)
should_prune_fn = should_expire_reflog_ent_verbose;
- if (argc - i < 1)
+ if (argc < 1)
return error(_("no reflog specified to delete"));
- for ( ; i < argc; i++) {
+ for (i = 0; i < argc; i++) {
const char *spec = strstr(argv[i], "@{");
char *ep, *ref;
int recno;
*/
static const char reflog_usage[] =
-N_("git reflog [ show | expire | delete | exists ]");
+"git reflog [ show | expire | delete | exists ]";
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
#include "commit-reach.h"
static const char * const builtin_remote_usage[] = {
- N_("git remote [-v | --verbose]"),
+ "git remote [-v | --verbose]",
N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"),
N_("git remote rename <old> <new>"),
N_("git remote remove <name>"),
for_each_ref(read_remote_branches, &rename);
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
- int flag = 0;
+ struct strbuf referent = STRBUF_INIT;
- read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag);
- if (!(flag & REF_ISSYMREF))
+ if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string,
+ &referent))
continue;
if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF))
die(_("deleting '%s' failed"), item->string);
+
+ strbuf_release(&referent);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
N_("git replace [-f] <object> <replacement>"),
N_("git replace [-f] --edit <object>"),
N_("git replace [-f] --graft <commit> [<parent>...]"),
- N_("git replace [-f] --convert-graft-file"),
+ "git replace [-f] --convert-graft-file",
N_("git replace -d <object>..."),
N_("git replace [--format=<format>] [-l [<pattern>]]"),
NULL
/*
* Special case: if the pattern is a path inside the cone
* followed by only wildcards, the pattern cannot match
- * partial sparse directories, so we don't expand the index.
+ * partial sparse directories, so we know we don't need to
+ * expand the index.
+ *
+ * Examples:
+ * - in-cone/foo***: doesn't need expanded index
+ * - not-in-cone/bar*: may need expanded index
+ * - **.c: may need expanded index
*/
- if (path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
- strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len)
+ if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
+ path_in_cone_mode_sparse_checkout(item.original, &the_index))
continue;
for (pos = 0; pos < active_nr; pos++) {
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- clear_pathspec(&opt.pathspec);
return 0;
}
#include "packfile.h"
static const char rev_list_usage[] =
-"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+"git rev-list [<options>] <commit-id>... [-- <path>...]\n"
" limiting output:\n"
" --max-count=<n>\n"
" --max-age=<epoch>\n"
if (value && !strcasecmp(value, "if-asked"))
args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
else
- return error("Invalid value for '%s'", k);
+ return error(_("invalid value for '%s'"), k);
}
}
}
parse_revision_opt(&rev, &ctx, options, shortlog_usage);
}
parse_done:
+ revision_opts_finish(&rev);
argc = parse_options_end(&ctx);
if (nongit && argc > 1) {
#include "parse-options.h"
#include "dir.h"
#include "commit-slab.h"
+#include "date.h"
static const char* show_branch_usage[] = {
N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
#include "builtin.h"
+#include "cache.h"
#include "config.h"
#include "dir.h"
#include "parse-options.h"
#include "wt-status.h"
#include "quote.h"
#include "sparse-index.h"
+#include "worktree.h"
static const char *empty_base = "";
}
static char const * const builtin_sparse_checkout_list_usage[] = {
- N_("git sparse-checkout list"),
+ "git sparse-checkout list",
NULL
};
item->string);
}
+ strvec_clear(&s);
+ clear_pathspec(&p);
dir_clear(&dir);
}
fd = hold_lock_file_for_update(&lk, sparse_filename,
LOCK_DIE_ON_ERROR);
+ free(sparse_filename);
result = update_working_directory(pl);
if (result) {
rollback_lock_file(&lk);
- free(sparse_filename);
clear_pattern_list(pl);
update_working_directory(NULL);
return result;
fflush(fp);
commit_lock_file(&lk);
- free(sparse_filename);
clear_pattern_list(pl);
return 0;
static int set_config(enum sparse_checkout_mode mode)
{
- const char *config_path;
-
- if (upgrade_repository_format(1) < 0)
- die(_("unable to upgrade repository format to enable worktreeConfig"));
- if (git_config_set_gently("extensions.worktreeConfig", "true")) {
- error(_("failed to set extensions.worktreeConfig setting"));
+ /* Update to use worktree config, if not already. */
+ if (init_worktree_config(the_repository)) {
+ error(_("failed to initialize worktree config"));
return 1;
}
- config_path = git_path("config.worktree");
- git_config_set_in_file_gently(config_path,
- "core.sparseCheckout",
- mode ? "true" : NULL);
-
- git_config_set_in_file_gently(config_path,
- "core.sparseCheckoutCone",
- mode == MODE_CONE_PATTERNS ? "true" : NULL);
+ if (repo_config_set_worktree_gently(the_repository,
+ "core.sparseCheckout",
+ mode ? "true" : "false") ||
+ repo_config_set_worktree_gently(the_repository,
+ "core.sparseCheckoutCone",
+ mode == MODE_CONE_PATTERNS ?
+ "true" : "false"))
+ return 1;
if (mode == MODE_NO_PATTERNS)
- set_sparse_index_config(the_repository, 0);
+ return set_sparse_index_config(the_repository, 0);
return 0;
}
core_sparse_checkout_cone = 1;
} else {
mode = MODE_ALL_PATTERNS;
+ core_sparse_checkout_cone = 0;
}
if (record_mode && set_config(mode))
return 1;
}
static char const * const builtin_sparse_checkout_init_usage[] = {
- N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
+ "git sparse-checkout init [--cone] [--[no-]sparse-index]",
NULL
};
FILE *fp;
/* assume we are in a fresh repo, but update the sparse-checkout file */
+ if (safe_create_leading_directories(sparse_filename))
+ die(_("unable to create leading directories of %s"),
+ sparse_filename);
fp = xfopen(sparse_filename, "w");
if (!fp)
die(_("failed to open '%s'"), sparse_filename);
return result;
}
+static void sanitize_paths(int argc, const char **argv,
+ const char *prefix, int skip_checks)
+{
+ int i;
+
+ if (!argc)
+ return;
+
+ if (prefix && *prefix && core_sparse_checkout_cone) {
+ /*
+ * The args are not pathspecs, so unfortunately we
+ * cannot imitate how cmd_add() uses parse_pathspec().
+ */
+ int prefix_len = strlen(prefix);
+
+ for (i = 0; i < argc; i++)
+ argv[i] = prefix_path(prefix, prefix_len, argv[i]);
+ }
+
+ if (skip_checks)
+ return;
+
+ if (prefix && *prefix && !core_sparse_checkout_cone)
+ die(_("please run from the toplevel directory in non-cone mode"));
+
+ if (core_sparse_checkout_cone) {
+ for (i = 0; i < argc; i++) {
+ if (argv[i][0] == '/')
+ die(_("specify directories rather than patterns (no leading slash)"));
+ if (argv[i][0] == '!')
+ die(_("specify directories rather than patterns. If your directory starts with a '!', pass --skip-checks"));
+ if (strpbrk(argv[i], "*?[]"))
+ die(_("specify directories rather than patterns. If your directory really has any of '*?[]\\' in it, pass --skip-checks"));
+ }
+ }
+
+ for (i = 0; i < argc; i++) {
+ struct cache_entry *ce;
+ struct index_state *index = the_repository->index;
+ int pos = index_name_pos(index, argv[i], strlen(argv[i]));
+
+ if (pos < 0)
+ continue;
+ ce = index->cache[pos];
+ if (S_ISSPARSEDIR(ce->ce_mode))
+ continue;
+
+ if (core_sparse_checkout_cone)
+ die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), argv[i]);
+ else
+ warning(_("pass a leading slash before paths such as '%s' if you want a single file (see NON-CONE PROBLEMS in the git-sparse-checkout manual)."), argv[i]);
+ }
+}
+
static char const * const builtin_sparse_checkout_add_usage[] = {
- N_("git sparse-checkout add (--stdin | <patterns>)"),
+ N_("git sparse-checkout add [--skip-checks] (--stdin | <patterns>)"),
NULL
};
static struct sparse_checkout_add_opts {
+ int skip_checks;
int use_stdin;
} add_opts;
static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
{
static struct option builtin_sparse_checkout_add_options[] = {
+ OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
+ N_("skip some sanity checks on the given paths that might give false positives"),
+ PARSE_OPT_NONEG),
OPT_BOOL(0, "stdin", &add_opts.use_stdin,
N_("read patterns from standard in")),
OPT_END(),
builtin_sparse_checkout_add_usage,
PARSE_OPT_KEEP_UNKNOWN);
+ sanitize_paths(argc, argv, prefix, add_opts.skip_checks);
+
return modify_pattern_list(argc, argv, add_opts.use_stdin, ADD);
}
static char const * const builtin_sparse_checkout_set_usage[] = {
- N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] (--stdin | <patterns>)"),
+ N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] [--skip-checks] (--stdin | <patterns>)"),
NULL
};
static struct sparse_checkout_set_opts {
int cone_mode;
int sparse_index;
+ int skip_checks;
int use_stdin;
} set_opts;
N_("initialize the sparse-checkout in cone mode")),
OPT_BOOL(0, "sparse-index", &set_opts.sparse_index,
N_("toggle the use of a sparse index")),
+ OPT_BOOL_F(0, "skip-checks", &set_opts.skip_checks,
+ N_("skip some sanity checks on the given paths that might give false positives"),
+ PARSE_OPT_NONEG),
OPT_BOOL_F(0, "stdin", &set_opts.use_stdin,
N_("read patterns from standard in"),
PARSE_OPT_NONEG),
if (!core_sparse_checkout_cone && argc == 0) {
argv = default_patterns;
argc = default_patterns_nr;
+ } else {
+ sanitize_paths(argc, argv, prefix, set_opts.skip_checks);
}
return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE);
}
static char const * const builtin_sparse_checkout_reapply_usage[] = {
- N_("git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]"),
+ "git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]",
NULL
};
if (!core_apply_sparse_checkout)
die(_("must be in a sparse-checkout to reapply sparsity patterns"));
+ reapply_opts.cone_mode = -1;
+ reapply_opts.sparse_index = -1;
+
argc = parse_options(argc, argv, NULL,
builtin_sparse_checkout_reapply_options,
builtin_sparse_checkout_reapply_usage, 0);
repo_read_index(the_repository);
- reapply_opts.cone_mode = -1;
- reapply_opts.sparse_index = -1;
-
if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index))
return 1;
}
static char const * const builtin_sparse_checkout_disable_usage[] = {
- N_("git sparse-checkout disable"),
+ "git sparse-checkout disable",
NULL
};
static int show_stat = 1;
static int show_patch;
static int show_include_untracked;
-static int use_legacy_stash;
static int git_stash_config(const char *var, const char *value, void *cb)
{
show_include_untracked = git_config_bool(var, value);
return 0;
}
- if (!strcmp(var, "stash.usebuiltin")) {
- use_legacy_stash = !git_config_bool(var, value);
- return 0;
- }
return git_diff_basic_config(var, value, cb);
}
branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
if (flags & REF_ISSYMREF)
- branch_name = strrchr(branch_ref, '/') + 1;
+ skip_prefix(branch_ref, "refs/heads/", &branch_name);
head_short_sha1 = find_unique_abbrev(&head_commit->object.oid,
DEFAULT_ABBREV);
strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1);
git_config(git_stash_config, NULL);
- if (use_legacy_stash ||
- !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
- warning(_("the stash.useBuiltin support has been removed!\n"
- "See its entry in 'git help config' for details."));
-
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
else if (!strcmp(argv[0], "save"))
return !!save_stash(argc, argv, prefix);
else if (*argv[0] != '-')
- usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
- git_stash_usage, options);
+ usage_msg_optf(_("unknown subcommand: %s"),
+ git_stash_usage, options, argv[0]);
/* Assume 'stash push' */
strvec_push(&args, "push");
}
static const char * const stripspace_usage[] = {
- N_("git stripspace [-s | --strip-comments]"),
- N_("git stripspace [-c | --comment-lines]"),
+ "git stripspace [-s | --strip-comments]",
+ "git stripspace [-c | --comment-lines]",
NULL
};
#include "diff.h"
#include "object-store.h"
#include "advice.h"
+#include "branch.h"
+#include "list-objects-filter-options.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
const char *name;
const char *url;
const char *depth;
+ struct list_objects_filter_options *filter_options;
struct string_list reference;
unsigned int quiet: 1;
unsigned int progress: 1;
strvec_push(&cp.args, "--dissociate");
if (sm_gitdir && *sm_gitdir)
strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL);
+ if (clone_data->filter_options && clone_data->filter_options->choice)
+ strvec_pushf(&cp.args, "--filter=%s",
+ expand_list_objects_filter_spec(
+ clone_data->filter_options));
if (clone_data->single_branch >= 0)
strvec_push(&cp.args, clone_data->single_branch ?
"--single-branch" :
{
int dissociate = 0, quiet = 0, progress = 0, require_init = 0;
struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
+ struct list_objects_filter_options filter_options;
struct option module_clone_options[] = {
OPT_STRING(0, "prefix", &clone_data.prefix,
OPT_STRING(0, "depth", &clone_data.depth,
N_("string"),
N_("depth for shallow clones")),
- OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
+ OPT__QUIET(&quiet, "suppress output for cloning a submodule"),
OPT_BOOL(0, "progress", &progress,
N_("force cloning progress")),
OPT_BOOL(0, "require-init", &require_init,
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &clone_data.single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
"[--reference <repository>] [--name <name>] [--depth <depth>] "
- "[--single-branch] "
+ "[--single-branch] [--filter <filter-spec>]"
"--url <url> --path <path>"),
NULL
};
+ memset(&filter_options, 0, sizeof(filter_options));
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
clone_data.quiet = !!quiet;
clone_data.progress = !!progress;
clone_data.require_init = !!require_init;
+ clone_data.filter_options = &filter_options;
if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))
usage_with_options(git_submodule_helper_usage,
module_clone_options);
clone_submodule(&clone_data);
+ list_objects_filter_release(&filter_options);
return 0;
}
const char *recursive_prefix;
const char *prefix;
int single_branch;
+ struct list_objects_filter_options *filter_options;
/* to be consumed by git-submodule.sh */
struct update_clone_data *update_clone;
strvec_pushl(&child->args, "--prefix", suc->prefix, NULL);
if (suc->recommend_shallow && sub->recommend_shallow == 1)
strvec_push(&child->args, "--depth=1");
+ if (suc->filter_options && suc->filter_options->choice)
+ strvec_pushf(&child->args, "--filter=%s",
+ expand_list_objects_filter_spec(suc->filter_options));
if (suc->require_init)
strvec_push(&child->args, "--require-init");
strvec_pushl(&child->args, "--path", sub->path, NULL);
const char *update = NULL;
struct pathspec pathspec;
struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+ struct list_objects_filter_options filter_options;
+ int ret;
struct option module_update_clone_options[] = {
OPT_STRING(0, "prefix", &prefix,
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &suc.single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
update_clone_config_from_gitmodules(&suc.max_jobs);
git_config(git_update_clone_config, &suc.max_jobs);
+ memset(&filter_options, 0, sizeof(filter_options));
argc = parse_options(argc, argv, prefix, module_update_clone_options,
git_submodule_helper_usage, 0);
+ suc.filter_options = &filter_options;
if (update)
if (parse_submodule_update_strategy(update, &suc.update) < 0)
die(_("bad value for update parameter"));
- if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+ if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) {
+ list_objects_filter_release(&filter_options);
return 1;
+ }
if (pathspec.nr)
suc.warn_if_uninitialized = 1;
- return update_submodules(&suc);
+ ret = update_submodules(&suc);
+ list_objects_filter_release(&filter_options);
+ return ret;
}
static int run_update_procedure(int argc, const char **argv, const char *prefix)
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper config <name> [<value>]"),
N_("git submodule--helper config --unset <name>"),
- N_("git submodule--helper config --check-writeable"),
+ "git submodule--helper config --check-writeable",
NULL
};
return !!ret;
}
+static int module_create_branch(int argc, const char **argv, const char *prefix)
+{
+ enum branch_track track;
+ int quiet = 0, force = 0, reflog = 0, dry_run = 0;
+
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("print only error messages")),
+ OPT__FORCE(&force, N_("force creation"), 0),
+ OPT_BOOL(0, "create-reflog", &reflog,
+ N_("create the branch's reflog")),
+ OPT_SET_INT('t', "track", &track,
+ N_("set up tracking mode (see git-pull(1))"),
+ BRANCH_TRACK_EXPLICIT),
+ OPT__DRY_RUN(&dry_run,
+ N_("show whether the branch would be created")),
+ OPT_END()
+ };
+ const char *const usage[] = {
+ N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"),
+ NULL
+ };
+
+ git_config(git_default_config, NULL);
+ track = git_branch_track;
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 3)
+ usage_with_options(usage, options);
+
+ if (!quiet && !dry_run)
+ printf_ln(_("creating branch '%s'"), argv[0]);
+
+ create_branches_recursively(the_repository, argv[0], argv[1], argv[2],
+ force, reflog, quiet, track, dry_run);
+ return 0;
+}
struct add_data {
const char *prefix;
const char *branch;
{
int force = 0, quiet = 0, progress = 0, dissociate = 0;
struct add_data add_data = ADD_DATA_INIT;
+ char *to_free = NULL;
struct option options[] = {
OPT_STRING('b', "branch", &add_data.branch, N_("branch"),
"of the working tree"));
/* dereference source url relative to parent's url */
- add_data.realrepo = resolve_relative_url(add_data.repo, NULL, 1);
+ to_free = resolve_relative_url(add_data.repo, NULL, 1);
+ add_data.realrepo = to_free;
} else if (is_dir_sep(add_data.repo[0]) || strchr(add_data.repo, ':')) {
add_data.realrepo = add_data.repo;
} else {
}
configure_added_submodule(&add_data);
free(add_data.sm_path);
+ free(to_free);
return 0;
}
{"config", module_config, 0},
{"set-url", module_set_url, 0},
{"set-branch", module_set_branch, 0},
+ {"create-branch", module_create_branch, 0},
};
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
#include "oid-array.h"
#include "column.h"
#include "ref-filter.h"
+#include "date.h"
static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"
error("%s: not in %s branch.", path, which);
return NULL;
}
- if (mode == S_IFDIR) {
+ if (!the_index.sparse_index && mode == S_IFDIR) {
if (which)
error("%s: not a blob in %s branch.", path, which);
return NULL;
*/
has_head = 0;
redo:
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++) {
const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
discard_cache_entry(old);
continue; /* unchanged */
}
+
+ /* At this point, we know the contents of the sparse directory are
+ * modified with respect to HEAD, so we expand the index and restart
+ * to process each path individually
+ */
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ ensure_full_index(&the_index);
+ goto redo;
+ }
+
/* Be careful. The working tree may not have the
* path anymore, in which case, under 'allow_remove',
* or worse yet 'allow_replace', active_nr may decrease.
setup_work_tree();
read_cache();
*o->has_errors |= refresh_cache(o->flags | flag);
+ if (has_racy_timestamp(&the_index)) {
+ /*
+ * Even if nothing else has changed, updating the file
+ * increases the chance that racy timestamps become
+ * non-racy, helping future run-time performance.
+ * We do that even in case of "errors" returned by
+ * refresh_cache() as these are no actual errors.
+ * cmd_status() does the same.
+ */
+ active_cache_changed |= SOMETHING_CHANGED;
+ }
return 0;
}
git_config(git_default_config, NULL);
+ prepare_repo_settings(r);
+ the_repository->settings.command_requires_full_index = 0;
+
/* we will diagnose later if it turns out that we need to update it */
newfd = hold_locked_index(&lock_file, 0);
if (newfd < 0)
#include "parse-options.h"
static const char * const update_server_info_usage[] = {
- N_("git update-server-info [--force]"),
+ "git update-server-info [--force]",
NULL
};
die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), path, cmd);
}
+static void copy_sparse_checkout(const char *worktree_git_dir)
+{
+ char *from_file = git_pathdup("info/sparse-checkout");
+ char *to_file = xstrfmt("%s/info/sparse-checkout", worktree_git_dir);
+
+ if (file_exists(from_file)) {
+ if (safe_create_leading_directories(to_file) ||
+ copy_file(to_file, from_file, 0666))
+ error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"),
+ from_file, to_file);
+ }
+
+ free(from_file);
+ free(to_file);
+}
+
+static void copy_filtered_worktree_config(const char *worktree_git_dir)
+{
+ char *from_file = git_pathdup("config.worktree");
+ char *to_file = xstrfmt("%s/config.worktree", worktree_git_dir);
+
+ if (file_exists(from_file)) {
+ struct config_set cs = { { 0 } };
+ const char *core_worktree;
+ int bare;
+
+ if (safe_create_leading_directories(to_file) ||
+ copy_file(to_file, from_file, 0666)) {
+ error(_("failed to copy worktree config from '%s' to '%s'"),
+ from_file, to_file);
+ goto worktree_copy_cleanup;
+ }
+
+ git_configset_init(&cs);
+ git_configset_add_file(&cs, from_file);
+
+ if (!git_configset_get_bool(&cs, "core.bare", &bare) &&
+ bare &&
+ git_config_set_multivar_in_file_gently(
+ to_file, "core.bare", NULL, "true", 0))
+ error(_("failed to unset '%s' in '%s'"),
+ "core.bare", to_file);
+ if (!git_configset_get_value(&cs, "core.worktree", &core_worktree) &&
+ git_config_set_in_file_gently(to_file,
+ "core.worktree", NULL))
+ error(_("failed to unset '%s' in '%s'"),
+ "core.worktree", to_file);
+
+ git_configset_clear(&cs);
+ }
+
+worktree_copy_cleanup:
+ free(from_file);
+ free(to_file);
+}
+
+static int checkout_worktree(const struct add_opts *opts,
+ struct strvec *child_env)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ cp.git_cmd = 1;
+ strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL);
+ if (opts->quiet)
+ strvec_push(&cp.args, "--quiet");
+ strvec_pushv(&cp.env_array, child_env->v);
+ return run_command(&cp);
+}
+
static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
+ /*
+ * If the current worktree has sparse-checkout enabled, then copy
+ * the sparse-checkout patterns from the current worktree.
+ */
+ if (core_apply_sparse_checkout)
+ copy_sparse_checkout(sb_repo.buf);
+
+ /*
+ * If we are using worktree config, then copy all current config
+ * values from the current worktree into the new one, that way the
+ * new worktree behaves the same as this one.
+ */
+ if (repository_format_worktree_config)
+ copy_filtered_worktree_config(sb_repo.buf);
+
strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
cp.git_cmd = 1;
if (ret)
goto done;
- if (opts->checkout) {
- struct child_process cp = CHILD_PROCESS_INIT;
- cp.git_cmd = 1;
- strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL);
- if (opts->quiet)
- strvec_push(&cp.args, "--quiet");
- strvec_pushv(&cp.env_array, child_env.v);
- ret = run_command(&cp);
- if (ret)
- goto done;
- }
+ if (opts->checkout &&
+ (ret = checkout_worktree(opts, &child_env)))
+ goto done;
is_junk = 0;
FREE_AND_NULL(junk_work_tree);
* is_junk is cleared, but do return appropriate code when hook fails.
*/
if (!ret && opts->checkout) {
- const char *hook = find_hook("post-checkout");
- if (hook) {
- const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
- struct child_process cp = CHILD_PROCESS_INIT;
- cp.no_stdin = 1;
- cp.stdout_to_stderr = 1;
- cp.dir = path;
- strvec_pushv(&cp.env_array, env);
- cp.trace2_hook_name = "post-checkout";
- strvec_pushl(&cp.args, absolute_path(hook),
- oid_to_hex(null_oid()),
- oid_to_hex(&commit->object.oid),
- "1", NULL);
- ret = run_command(&cp);
- }
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+ strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+ strvec_pushl(&opt.args,
+ oid_to_hex(null_oid()),
+ oid_to_hex(&commit->object.oid),
+ "1",
+ NULL);
+ opt.dir = path;
+
+ ret = run_hooks_opt("post-checkout", &opt);
}
strvec_clear(&child_env);
#include "repository.h"
#include "mem-pool.h"
-#include <zlib.h>
typedef struct git_zstream {
z_stream z;
unsigned long avail_in;
#define CE_MATCH_IGNORE_FSMONITOR 0X20
int is_racy_timestamp(const struct index_state *istate,
const struct cache_entry *ce);
+int has_racy_timestamp(struct index_state *istate);
int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
extern int core_apply_sparse_checkout;
extern int core_sparse_checkout_cone;
+extern int sparse_expect_files_outside_of_patterns;
/*
* Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value).
#define GET_OID_FOLLOW_SYMLINKS 0100
#define GET_OID_RECORD_PATH 0200
#define GET_OID_ONLY_TO_DIE 04000
+#define GET_OID_REQUIRE_PATH 010000
#define GET_OID_DISAMBIGUATORS \
(GET_OID_COMMIT | GET_OID_COMMITTISH | \
#define peel_to_type(name, namelen, obj, type) \
repo_peel_to_type(the_repository, name, namelen, obj, type)
-enum date_mode_type {
- DATE_NORMAL = 0,
- DATE_HUMAN,
- DATE_RELATIVE,
- DATE_SHORT,
- DATE_ISO8601,
- DATE_ISO8601_STRICT,
- DATE_RFC2822,
- DATE_STRFTIME,
- DATE_RAW,
- DATE_UNIX
-};
-
-struct date_mode {
- enum date_mode_type type;
- const char *strftime_fmt;
- int local;
-};
-
-/*
- * Convenience helper for passing a constant type, like:
- *
- * show_date(t, tz, DATE_MODE(NORMAL));
- */
-#define DATE_MODE(t) date_mode_from_type(DATE_##t)
-struct date_mode *date_mode_from_type(enum date_mode_type type);
-
-const char *show_date(timestamp_t time, int timezone, const struct date_mode *mode);
-void show_date_relative(timestamp_t time, struct strbuf *timebuf);
-void show_date_human(timestamp_t time, int tz, const struct timeval *now,
- struct strbuf *timebuf);
-int parse_date(const char *date, struct strbuf *out);
-int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
-int parse_expiry_date(const char *date, timestamp_t *timestamp);
-void datestamp(struct strbuf *out);
-#define approxidate(s) approxidate_careful((s), NULL)
-timestamp_t approxidate_careful(const char *, int *);
-timestamp_t approxidate_relative(const char *date);
-void parse_date_format(const char *format, struct date_mode *mode);
-int date_overflows(timestamp_t date);
-time_t tm_to_time_t(const struct tm *tm);
-
#define IDENT_STRICT 1
#define IDENT_NO_DATE 2
#define IDENT_NO_NAME 4
*/
int split_ident_line(struct ident_split *, const char *, int);
-/*
- * Like show_date, but pull the timestamp and tz parameters from
- * the ident_split. It will also sanity-check the values and produce
- * a well-known sentinel date if they appear bogus.
- */
-const char *show_ident_date(const struct ident_split *id,
- const struct date_mode *mode);
-
/*
* Compare split idents for equality or strict ordering. Note that we
* compare only the ident part of the line, ignoring any timestamp.
case "$jobname" in
linux32)
CC=gcc
- MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1"
;;
linux-musl)
CC=gcc
git-gui mainporcelain
git-hash-object plumbingmanipulators
git-help ancillaryinterrogators complete
+git-hook purehelpers
git-http-backend synchingrepositories
git-http-fetch synchelpers
git-http-push synchelpers
}
static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
- struct string_list *pack_indexes)
+ const struct string_list *pack_indexes)
{
uint32_t i;
struct strbuf progress_title = STRBUF_INIT;
struct strbuf packname = STRBUF_INIT;
int dirlen;
+ int ret = 0;
strbuf_addf(&packname, "%s/pack/", ctx->odb->path);
dirlen = packname.len;
strbuf_addstr(&packname, pack_indexes->items[i].string);
p = add_packed_git(packname.buf, packname.len, 1);
if (!p) {
- error(_("error adding pack %s"), packname.buf);
- return -1;
+ ret = error(_("error adding pack %s"), packname.buf);
+ goto cleanup;
}
if (open_pack_index(p)) {
- error(_("error opening index for %s"), packname.buf);
- return -1;
+ ret = error(_("error opening index for %s"), packname.buf);
+ goto cleanup;
}
for_each_object_in_pack(p, add_packed_commits, ctx,
FOR_EACH_OBJECT_PACK_ORDER);
free(p);
}
+cleanup:
stop_progress(&ctx->progress);
strbuf_release(&progress_title);
strbuf_release(&packname);
- return 0;
+ return ret;
}
static int fill_oids_from_commits(struct write_commit_graph_context *ctx,
hold_lock_file_for_update_mode(&lk, lock_name,
LOCK_DIE_ON_ERROR, 0444);
+ free(lock_name);
fd = git_mkstemp_mode(ctx->graph_name, 0444);
if (fd < 0) {
} else {
char *graph_name = get_commit_graph_filename(ctx->odb);
unlink(graph_name);
+ free(graph_name);
}
ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(hash_to_hex(file_hash));
}
int write_commit_graph(struct object_directory *odb,
- struct string_list *pack_indexes,
+ const struct string_list *const pack_indexes,
struct oidset *commits,
enum commit_graph_write_flags flags,
const struct commit_graph_opts *opts)
enum commit_graph_write_flags flags,
const struct commit_graph_opts *opts);
int write_commit_graph(struct object_directory *odb,
- struct string_list *pack_indexes,
+ const struct string_list *pack_indexes,
struct oidset *commits,
enum commit_graph_write_flags flags,
const struct commit_graph_opts *opts);
#include "commit-reach.h"
#include "run-command.h"
#include "shallow.h"
+#include "hook.h"
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
return &new_commit->next;
}
-const char *find_commit_header(const char *msg, const char *key, size_t *out_len)
+const char *find_header_mem(const char *msg, size_t len,
+ const char *key, size_t *out_len)
{
int key_len = strlen(key);
const char *line = msg;
- while (line) {
+ /*
+ * NEEDSWORK: It's possible for strchrnul() to scan beyond the range
+ * given by len. However, current callers are safe because they compute
+ * len by scanning a NUL-terminated block of memory starting at msg.
+ * Nonetheless, it would be better to ensure the function does not look
+ * at msg beyond the len provided by the caller.
+ */
+ while (line && line < msg + len) {
const char *eol = strchrnul(line, '\n');
if (line == eol)
return NULL;
}
+const char *find_commit_header(const char *msg, const char *key, size_t *out_len)
+{
+ return find_header_mem(msg, strlen(msg), key, out_len);
+}
/*
* Inspect the given string and determine the true "end" of the log message, in
* order to find where to put a new Signed-off-by trailer. Ignored are
int run_commit_hook(int editor_is_used, const char *index_file,
const char *name, ...)
{
- struct strvec hook_env = STRVEC_INIT;
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
va_list args;
- int ret;
+ const char *arg;
- strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+ strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
/*
* Let the hook know that no editor will be launched.
*/
if (!editor_is_used)
- strvec_push(&hook_env, "GIT_EDITOR=:");
+ strvec_push(&opt.env, "GIT_EDITOR=:");
va_start(args, name);
- ret = run_hook_ve(hook_env.v, name, args);
+ while ((arg = va_arg(args, const char *)))
+ strvec_push(&opt.args, arg);
va_end(args);
- strvec_clear(&hook_env);
- return ret;
+ return run_hooks_opt(name, &opt);
}
/*
* Search the commit object contents given by "msg" for the header "key".
+ * Reads up to "len" bytes of "msg".
* Returns a pointer to the start of the header contents, or NULL. The length
* of the header, up to the first newline, is returned via out_len.
*
* Note that some headers (like mergetag) may be multi-line. It is the caller's
* responsibility to parse further in this case!
*/
+const char *find_header_mem(const char *msg, size_t len,
+ const char *key,
+ size_t *out_len);
+
const char *find_commit_header(const char *msg, const char *key,
size_t *out_len);
int (*cmp)(const void *, const void *, void *), void *ctx)
{
const size_t size = st_mult(n, s);
- char buf[1024];
+ char *tmp;
if (!n)
return 0;
if (!b || !cmp)
return -1;
- if (size < sizeof(buf)) {
- /* The temporary array fits on the small on-stack buffer. */
- msort_with_tmp(b, n, s, cmp, buf, ctx);
- } else {
- /* It's somewhat large, so malloc it. */
- char *tmp = xmalloc(size);
- msort_with_tmp(b, n, s, cmp, tmp, ctx);
- free(tmp);
- }
+ tmp = xmalloc(size);
+ msort_with_tmp(b, n, s, cmp, tmp, ctx);
+ free(tmp);
return 0;
}
*/
#undef NOGDI
+
+/*
+ * Including the appropriate header file for RtlGenRandom causes MSVC to see a
+ * redefinition of types in an incompatible way when including headers below.
+ */
+#undef HAVE_RTLGENRANDOM
#include "../git-compat-util.h"
#include <wingdi.h>
#include <winreg.h>
+#include "git-compat-util.h"
+
+#if ZLIB_VERNUM < 0x1290
/* taken from zlib's uncompr.c
commit cacf7f1d4e3d44d871b605da3b647f07d718623f
*/
-#include "../reftable/system.h"
-#define z_const
-
/*
* Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
-#include <zlib.h>
-
/* clang-format off */
/* ===========================================================================
err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
err;
}
+#else
+static void *dummy_variable = &dummy_variable;
+#endif
*
*/
#include "cache.h"
+#include "date.h"
#include "branch.h"
#include "config.h"
#include "environment.h"
#include "dir.h"
#include "color.h"
#include "refs.h"
+#include "worktree.h"
struct config_source {
struct config_source *prev;
return conf->u.buf.pos;
}
+struct config_include_data {
+ int depth;
+ config_fn_t fn;
+ void *data;
+ const struct config_options *opts;
+ struct git_config_source *config_source;
+
+ /*
+ * All remote URLs discovered when reading all config files.
+ */
+ struct string_list *remote_urls;
+};
+#define CONFIG_INCLUDE_INIT { 0 }
+
+static int git_config_include(const char *var, const char *value, void *data);
+
#define MAX_INCLUDE_DEPTH 10
static const char include_depth_advice[] = N_(
"exceeded maximum include depth (%d) while including\n"
return ret;
}
-static int include_condition_is_true(const struct config_options *opts,
+static int add_remote_url(const char *var, const char *value, void *data)
+{
+ struct string_list *remote_urls = data;
+ const char *remote_name;
+ size_t remote_name_len;
+ const char *key;
+
+ if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
+ &key) &&
+ remote_name &&
+ !strcmp(key, "url"))
+ string_list_append(remote_urls, value);
+ return 0;
+}
+
+static void populate_remote_urls(struct config_include_data *inc)
+{
+ struct config_options opts;
+
+ struct config_source *store_cf = cf;
+ struct key_value_info *store_kvi = current_config_kvi;
+ enum config_scope store_scope = current_parsing_scope;
+
+ opts = *inc->opts;
+ opts.unconditional_remote_url = 1;
+
+ cf = NULL;
+ current_config_kvi = NULL;
+ current_parsing_scope = 0;
+
+ inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
+ string_list_init_dup(inc->remote_urls);
+ config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
+
+ cf = store_cf;
+ current_config_kvi = store_kvi;
+ current_parsing_scope = store_scope;
+}
+
+static int forbid_remote_url(const char *var, const char *value, void *data)
+{
+ const char *remote_name;
+ size_t remote_name_len;
+ const char *key;
+
+ if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
+ &key) &&
+ remote_name &&
+ !strcmp(key, "url"))
+ die(_("remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url"));
+ return 0;
+}
+
+static int at_least_one_url_matches_glob(const char *glob, int glob_len,
+ struct string_list *remote_urls)
+{
+ struct strbuf pattern = STRBUF_INIT;
+ struct string_list_item *url_item;
+ int found = 0;
+
+ strbuf_add(&pattern, glob, glob_len);
+ for_each_string_list_item(url_item, remote_urls) {
+ if (!wildmatch(pattern.buf, url_item->string, WM_PATHNAME)) {
+ found = 1;
+ break;
+ }
+ }
+ strbuf_release(&pattern);
+ return found;
+}
+
+static int include_by_remote_url(struct config_include_data *inc,
+ const char *cond, size_t cond_len)
+{
+ if (inc->opts->unconditional_remote_url)
+ return 1;
+ if (!inc->remote_urls)
+ populate_remote_urls(inc);
+ return at_least_one_url_matches_glob(cond, cond_len,
+ inc->remote_urls);
+}
+
+static int include_condition_is_true(struct config_include_data *inc,
const char *cond, size_t cond_len)
{
+ const struct config_options *opts = inc->opts;
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
return include_by_gitdir(opts, cond, cond_len, 0);
return include_by_gitdir(opts, cond, cond_len, 1);
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
return include_by_branch(cond, cond_len);
+ else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
+ &cond_len))
+ return include_by_remote_url(inc, cond, cond_len);
/* unknown conditionals are always false */
return 0;
}
-int git_config_include(const char *var, const char *value, void *data)
+static int git_config_include(const char *var, const char *value, void *data)
{
struct config_include_data *inc = data;
const char *cond, *key;
ret = handle_path_include(value, inc);
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
- (cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
- !strcmp(key, "path"))
+ cond && include_condition_is_true(inc, cond, cond_len) &&
+ !strcmp(key, "path")) {
+ config_fn_t old_fn = inc->fn;
+
+ if (inc->opts->unconditional_remote_url)
+ inc->fn = forbid_remote_url;
ret = handle_path_include(value, inc);
+ inc->fn = old_fn;
+ }
return ret;
}
return platform_core_config(var, value, cb);
}
+static int git_default_sparse_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "sparse.expectfilesoutsideofpatterns")) {
+ sparse_expect_files_outside_of_patterns = git_config_bool(var, value);
+ return 0;
+ }
+
+ /* Add other config variables here and to Documentation/config/sparse.txt. */
+ return 0;
+}
+
static int git_default_i18n_config(const char *var, const char *value)
{
if (!strcmp(var, "i18n.commitencoding"))
return 0;
}
+ if (starts_with(var, "sparse."))
+ return git_default_sparse_config(var, value);
+
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
const struct config_options *opts)
{
struct config_include_data inc = CONFIG_INCLUDE_INIT;
+ int ret;
if (opts->respect_includes) {
inc.fn = fn;
inc.data = data;
inc.opts = opts;
+ inc.config_source = config_source;
fn = git_config_include;
data = &inc;
}
* regular lookup sequence.
*/
if (config_source && config_source->use_stdin) {
- return git_config_from_stdin(fn, data);
+ ret = git_config_from_stdin(fn, data);
} else if (config_source && config_source->file) {
- return git_config_from_file(fn, config_source->file, data);
+ ret = git_config_from_file(fn, config_source->file, data);
} else if (config_source && config_source->blob) {
struct repository *repo = config_source->repo ?
config_source->repo : the_repository;
- return git_config_from_blob_ref(fn, repo, config_source->blob,
+ ret = git_config_from_blob_ref(fn, repo, config_source->blob,
data);
+ } else {
+ ret = do_git_config_sequence(opts, fn, data);
}
- return do_git_config_sequence(opts, fn, data);
+ if (inc.remote_urls) {
+ string_list_clear(inc.remote_urls, 0);
+ FREE_AND_NULL(inc.remote_urls);
+ }
+ return ret;
}
static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
return 1;
}
-int git_configset_get_string_tmp(struct config_set *cs, const char *key,
- const char **dest)
+static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
+ const char **dest)
{
const char *value;
if (!git_configset_get_value(cs, key, &value)) {
return git_config_set_multivar_gently(key, value, NULL, 0);
}
+int repo_config_set_worktree_gently(struct repository *r,
+ const char *key, const char *value)
+{
+ /* Only use worktree-specific config if it is is already enabled. */
+ if (repository_format_worktree_config) {
+ char *file = repo_git_path(r, "config.worktree");
+ int ret = git_config_set_multivar_in_file_gently(
+ file, key, value, NULL, 0);
+ free(file);
+ return ret;
+ }
+ return repo_config_set_multivar_gently(r, key, value, NULL, 0);
+}
+
void git_config_set(const char *key, const char *value)
{
git_config_set_multivar(key, value, NULL, 0);
int git_config_set_multivar_gently(const char *key, const char *value,
const char *value_pattern, unsigned flags)
{
- return git_config_set_multivar_in_file_gently(NULL, key, value, value_pattern,
- flags);
+ return repo_config_set_multivar_gently(the_repository, key, value,
+ value_pattern, flags);
+}
+
+int repo_config_set_multivar_gently(struct repository *r, const char *key,
+ const char *value,
+ const char *value_pattern, unsigned flags)
+{
+ char *file = repo_git_path(r, "config");
+ int res = git_config_set_multivar_in_file_gently(file,
+ key, value,
+ value_pattern,
+ flags);
+ free(file);
+ return res;
}
void git_config_set_multivar(const char *key, const char *value,
const char *value_pattern, unsigned flags)
{
- git_config_set_multivar_in_file(NULL, key, value, value_pattern,
+ git_config_set_multivar_in_file(git_path("config"),
+ key, value, value_pattern,
flags);
}
unsigned int ignore_worktree : 1;
unsigned int ignore_cmdline : 1;
unsigned int system_gently : 1;
+
+ /*
+ * For internal use. Include all includeif.hasremoteurl paths without
+ * checking if the repo has that remote URL, and when doing so, verify
+ * that files included in this way do not configure any remote URLs
+ * themselves.
+ */
+ unsigned int unconditional_remote_url : 1;
+
const char *commondir;
const char *git_dir;
config_parser_event_fn_t event_fn;
/**
* Read a specific file in git-config format.
* This function takes the same callback and data parameters as `git_config`.
+ *
+ * Unlike git_config(), this function does not respect includes.
*/
int git_config_from_file(config_fn_t fn, const char *, void *);
* will first feed the user-wide one to the callback, and then the
* repo-specific one; by overwriting, the higher-priority repo-specific
* value is left at the end).
+ *
+ * Unlike git_config_from_file(), this function respects includes.
*/
void git_config(config_fn_t fn, void *);
int git_config_set_gently(const char *, const char *);
+/**
+ * Write a config value that should apply to the current worktree. If
+ * extensions.worktreeConfig is enabled, then the write will happen in the
+ * current worktree's config. Otherwise, write to the common config file.
+ */
+int repo_config_set_worktree_gently(struct repository *, const char *, const char *);
+
/**
* write config values to `.git/config`, takes a key/value pair as parameter.
*/
int git_config_set_multivar_gently(const char *, const char *, const char *, unsigned);
void git_config_set_multivar(const char *, const char *, const char *, unsigned);
+int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned);
int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, unsigned);
/**
const char *current_config_name(void);
int current_config_line(void);
-/**
- * Include Directives
- * ------------------
- *
- * By default, the config parser does not respect include directives.
- * However, a caller can use the special `git_config_include` wrapper
- * callback to support them. To do so, you simply wrap your "real" callback
- * function and data pointer in a `struct config_include_data`, and pass
- * the wrapper to the regular config-reading functions. For example:
- *
- * -------------------------------------------
- * int read_file_with_include(const char *file, config_fn_t fn, void *data)
- * {
- * struct config_include_data inc = CONFIG_INCLUDE_INIT;
- * inc.fn = fn;
- * inc.data = data;
- * return git_config_from_file(git_config_include, file, &inc);
- * }
- * -------------------------------------------
- *
- * `git_config` respects includes automatically. The lower-level
- * `git_config_from_file` does not.
- *
- */
-struct config_include_data {
- int depth;
- config_fn_t fn;
- void *data;
- const struct config_options *opts;
-};
-#define CONFIG_INCLUDE_INIT { 0 }
-int git_config_include(const char *name, const char *value, void *data);
-
/*
* Match and parse a config key of the form:
*
int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);
int git_configset_get_string(struct config_set *cs, const char *key, char **dest);
-int git_configset_get_string_tmp(struct config_set *cs, const char *key, const char **dest);
int git_configset_get_int(struct config_set *cs, const char *key, int *dest);
int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);
int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);
# centos7/rhel7 provides gcc 4.8.5 and zlib 1.2.7.
ifneq ($(findstring .el7.,$(uname_R)),)
BASIC_CFLAGS += -std=c99
- NO_UNCOMPRESS2 = YesPlease
endif
endif
ifeq ($(uname_S),GNU/kFreeBSD)
HAVE_BSD_SYSCTL = YesPlease
FREAD_READS_DIRECTORIES = UnfortunatelyYes
HAVE_NS_GET_EXECUTABLE_PATH = YesPlease
+ CSPRNG_METHOD = arc4random
# Workaround for `gettext` being keg-only and not even being linked via
# `brew link --force gettext`, should be obsolete as of
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
PAGER_ENV = LESS=FRX LV=-c MORE=FRX
FREAD_READS_DIRECTORIES = UnfortunatelyYes
FILENO_IS_A_MACRO = UnfortunatelyYes
endif
ifeq ($(uname_S),OpenBSD)
- # Versions < 7.0 need compatibility layer
- ifeq ($(shell expr "$(uname_R)" : "[1-6]\."),2)
- NO_UNCOMPRESS2 = UnfortunatelyYes
- endif
NO_STRCASESTR = YesPlease
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
PROCFS_EXECUTABLE_PATH = /proc/curproc/file
FREAD_READS_DIRECTORIES = UnfortunatelyYes
FILENO_IS_A_MACRO = UnfortunatelyYes
NEEDS_LIBICONV = YesPlease
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
endif
ifeq ($(uname_S),NetBSD)
ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
PROCFS_EXECUTABLE_PATH = /proc/curproc/exe
endif
ifeq ($(uname_S),AIX)
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_INTTYPES_H = YesPlease
+ CSPRNG_METHOD = rtlgenrandom
# VS2015 with UCRT claims that snprintf and friends are C99 compliant,
# so we don't need this:
#
endif
endif
ifeq ($(uname_S),Minix)
- NO_UNCOMPRESS2 = YesPlease
NO_IPV6 = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
NO_SETENV = YesPlease
NO_UNSETENV = YesPlease
NO_MKDTEMP = YesPlease
- NO_UNCOMPRESS2 = YesPlease
# Currently libiconv-1.9.1.
OLD_ICONV = UnfortunatelyYes
NO_REGEX = NeedsStartEnd
NO_MMAP = YesPlease
NO_POLL = YesPlease
NO_INTPTR_T = UnfortunatelyYes
+ CSPRNG_METHOD = openssl
SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
SHELL_PATH = /usr/coreutils/bin/bash
endif
NO_POSIX_GOODIES = UnfortunatelyYes
DEFAULT_HELP_FORMAT = html
HAVE_PLATFORM_PROCINFO = YesPlease
+ CSPRNG_METHOD = rtlgenrandom
BASIC_LDFLAGS += -municode
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
git diff-index --cached --quiet HEAD --
# Make .vcxproj files and add them
- unset QUIET_GEN QUIET_BUILT_IN; \
perl contrib/buildsystems/generate -g Vcxproj
git add -f git.sln {*,*/lib,t/helper/*}/*.vcxproj
NO_DEFLATE_BOUND=yes])
LIBS="$old_LIBS"
-AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [
-AC_LANG_PROGRAM([#include <zlib.h>],
- [uncompress2(NULL,NULL,NULL,NULL);])])
-AC_MSG_CHECKING([for uncompress2 in -lz])
-old_LIBS="$LIBS"
-LIBS="$LIBS -lz"
-AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],
- [AC_MSG_RESULT([yes])],
- [AC_MSG_RESULT([no])
- NO_UNCOMPRESS2=yes])
-LIBS="$old_LIBS"
-
GIT_UNSTASH_FLAGS($ZLIB_PATH)
GIT_CONF_SUBST([NO_DEFLATE_BOUND])
-GIT_CONF_SUBST([NO_UNCOMPRESS2])
#
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
static int process_ref_v2(struct packet_reader *reader, struct ref ***list,
- char **unborn_head_target)
+ const char **unborn_head_target)
{
int ret = 1;
int i = 0;
const char *hash_name;
struct strvec *ref_prefixes = transport_options ?
&transport_options->ref_prefixes : NULL;
- char **unborn_head_target = transport_options ?
+ const char **unborn_head_target = transport_options ?
&transport_options->unborn_head_target : NULL;
*list = NULL;
_CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT
NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0
USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP
- UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET)
+ UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM)
list(APPEND compat_SOURCES compat/mingw.c compat/winansi.c compat/win32/path-utils.c
compat/win32/pthread.c compat/win32mmap.c compat/win32/syslog.c
compat/win32/trace2_win32_process_info.c compat/win32/dirent.c
# and git-switch completion (e.g., completing "foo" when "origin/foo"
# exists).
#
+# GIT_COMPLETION_SHOW_ALL_COMMANDS
+#
+# When set to "1" suggest all commands, including plumbing commands
+# which are hidden by default (e.g. "cat-file" on "git ca<TAB>").
+#
# GIT_COMPLETION_SHOW_ALL
#
# When set to "1" suggest all options, including options which are
__git_complete_revlist
}
+__gitcomp_directories ()
+{
+ local _tmp_dir _tmp_completions _found=0
+
+ # Get the directory of the current token; this differs from dirname
+ # in that it keeps up to the final trailing slash. If no slash found
+ # that's fine too.
+ [[ "$cur" =~ .*/ ]]
+ _tmp_dir=$BASH_REMATCH
+
+ # Find possible directory completions, adding trailing '/' characters,
+ # de-quoting, and handling unusual characters.
+ while IFS= read -r -d $'\0' c ; do
+ # If there are directory completions, find ones that start
+ # with "$cur", the current token, and put those in COMPREPLY
+ if [[ $c == "$cur"* ]]; then
+ COMPREPLY+=("$c/")
+ _found=1
+ fi
+ done < <(git ls-tree -z -d --name-only HEAD $_tmp_dir)
+
+ if [[ $_found == 0 ]] && [[ "$cur" =~ /$ ]]; then
+ # No possible further completions any deeper, so assume we're at
+ # a leaf directory and just consider it complete
+ __gitcomp_direct_append "$cur "
+ fi
+}
+
_git_sparse_checkout ()
{
- local subcommands="list init set disable"
+ local subcommands="list init set disable add reapply"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
fi
case "$subcommand,$cur" in
- init,--*)
- __gitcomp "--cone"
- ;;
- set,--*)
- __gitcomp "--stdin"
- ;;
- *)
+ *,--*)
+ __gitcomp_builtin sparse-checkout_$subcommand "" "--"
;;
+ set,*|add,*)
+ if [ "$(__git config core.sparseCheckoutCone)" == "true" ] ||
+ [ -n "$(__git_find_on_cmdline --cone)" ]; then
+ __gitcomp_directories
+ fi
esac
}
then
__gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
else
- __gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
+ local list_cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config
+
+ if test "${GIT_COMPLETION_SHOW_ALL_COMMANDS-}" = "1"
+ then
+ list_cmds=builtins,$list_cmds
+ fi
+ __gitcomp "$(__git --list-cmds=$list_cmds)"
fi
;;
esac
fi
if test -s "$GIT_DIR/MERGE_RR"
then
- git show -s --pretty=format:"Learning from %h %s" "$commit"
+ git --no-pager show -s --format="Learning from %h %s" "$commit"
git rerere
git checkout -q $commit -- .
git rerere
-QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
-QUIET_SUBDIR1 =
-
-ifneq ($(findstring s,$(MAKEFLAGS)),s)
-ifndef V
- QUIET_GEN = @echo ' ' GEN $@;
- QUIET_SUBDIR0 = +@subdir=
- QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
- $(MAKE) $(PRINT_DIR) -C $$subdir
-else
- export V
-endif
-endif
-
-all:
+# The default target of this Makefile is...
+all::
+
+# Import tree-wide shared Makefile behavior and libraries
+include ../../shared.mak
include ../../config.mak.uname
-include ../../config.mak.autogen
struct strbuf scalar_usage = STRBUF_INIT;
int i;
+ while (argc > 1 && *argv[1] == '-') {
+ if (!strcmp(argv[1], "-C")) {
+ if (argc < 3)
+ die(_("-C requires a <directory>"));
+ if (chdir(argv[2]) < 0)
+ die_errno(_("could not change to '%s'"),
+ argv[2]);
+ argc -= 2;
+ argv += 2;
+ } else if (!strcmp(argv[1], "-c")) {
+ if (argc < 3)
+ die(_("-c requires a <key>=<value> argument"));
+ git_config_push_parameter(argv[2]);
+ argc -= 2;
+ argv += 2;
+ } else
+ break;
+ }
+
if (argc > 1) {
argv++;
argc--;
}
strbuf_addstr(&scalar_usage,
- N_("scalar <command> [<options>]\n\nCommands:\n"));
+ N_("scalar [-C <directory>] [-c <key>=<value>] "
+ "<command> [<options>]\n\nCommands:\n"));
for (i = 0; builtins[i].name; i++)
strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
depending on the subcommand. With the exception of `clone`, `list` and
`reconfigure --all`, all subcommands expect to be run in an enlistment.
+The following options can be specified _before_ the subcommand:
+
+-C <directory>::
+ Before running the subcommand, change the working directory. This
+ option imitates the same option of linkgit:git[1].
+
+-c <key>=<value>::
+ For the duration of running the specified subcommand, configure this
+ setting. This option imitates the same option of linkgit:git[1].
+
COMMANDS
--------
+# Import tree-wide shared Makefile behavior and libraries
+include ../../../shared.mak
+
# Run scalar tests
#
# Copyright (c) 2005,2021 Junio C Hamano, Johannes Schindelin
test_path_is_missing cloned
'
+test_expect_success 'scalar supports -c/-C' '
+ test_when_finished "scalar delete sub" &&
+ git init sub &&
+ scalar -C sub -c status.aheadBehind=bogus register &&
+ test -z "$(git -C sub config --local status.aheadBehind)" &&
+ test true = "$(git -C sub config core.preloadIndex)"
+'
+
test_done
if test -n "$arg_addmerge_message"
then
- git merge -Xsubtree="$arg_prefix" \
+ git merge --no-ff -Xsubtree="$arg_prefix" \
--message="$arg_addmerge_message" "$rev"
else
- git merge -Xsubtree="$arg_prefix" $rev
+ git merge --no-ff -Xsubtree="$arg_prefix" $rev
fi
}
}
static struct stream_filter_vtbl null_vtbl = {
- null_filter_fn,
- null_free_fn,
+ .filter = null_filter_fn,
+ .free = null_free_fn,
};
static struct stream_filter null_filter_singleton = {
- &null_vtbl,
+ .vtbl = &null_vtbl,
};
int is_null_stream_filter(struct stream_filter *filter)
}
static struct stream_filter_vtbl lf_to_crlf_vtbl = {
- lf_to_crlf_filter_fn,
- lf_to_crlf_free_fn,
+ .filter = lf_to_crlf_filter_fn,
+ .free = lf_to_crlf_free_fn,
};
static struct stream_filter *lf_to_crlf_filter(void)
}
static struct stream_filter_vtbl cascade_vtbl = {
- cascade_filter_fn,
- cascade_free_fn,
+ .filter = cascade_filter_fn,
+ .free = cascade_free_fn,
};
static struct stream_filter *cascade_filter(struct stream_filter *one,
}
static struct stream_filter_vtbl ident_vtbl = {
- ident_filter_fn,
- ident_free_fn,
+ .filter = ident_filter_fn,
+ .free = ident_free_fn,
};
static struct stream_filter *ident_filter(const struct object_id *oid)
git_config(urlmatch_config_entry, &config);
string_list_clear(&config.vars, 1);
free(normalized_url);
+ urlmatch_config_release(&config);
strbuf_release(&url);
c->configured = 1;
*/
#include "cache.h"
+#include "date.h"
/*
* This is like mktime, but without normalization of tm_wday and tm_yday.
struct date_mode *date_mode_from_type(enum date_mode_type type)
{
- static struct date_mode mode;
+ static struct date_mode mode = DATE_MODE_INIT;
if (type == DATE_STRFTIME)
BUG("cannot create anonymous strftime date_mode struct");
mode.type = type;
- mode.local = 0;
return &mode;
}
die("unknown date format %s", format);
}
+void date_mode_release(struct date_mode *mode)
+{
+ free((char *)mode->strftime_fmt);
+}
+
void datestamp(struct strbuf *out)
{
time_t now;
--- /dev/null
+#ifndef DATE_H
+#define DATE_H
+
+/**
+ * The date mode type. This has DATE_NORMAL at an explicit "= 0" to
+ * accommodate a memset([...], 0, [...]) initialization when "struct
+ * date_mode" is used as an embedded struct member, as in the case of
+ * e.g. "struct pretty_print_context" and "struct rev_info".
+ */
+enum date_mode_type {
+ DATE_NORMAL = 0,
+ DATE_HUMAN,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_ISO8601,
+ DATE_ISO8601_STRICT,
+ DATE_RFC2822,
+ DATE_STRFTIME,
+ DATE_RAW,
+ DATE_UNIX
+};
+
+struct date_mode {
+ enum date_mode_type type;
+ const char *strftime_fmt;
+ int local;
+};
+
+#define DATE_MODE_INIT { \
+ .type = DATE_NORMAL, \
+}
+
+/**
+ * Convenience helper for passing a constant type, like:
+ *
+ * show_date(t, tz, DATE_MODE(NORMAL));
+ */
+#define DATE_MODE(t) date_mode_from_type(DATE_##t)
+struct date_mode *date_mode_from_type(enum date_mode_type type);
+
+/**
+ * Format <'time', 'timezone'> into static memory according to 'mode'
+ * and return it. The mode is an initialized "struct date_mode"
+ * (usually from the DATE_MODE() macro).
+ */
+const char *show_date(timestamp_t time, int timezone, const struct date_mode *mode);
+
+/**
+ * Parse a date format for later use with show_date().
+ *
+ * When the "date_mode_type" is DATE_STRFTIME the "strftime_fmt"
+ * member of "struct date_mode" will be a malloc()'d format string to
+ * be used with strbuf_addftime(), in which case you'll need to call
+ * date_mode_release() later.
+ */
+void parse_date_format(const char *format, struct date_mode *mode);
+
+/**
+ * Release a "struct date_mode", currently only required if
+ * parse_date_format() has parsed a "DATE_STRFTIME" format.
+ */
+void date_mode_release(struct date_mode *mode);
+
+void show_date_relative(timestamp_t time, struct strbuf *timebuf);
+int parse_date(const char *date, struct strbuf *out);
+int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
+int parse_expiry_date(const char *date, timestamp_t *timestamp);
+void datestamp(struct strbuf *out);
+#define approxidate(s) approxidate_careful((s), NULL)
+timestamp_t approxidate_careful(const char *, int *);
+timestamp_t approxidate_relative(const char *date);
+int date_overflows(timestamp_t date);
+time_t tm_to_time_t(const struct tm *tm);
+#endif
revs->combined_all_paths = 0;
revs->merges_imply_patch = 0;
revs->merges_need_diff = 0;
+ revs->remerge_diff = 0;
}
static void set_separate(struct rev_info *revs)
{
suppress(revs);
revs->separate_merges = 1;
+ revs->simplify_history = 0;
}
static void set_first_parent(struct rev_info *revs)
revs->dense_combined_merges = 1;
}
+static void set_remerge_diff(struct rev_info *revs)
+{
+ suppress(revs);
+ revs->remerge_diff = 1;
+ revs->simplify_history = 0;
+}
+
static diff_merges_setup_func_t func_by_opt(const char *optarg)
{
if (!strcmp(optarg, "off") || !strcmp(optarg, "none"))
return set_combined;
else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
return set_dense_combined;
+ else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge"))
+ return set_remerge_diff;
else if (!strcmp(optarg, "m") || !strcmp(optarg, "on"))
return set_to_default;
return NULL;
diff_merges_setup_func_t func = func_by_opt(optarg);
if (!func)
- die(_("unknown value for --diff-merges: %s"), optarg);
+ die(_("invalid value for '%s': '%s'"), "--diff-merges", optarg);
func(revs);
} else if (!strcmp(arg, "--cc")) {
set_dense_combined(revs);
revs->merges_imply_patch = 1;
+ } else if (!strcmp(arg, "--remerge-diff")) {
+ set_remerge_diff(revs);
+ revs->merges_imply_patch = 1;
} else if (!strcmp(arg, "--no-diff-merges")) {
suppress(revs);
} else if (!strcmp(arg, "--combined-all-paths")) {
#include "help.h"
#include "promisor-remote.h"
#include "dir.h"
+#include "strmap.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
for (i = 0; i < wol->nr; i++)
free((void *)wol->buf[i].line);
+ free(wol->buf);
wol->nr = 0;
}
return userdiff_get_textconv(r, one->driver);
}
+static struct strbuf *additional_headers(struct diff_options *o,
+ const char *path)
+{
+ if (!o->additional_path_headers)
+ return NULL;
+ return strmap_get(o->additional_path_headers, path);
+}
+
+static void add_formatted_headers(struct strbuf *msg,
+ struct strbuf *more_headers,
+ const char *line_prefix,
+ const char *meta,
+ const char *reset)
+{
+ char *next, *newline;
+
+ for (next = more_headers->buf; *next; next = newline) {
+ newline = strchrnul(next, '\n');
+ strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
+ (int)(newline - next), next, reset);
+ if (*newline)
+ newline++;
+ }
+}
+
static void builtin_diff(const char *name_a,
const char *name_b,
struct diff_filespec *one,
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+ if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) {
+ /*
+ * We should only reach this point for pairs from
+ * create_filepairs_for_header_only_notifications(). For
+ * these, we should avoid the "/dev/null" special casing
+ * above, meaning we avoid showing such pairs as either
+ * "new file" or "deleted file" below.
+ */
+ lbl[0] = a_one;
+ lbl[1] = b_two;
+ }
strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
if (lbl[0][0] == '/') {
/* /dev/null */
const char *set = diff_get_color(use_color, DIFF_METAINFO);
const char *reset = diff_get_color(use_color, DIFF_RESET);
const char *line_prefix = diff_line_prefix(o);
+ struct strbuf *more_headers = NULL;
*must_show_header = 1;
strbuf_init(msg, PATH_MAX * 2 + 300);
default:
*must_show_header = 0;
}
+ if ((more_headers = additional_headers(o, name))) {
+ add_formatted_headers(msg, more_headers,
+ line_prefix, set, reset);
+ *must_show_header = 1;
+ }
if (one && two && !oideq(&one->oid, &two->oid)) {
const unsigned hexsz = the_hash_algo->hexsz;
int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
prep_parse_options(options);
}
+static const char diff_status_letters[] = {
+ DIFF_STATUS_ADDED,
+ DIFF_STATUS_COPIED,
+ DIFF_STATUS_DELETED,
+ DIFF_STATUS_MODIFIED,
+ DIFF_STATUS_RENAMED,
+ DIFF_STATUS_TYPE_CHANGED,
+ DIFF_STATUS_UNKNOWN,
+ DIFF_STATUS_UNMERGED,
+ DIFF_STATUS_FILTER_AON,
+ DIFF_STATUS_FILTER_BROKEN,
+ '\0',
+};
+
+static unsigned int filter_bit['Z' + 1];
+
+static void prepare_filter_bits(void)
+{
+ int i;
+
+ if (!filter_bit[DIFF_STATUS_ADDED]) {
+ for (i = 0; diff_status_letters[i]; i++)
+ filter_bit[(int) diff_status_letters[i]] = (1 << i);
+ }
+}
+
+static unsigned filter_bit_tst(char status, const struct diff_options *opt)
+{
+ return opt->filter & filter_bit[(int) status];
+}
+
+unsigned diff_filter_bit(char status)
+{
+ prepare_filter_bits();
+ return filter_bit[(int) status];
+}
+
void diff_setup_done(struct diff_options *options)
{
unsigned check_mask = DIFF_FORMAT_NAME |
if (!options->use_color || external_diff())
options->color_moved = 0;
+ if (options->filter_not) {
+ if (!options->filter)
+ options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON];
+ options->filter &= ~options->filter_not;
+ }
+
FREE_AND_NULL(options->parseopts);
}
return 1;
}
-static const char diff_status_letters[] = {
- DIFF_STATUS_ADDED,
- DIFF_STATUS_COPIED,
- DIFF_STATUS_DELETED,
- DIFF_STATUS_MODIFIED,
- DIFF_STATUS_RENAMED,
- DIFF_STATUS_TYPE_CHANGED,
- DIFF_STATUS_UNKNOWN,
- DIFF_STATUS_UNMERGED,
- DIFF_STATUS_FILTER_AON,
- DIFF_STATUS_FILTER_BROKEN,
- '\0',
-};
-
-static unsigned int filter_bit['Z' + 1];
-
-static void prepare_filter_bits(void)
-{
- int i;
-
- if (!filter_bit[DIFF_STATUS_ADDED]) {
- for (i = 0; diff_status_letters[i]; i++)
- filter_bit[(int) diff_status_letters[i]] = (1 << i);
- }
-}
-
-static unsigned filter_bit_tst(char status, const struct diff_options *opt)
-{
- return opt->filter & filter_bit[(int) status];
-}
-
-unsigned diff_filter_bit(char status)
-{
- prepare_filter_bits();
- return filter_bit[(int) status];
-}
-
static int diff_opt_diff_filter(const struct option *option,
const char *optarg, int unset)
{
BUG_ON_OPT_NEG(unset);
prepare_filter_bits();
- /*
- * If there is a negation e.g. 'd' in the input, and we haven't
- * initialized the filter field with another --diff-filter, start
- * from full set of bits, except for AON.
- */
- if (!opt->filter) {
- for (i = 0; (optch = optarg[i]) != '\0'; i++) {
- if (optch < 'a' || 'z' < optch)
- continue;
- opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
- opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
- break;
- }
- }
-
for (i = 0; (optch = optarg[i]) != '\0'; i++) {
unsigned int bit;
int negate;
return error(_("unknown change class '%c' in --diff-filter=%s"),
optarg[i], optarg);
if (negate)
- opt->filter &= ~bit;
+ opt->filter_not |= bit;
else
opt->filter |= bit;
}
N_("select files by diff type"),
PARSE_OPT_NONEG, diff_opt_diff_filter),
{ OPTION_CALLBACK, 0, "output", options, N_("<file>"),
- N_("Output to a specific file"),
+ N_("output to a specific file"),
PARSE_OPT_NONEG, NULL, 0, diff_opt_output },
OPT_END()
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
{
- if (diff_unmodified_pair(p))
+ int include_conflict_headers =
+ (additional_headers(o, p->one->path) &&
+ (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+ /*
+ * Check if we can return early without showing a diff. Note that
+ * diff_filepair only stores {oid, path, mode, is_valid}
+ * information for each path, and thus diff_unmodified_pair() only
+ * considers those bits of info. However, we do not want pairs
+ * created by create_filepairs_for_header_only_notifications()
+ * (which always look like unmodified pairs) to be ignored, so
+ * return early if both p is unmodified AND we don't want to
+ * include_conflict_headers.
+ */
+ if (diff_unmodified_pair(p) && !include_conflict_headers)
return;
+ /* Actually, we can also return early to avoid showing tree diffs */
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
+ return;
run_diff(p, o);
}
run_checkdiff(p, o);
}
-int diff_queue_is_empty(void)
+int diff_queue_is_empty(struct diff_options *o)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
+ int include_conflict_headers =
+ (o->additional_path_headers &&
+ (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+ if (include_conflict_headers)
+ return 0;
+
for (i = 0; i < q->nr; i++)
if (!diff_unmodified_pair(q->queue[i]))
return 0;
warning(_(rename_limit_advice), varname, needed);
}
+static void create_filepairs_for_header_only_notifications(struct diff_options *o)
+{
+ struct strset present;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct hashmap_iter iter;
+ struct strmap_entry *e;
+ int i;
+
+ strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
+
+ /*
+ * Find out which paths exist in diff_queued_diff, preferring
+ * one->path for any pair that has multiple paths.
+ */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ char *path = p->one->path ? p->one->path : p->two->path;
+
+ if (strmap_contains(o->additional_path_headers, path))
+ strset_add(&present, path);
+ }
+
+ /*
+ * Loop over paths in additional_path_headers; for each NOT already
+ * in diff_queued_diff, create a synthetic filepair and insert that
+ * into diff_queued_diff.
+ */
+ strmap_for_each_entry(o->additional_path_headers, &iter, e) {
+ if (!strset_contains(&present, e->key)) {
+ struct diff_filespec *one, *two;
+ struct diff_filepair *p;
+
+ one = alloc_filespec(e->key);
+ two = alloc_filespec(e->key);
+ fill_filespec(one, null_oid(), 0, 0);
+ fill_filespec(two, null_oid(), 0, 0);
+ p = diff_queue(q, one, two);
+ p->status = DIFF_STATUS_MODIFIED;
+ }
+ }
+
+ /* Re-sort the filepairs */
+ diffcore_fix_diff_index();
+
+ /* Cleanup */
+ strset_clear(&present);
+}
+
static void diff_flush_patch_all_file_pairs(struct diff_options *o)
{
int i;
if (o->color_moved)
o->emitted_symbols = &esm;
+ if (o->additional_path_headers)
+ create_filepairs_for_header_only_notifications(o);
+
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
if (check_pair_status(p))
diff_free_file(options);
diff_free_ignore_regex(options);
+ clear_pathspec(&options->pathspec);
+ FREE_AND_NULL(options->parseopts);
}
void diff_flush(struct diff_options *options)
* Order: raw, stat, summary, patch
* or: name/name-status/checkdiff (other bits clear)
*/
- if (!q->nr)
+ if (!q->nr && !options->additional_path_headers)
goto free_queue;
if (output_format & (DIFF_FORMAT_RAW |
struct diff_flags flags;
/* diff-filter bits */
- unsigned int filter;
+ unsigned int filter, filter_not;
int use_color;
struct repository *repo;
struct option *parseopts;
+ struct strmap *additional_path_headers;
int no_free;
};
" show all files diff when -S is used and hit is found.\n" \
" -a --text treat all files as text.\n"
-int diff_queue_is_empty(void);
+int diff_queue_is_empty(struct diff_options *o);
void diff_flush(struct diff_options*);
void diff_free(struct diff_options*);
void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
int base_len,
- const struct pathspec *pathspec)
+ const struct pathspec *pathspec,
+ struct index_state *istate)
{
struct untracked_cache_dir *root;
static int untracked_cache_disabled = -1;
return NULL;
}
- if (!dir->untracked->root)
+ if (!dir->untracked->root) {
+ /* Untracked cache existed but is not initialized; fix that */
FLEX_ALLOC_STR(dir->untracked->root, name, "");
+ istate->cache_changed |= UNTRACKED_CHANGED;
+ }
/* Validate $GIT_DIR/info/exclude and core.excludesfile */
root = dir->untracked->root;
return dir->nr;
}
- untracked = validate_untracked_cache(dir, len, pathspec);
+ untracked = validate_untracked_cache(dir, len, pathspec, istate);
if (!untracked)
/*
* make sure untracked cache code path is disabled,
if (force_untracked_cache < 0)
force_untracked_cache =
- git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0);
+ git_env_bool("GIT_FORCE_UNTRACKED_CACHE", -1);
+ if (force_untracked_cache < 0)
+ force_untracked_cache = (istate->repo->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE);
if (force_untracked_cache &&
dir->untracked == istate->untracked &&
(dir->untracked->dir_opened ||
int grafts_replace_parents = 1;
int core_apply_sparse_checkout;
int core_sparse_checkout_cone;
+int sparse_expect_files_outside_of_patterns;
int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg;
noop_negotiator_init(negotiator);
return;
- case FETCH_NEGOTIATION_DEFAULT:
+ case FETCH_NEGOTIATION_CONSECUTIVE:
default_negotiator_init(negotiator);
return;
}
trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
for (ref = *refs; ref; ref = ref->next) {
- struct object *o;
+ struct commit *commit;
+
+ commit = lookup_commit_in_graph(the_repository, &ref->old_oid);
+ if (!commit) {
+ struct object *o;
- if (!has_object_file_with_flags(&ref->old_oid,
+ if (!has_object_file_with_flags(&ref->old_oid,
OBJECT_INFO_QUICK |
- OBJECT_INFO_SKIP_FETCH_OBJECT))
- continue;
- o = parse_object(the_repository, &ref->old_oid);
- if (!o)
- continue;
+ OBJECT_INFO_SKIP_FETCH_OBJECT))
+ continue;
+ o = parse_object(the_repository, &ref->old_oid);
+ if (!o || o->type != OBJ_COMMIT)
+ continue;
+
+ commit = (struct commit *)o;
+ }
/*
* We already have it -- which may mean that we were
* in sync with the other side at some time after
* that (it is OK if we guess wrong here).
*/
- if (o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- if (!cutoff || cutoff < commit->date)
- cutoff = commit->date;
- }
+ if (!cutoff || cutoff < commit->date)
+ cutoff = commit->date;
}
trace2_region_leave("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
* otherwise.
*/
if (*received_ready && reader->status != PACKET_READ_DELIM)
- die(_("expected packfile to be sent after 'ready'"));
+ /*
+ * TRANSLATORS: The parameter will be 'ready', a protocol
+ * keyword.
+ */
+ die(_("expected packfile to be sent after '%s'"), "ready");
if (!*received_ready && reader->status != PACKET_READ_FLUSH)
- die(_("expected no other sections to be sent after no 'ready'"));
+ /*
+ * TRANSLATORS: The parameter will be 'ready', a protocol
+ * keyword.
+ */
+ die(_("expected no other sections to be sent after no '%s'"), "ready");
return 0;
}
#endif
#include <windows.h>
#define GIT_WINDOWS_NATIVE
+#ifdef HAVE_RTLGENRANDOM
+/* This is required to get access to RtlGenRandom. */
+#define SystemFunction036 NTAPI SystemFunction036
+#include <NTSecAPI.h>
+#undef SystemFunction036
+#endif
#endif
#include <unistd.h>
#else
#include <stdint.h>
#endif
+#ifdef HAVE_ARC4RANDOM_LIBBSD
+#include <bsd/stdlib.h>
+#endif
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h>
+#endif
#ifdef NO_INTPTR_T
/*
* On I16LP32, ILP32 and LP64 "long" is the safe bet, however
/*
* Let callers be aware of the constant return value; this can help
* gcc with -Wuninitialized analysis. We restrict this trick to gcc, though,
- * because some compilers may not support variadic macros. Since we're only
- * trying to help gcc, anyway, it's OK; other compilers will fall back to
- * using the function as usual.
+ * because other compilers may be confused by this.
*/
#if defined(__GNUC__)
static inline int const_error(void)
#endif
#endif
-/*
- * This is always defined as a first step towards making the use of variadic
- * macros unconditional. If it causes compilation problems on your platform,
- * please report it to the Git mailing list at git@vger.kernel.org.
- */
-#define HAVE_VARIADIC_MACROS 1
-
/* usage.c: only to be used for testing BUG() implementation (see test-tool) */
extern int BUG_exit_code;
-#ifdef HAVE_VARIADIC_MACROS
__attribute__((format (printf, 3, 4))) NORETURN
void BUG_fl(const char *file, int line, const char *fmt, ...);
#define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__)
-#else
-__attribute__((format (printf, 1, 2))) NORETURN
-void BUG(const char *fmt, ...);
-#endif
/*
* Preserves errno, prints a message, but gives no warning for ENOENT.
#define UNLEAK(var) do {} while (0)
#endif
+#define z_const
+#include <zlib.h>
+
+#if ZLIB_VERNUM < 0x1290
+/*
+ * This is uncompress2, which is only available in zlib >= 1.2.9
+ * (released as of early 2017). See compat/zlib-uncompress2.c.
+ */
+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
+ uLong *sourceLen);
+#endif
+
/*
* This include must come after system headers, since it introduces macros that
* replace system names.
void sleep_millisec(int millisec);
+/*
+ * Generate len bytes from the system cryptographically secure PRNG.
+ * Returns 0 on success and -1 on error, setting errno. The inability to
+ * satisfy the full request is an error.
+ */
+int csprng_bytes(void *buf, size_t len);
+
#endif
# Provide a way to not pass this option by setting git-p4.retries to 0
real_cmd += ["-r", str(retries)]
- if not isinstance(cmd, list):
- real_cmd = ' '.join(real_cmd) + ' ' + cmd
- else:
- real_cmd += cmd
+ real_cmd += cmd
# now check that we can actually talk to the server
global p4_access_checked
def run_git_hook(cmd, param=[]):
"""Execute a hook if the hook exists."""
+ args = ['git', 'hook', 'run', '--ignore-missing', cmd]
+ if param:
+ args.append("--")
+ for p in param:
+ args.append(p)
+ return subprocess.call(args) == 0
+
+def write_pipe(c, stdin, *k, **kw):
if verbose:
- sys.stderr.write("Looking for hook: %s\n" % cmd)
- sys.stderr.flush()
-
- hooks_path = gitConfig("core.hooksPath")
- if len(hooks_path) <= 0:
- hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
-
- if not isinstance(param, list):
- param=[param]
-
- # resolve hook file name, OS depdenent
- hook_file = os.path.join(hooks_path, cmd)
- if platform.system() == 'Windows':
- if not os.path.isfile(hook_file):
- # look for the file with an extension
- files = glob.glob(hook_file + ".*")
- if not files:
- return True
- files.sort()
- hook_file = files.pop()
- while hook_file.upper().endswith(".SAMPLE"):
- # The file is a sample hook. We don't want it
- if len(files) > 0:
- hook_file = files.pop()
- else:
- return True
-
- if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
- return True
-
- return run_hook_command(hook_file, param) == 0
-
-def run_hook_command(cmd, param):
- """Executes a git hook command
- cmd = the command line file to be executed. This can be
- a file that is run by OS association.
-
- param = a list of parameters to pass to the cmd command
-
- On windows, the extension is checked to see if it should
- be run with the Git for Windows Bash shell. If there
- is no file extension, the file is deemed a bash shell
- and will be handed off to sh.exe. Otherwise, Windows
- will be called with the shell to handle the file assocation.
-
- For non Windows operating systems, the file is called
- as an executable.
- """
- cli = [cmd] + param
- use_shell = False
- if platform.system() == 'Windows':
- (root,ext) = os.path.splitext(cmd)
- if ext == "":
- exe_path = os.environ.get("EXEPATH")
- if exe_path is None:
- exe_path = ""
- else:
- exe_path = os.path.join(exe_path, "bin")
- cli = [os.path.join(exe_path, "SH.EXE")] + cli
- else:
- use_shell = True
- return subprocess.call(cli, shell=use_shell)
-
-
-def write_pipe(c, stdin):
- if verbose:
- sys.stderr.write('Writing pipe: %s\n' % str(c))
+ sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c)))
- expand = not isinstance(c, list)
- p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
+ p = subprocess.Popen(c, stdin=subprocess.PIPE, *k, **kw)
pipe = p.stdin
val = pipe.write(stdin)
pipe.close()
if p.wait():
- die('Command failed: %s' % str(c))
+ die('Command failed: {}'.format(' '.join(c)))
return val
-def p4_write_pipe(c, stdin):
+def p4_write_pipe(c, stdin, *k, **kw):
real_cmd = p4_build_cmd(c)
if bytes is not str and isinstance(stdin, str):
stdin = encode_text_stream(stdin)
- return write_pipe(real_cmd, stdin)
+ return write_pipe(real_cmd, stdin, *k, **kw)
-def read_pipe_full(c):
+def read_pipe_full(c, *k, **kw):
""" Read output from command. Returns a tuple
of the return status, stdout text and stderr
text.
"""
if verbose:
- sys.stderr.write('Reading pipe: %s\n' % str(c))
+ sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
- expand = not isinstance(c, list)
- p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
+ p = subprocess.Popen(
+ c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *k, **kw)
(out, err) = p.communicate()
return (p.returncode, out, decode_text_stream(err))
-def read_pipe(c, ignore_error=False, raw=False):
+def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
""" Read output from command. Returns the output text on
success. On failure, terminates execution, unless
ignore_error is True, when it returns an empty string.
If raw is True, do not attempt to decode output text.
"""
- (retcode, out, err) = read_pipe_full(c)
+ (retcode, out, err) = read_pipe_full(c, *k, **kw)
if retcode != 0:
if ignore_error:
out = ""
else:
- die('Command failed: %s\nError: %s' % (str(c), err))
+ die('Command failed: {}\nError: {}'.format(' '.join(c), err))
if not raw:
out = decode_text_stream(out)
return out
-def read_pipe_text(c):
+def read_pipe_text(c, *k, **kw):
""" Read output from a command with trailing whitespace stripped.
On error, returns None.
"""
- (retcode, out, err) = read_pipe_full(c)
+ (retcode, out, err) = read_pipe_full(c, *k, **kw)
if retcode != 0:
return None
else:
return decode_text_stream(out).rstrip()
-def p4_read_pipe(c, ignore_error=False, raw=False):
+def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw):
real_cmd = p4_build_cmd(c)
- return read_pipe(real_cmd, ignore_error, raw=raw)
+ return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw)
-def read_pipe_lines(c, raw=False):
+def read_pipe_lines(c, raw=False, *k, **kw):
if verbose:
- sys.stderr.write('Reading pipe: %s\n' % str(c))
+ sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
- expand = not isinstance(c, list)
- p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+ p = subprocess.Popen(c, stdout=subprocess.PIPE, *k, **kw)
pipe = p.stdout
lines = pipe.readlines()
if not raw:
lines = [decode_text_stream(line) for line in lines]
if pipe.close() or p.wait():
- die('Command failed: %s' % str(c))
+ die('Command failed: {}'.format(' '.join(c)))
return lines
-def p4_read_pipe_lines(c):
+def p4_read_pipe_lines(c, *k, **kw):
"""Specifically invoke p4 on the command supplied. """
real_cmd = p4_build_cmd(c)
- return read_pipe_lines(real_cmd)
+ return read_pipe_lines(real_cmd, *k, **kw)
def p4_has_command(cmd):
"""Ask p4 for help on this command. If it returns an error, the
# assume it failed because @... was invalid changelist
return True
-def system(cmd, ignore_error=False):
- expand = not isinstance(cmd, list)
+def system(cmd, ignore_error=False, *k, **kw):
if verbose:
- sys.stderr.write("executing %s\n" % str(cmd))
- retcode = subprocess.call(cmd, shell=expand)
+ sys.stderr.write("executing {}\n".format(
+ ' '.join(cmd) if isinstance(cmd, list) else cmd))
+ retcode = subprocess.call(cmd, *k, **kw)
if retcode and not ignore_error:
- raise CalledProcessError(retcode, cmd)
+ raise subprocess.CalledProcessError(retcode, cmd)
return retcode
-def p4_system(cmd):
+def p4_system(cmd, *k, **kw):
"""Specifically invoke p4 as the system command. """
real_cmd = p4_build_cmd(cmd)
- expand = not isinstance(real_cmd, list)
- retcode = subprocess.call(real_cmd, shell=expand)
+ retcode = subprocess.call(real_cmd, *k, **kw)
if retcode:
- raise CalledProcessError(retcode, real_cmd)
+ raise subprocess.CalledProcessError(retcode, real_cmd)
def die_bad_access(s):
die("failure accessing depot: {0}".format(s.rstrip()))
return isModeExec(src_mode) != isModeExec(dst_mode)
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
- errors_as_exceptions=False):
-
- if not isinstance(cmd, list):
- cmd = "-G " + cmd
- expand = True
- else:
- cmd = ["-G"] + cmd
- expand = False
+ errors_as_exceptions=False, *k, **kw):
- cmd = p4_build_cmd(cmd)
+ cmd = p4_build_cmd(["-G"] + cmd)
if verbose:
- sys.stderr.write("Opening pipe: %s\n" % str(cmd))
+ sys.stderr.write("Opening pipe: {}\n".format(' '.join(cmd)))
# Use a temporary file to avoid deadlocks without
# subprocess.communicate(), which would put another copy
stdin_file.flush()
stdin_file.seek(0)
- p4 = subprocess.Popen(cmd,
- shell=expand,
- stdin=stdin_file,
- stdout=subprocess.PIPE)
+ p4 = subprocess.Popen(
+ cmd, stdin=stdin_file, stdout=subprocess.PIPE, *k, **kw)
result = []
try:
return result
-def p4Cmd(cmd):
- list = p4CmdList(cmd)
+def p4Cmd(cmd, *k, **kw):
+ list = p4CmdList(cmd, *k, **kw)
result = {}
for entry in list:
result.update(entry)
return git_dir(path) != None
def parseRevision(ref):
- return read_pipe("git rev-parse %s" % ref).strip()
+ return read_pipe(["git", "rev-parse", ref]).strip()
def branchExists(ref):
rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
branches = {}
- cmdline = "git rev-parse --symbolic "
+ cmdline = ["git", "rev-parse", "--symbolic"]
if branchesAreInRemotes:
- cmdline += "--remotes"
+ cmdline.append("--remotes")
else:
- cmdline += "--branches"
+ cmdline.append("--branches")
for line in read_pipe_lines(cmdline):
line = line.strip()
originPrefix = "origin/p4/"
- for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
+ for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]):
line = line.strip()
if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
continue
remoteHead, ','.join(settings['depot-paths'])))
if update:
- system("git update-ref %s %s" % (remoteHead, originHead))
+ system(["git", "update-ref", remoteHead, originHead])
def originP4BranchesExist():
return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
"""Look at the p4 client spec, create a View() object that contains
all the mappings, and return it."""
- specList = p4CmdList("client -o")
+ specList = p4CmdList(["client", "-o"])
if len(specList) != 1:
die('Output from "client -o" is %d lines, expecting 1' %
len(specList))
def getClientRoot():
"""Grab the client directory."""
- output = p4CmdList("client -o")
+ output = p4CmdList(["client", "-o"])
if len(output) != 1:
die('Output from "client -o" is %d lines, expecting 1' % len(output))
if self.myP4UserId:
return self.myP4UserId
- results = p4CmdList("user -o")
+ results = p4CmdList(["user", "-o"])
for r in results:
if 'User' in r:
self.myP4UserId = r['User']
self.users = {}
self.emails = {}
- for output in p4CmdList("users"):
+ for output in p4CmdList(["users"]):
if "User" not in output:
continue
self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
die("Large file system not supported for git-p4 submit command. Please remove it from config.")
def check(self):
- if len(p4CmdList("opened ...")) > 0:
+ if len(p4CmdList(["opened", "..."])) > 0:
die("You have files opened with perforce! Close them before starting the sync.")
def separate_jobs_from_description(self, message):
# then gets used to patch up the username in the change. If the same
# client spec is being used by multiple processes then this might go
# wrong.
- results = p4CmdList("client -o") # find the current client
+ results = p4CmdList(["client", "-o"]) # find the current client
client = None
for r in results:
if 'Client' in r:
def modifyChangelistUser(self, changelist, newUser):
# fixup the user field of a changelist after it has been submitted.
- changes = p4CmdList("change -o %s" % changelist)
+ changes = p4CmdList(["change", "-o", changelist])
if len(changes) != 1:
die("Bad output from p4 change modifying %s to user %s" %
(changelist, newUser))
# p4 does not understand format version 3 and above
input = marshal.dumps(c, 2)
- result = p4CmdList("change -f -i", stdin=input)
+ result = p4CmdList(["change", "-f", "-i"], stdin=input)
for r in result:
if 'code' in r:
if r['code'] == 'error':
if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""):
editor = os.environ.get("P4EDITOR")
else:
- editor = read_pipe("git var GIT_EDITOR").strip()
+ editor = read_pipe(["git", "var", "GIT_EDITOR"]).strip()
system(["sh", "-c", ('%s "$@"' % editor), editor, template_file])
# If the file was not saved, prompt to see if this patch should
(p4User, gitEmail) = self.p4UserForCommit(id)
- diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id))
+ diff = read_pipe_lines(
+ ["git", "diff-tree", "-r"] + self.diffOpts + ["{}^".format(id), id])
filesToAdd = set()
filesToChangeType = set()
filesToDelete = set()
#
# Apply the patch for real, and do add/delete/+x handling.
#
- system(applyPatchCmd)
+ system(applyPatchCmd, shell=True)
for f in filesToChangeType:
p4_edit(f, "-t", "auto")
#
if self.detectRenames:
# command-line -M arg
- self.diffOpts = "-M"
+ self.diffOpts = ["-M"]
else:
# If not explicitly set check the config variable
detectRenames = gitConfig("git-p4.detectRenames")
if detectRenames.lower() == "false" or detectRenames == "":
- self.diffOpts = ""
+ self.diffOpts = []
elif detectRenames.lower() == "true":
- self.diffOpts = "-M"
+ self.diffOpts = ["-M"]
else:
- self.diffOpts = "-M%s" % detectRenames
+ self.diffOpts = ["-M{}".format(detectRenames)]
# no command-line arg for -C or --find-copies-harder, just
# config variables
if detectCopies.lower() == "false" or detectCopies == "":
pass
elif detectCopies.lower() == "true":
- self.diffOpts += " -C"
+ self.diffOpts.append("-C")
else:
- self.diffOpts += " -C%s" % detectCopies
+ self.diffOpts.append("-C{}".format(detectCopies))
if gitConfigBool("git-p4.detectCopiesHarder"):
- self.diffOpts += " --find-copies-harder"
+ self.diffOpts.append("--find-copies-harder")
num_shelves = len(self.update_shelve)
if num_shelves > 0 and num_shelves != len(commits):
lostAndFoundBranches = set()
user = gitConfig("git-p4.branchUser")
- if len(user) > 0:
- command = "branches -u %s" % user
- else:
- command = "branches"
- for info in p4CmdList(command):
+ for info in p4CmdList(
+ ["branches"] + (["-u", user] if len(user) > 0 else [])):
details = p4Cmd(["branch", "-o", info["branch"]])
viewIdx = 0
while "View%s" % viewIdx in details:
while True:
if self.verbose:
print("trying: earliest %s latest %s" % (earliestCommit, latestCommit))
- next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
+ next = read_pipe(["git", "rev-list", "--bisect",
+ latestCommit, earliestCommit]).strip()
if len(next) == 0:
if self.verbose:
print("argh")
if self.hasOrigin:
if not self.silent:
print('Syncing with origin first, using "git fetch origin"')
- system("git fetch origin")
+ system(["git", "fetch", "origin"])
def importHeadRevision(self, revision):
print("Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch))
if len(self.branch) == 0:
self.branch = self.refPrefix + "master"
if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
- system("git update-ref %s refs/heads/p4" % self.branch)
- system("git branch -D p4")
+ system(["git", "update-ref", self.branch, "refs/heads/p4"])
+ system(["git", "branch", "-D", "p4"])
# accept either the command-line option, or the configuration variable
if self.useClientSpec:
# Cleanup temporary branches created during import
if self.tempBranches != []:
for branch in self.tempBranches:
- read_pipe("git update-ref -d %s" % branch)
+ read_pipe(["git", "update-ref", "-d", branch])
os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
# Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
def rebase(self):
if os.system("git update-index --refresh") != 0:
die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.");
- if len(read_pipe("git diff-index HEAD --")) > 0:
+ if len(read_pipe(["git", "diff-index", "HEAD", "--"])) > 0:
die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
[upstream, settings] = findUpstreamBranchPoint()
upstream = re.sub("~[0-9]+$", "", upstream)
print("Rebasing the current branch onto %s" % upstream)
- oldHead = read_pipe("git rev-parse HEAD").strip()
- system("git rebase %s" % upstream)
- system("git diff-tree --stat --summary -M %s HEAD --" % oldHead)
+ oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip()
+ system(["git", "rebase", upstream])
+ system(["git", "diff-tree", "--stat", "--summary", "-M", oldHead,
+ "HEAD", "--"])
return True
class P4Clone(P4Sync):
init_cmd.append("--bare")
retcode = subprocess.call(init_cmd)
if retcode:
- raise CalledProcessError(retcode, init_cmd)
+ raise subprocess.CalledProcessError(retcode, init_cmd)
if not P4Sync.run(self, depotPaths):
return False
# auto-set this variable if invoked with --use-client-spec
if self.useClientSpec_from_options:
- system("git config --bool git-p4.useclientspec true")
+ system(["git", "config", "--bool", "git-p4.useclientspec", "true"])
return True
if originP4BranchesExist():
createOrUpdateBranchesFromOrigin()
- cmdline = "git rev-parse --symbolic "
- cmdline += " --remotes"
-
- for line in read_pipe_lines(cmdline):
+ for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]):
line = line.strip()
if not line.startswith('p4/') or line == "p4/HEAD":
cmd.gitdir = os.path.abspath(".git")
if not isValidGitDir(cmd.gitdir):
# "rev-parse --git-dir" without arguments will try $PWD/.git
- cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
+ cmd.gitdir = read_pipe(["git", "rev-parse", "--git-dir"]).strip()
if os.path.exists(cmd.gitdir):
- cdup = read_pipe("git rev-parse --show-cdup").strip()
+ cdup = read_pipe(["git", "rev-parse", "--show-cdup"]).strip()
if len(cdup) > 0:
chdir(cdup);
my $editor;
sub system_or_msg {
- my ($args, $msg) = @_;
+ my ($args, $msg, $cmd_name) = @_;
system(@$args);
my $signalled = $? & 127;
my $exit_code = $? >> 8;
return unless $signalled or $exit_code;
- my @sprintf_args = ($args->[0], $exit_code);
+ my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
if (defined $msg) {
# Quiet the 'redundant' warning category, except we
# need to support down to Perl 5.8, so we can't do a
my ($fn, $xfer_encoding) = @_;
if ($repo) {
+ my $hook_name = 'sendemail-validate';
my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
require File::Spec;
- my $validate_hook = File::Spec->catfile($hooks_path,
- 'sendemail-validate');
+ my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
my $hook_error;
if (-x $validate_hook) {
require Cwd;
chdir($repo->wc_path() or $repo->repo_path())
or die("chdir: $!");
local $ENV{"GIT_DIR"} = $repo->repo_path();
- $hook_error = system_or_msg([$validate_hook, $target]);
+ my @cmd = ("git", "hook", "run", "--ignore-missing",
+ $hook_name, "--");
+ my @cmd_msg = (@cmd, "<patch>");
+ my @cmd_run = (@cmd, $target);
+ $hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
chdir($cwd_save) or die("chdir: $!");
}
if ($hook_error) {
- die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
- "%s\n" .
- "warning: no patches were sent\n"), $fn, $hook_error);
+ $hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
+ $hook_error . "\n" .
+ "warning: no patches were sent\n"),
+ $fn, $hook_name);
+ die $hook_error;
}
}
case "$1" in
-h)
echo "$LONG_USAGE"
- case "$0" in *git-legacy-stash) exit 129;; esac
exit
esac
fi
or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
or: $dashless [--quiet] init [--] [<path>...]
or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...)
- or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--] [<path>...]
+ or: $dashless [--quiet] update [--init [--filter=<filter-spec>]] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--] [<path>...]
or: $dashless [--quiet] set-branch (--default|--branch <branch>) [--] <path>
or: $dashless [--quiet] set-url [--] <path> <newurl>
or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
single_branch=
jobs=
recommend_shallow=
+filter=
die_if_unmatched ()
{
--no-single-branch)
single_branch="--no-single-branch"
;;
+ --filter)
+ case "$2" in '') usage ;; esac
+ filter="--filter=$2"
+ shift
+ ;;
+ --filter=*)
+ filter="$1"
+ ;;
--)
shift
break
shift
done
+ if test -n "$filter" && test "$init" != "1"
+ then
+ usage
+ fi
+
if test -n "$init"
then
cmd_init "--" "$@" || return
$single_branch \
$recommend_shallow \
$jobs \
+ $filter \
-- \
"$@" || echo "#unmatched" $?
} | {
} else {
prefix = NULL;
}
+ assert(!prefix || *prefix);
precompose_argv_prefix(argc, argv, NULL);
if (use_pager == -1 && run_setup &&
!(p->option & DELAY_PAGER_CONFIG))
{ "grep", cmd_grep, RUN_SETUP_GENTLY },
{ "hash-object", cmd_hash_object },
{ "help", cmd_help },
+ { "hook", cmd_hook, RUN_SETUP },
{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
{ "init", cmd_init_db },
{ "init-db", cmd_init_db },
struct tempfile *buffer_file;
int ret = -1;
const char *line;
- size_t trust_size;
char *principal;
struct strbuf ssh_principals_out = STRBUF_INIT;
struct strbuf ssh_principals_err = STRBUF_INIT;
ret = -1;
} else {
/* Check every principal we found (one per line) */
- for (line = ssh_principals_out.buf; *line;
- line = strchrnul(line + 1, '\n')) {
- while (*line == '\n')
- line++;
- if (!*line)
- break;
+ const char *next;
+ for (line = ssh_principals_out.buf;
+ *line;
+ line = next) {
+ const char *end_of_text;
+
+ next = end_of_text = strchrnul(line, '\n');
+
+ /* Did we find a LF, and did we have CR before it? */
+ if (*end_of_text &&
+ line < end_of_text &&
+ end_of_text[-1] == '\r')
+ end_of_text--;
+
+ /* Unless we hit NUL, skip over the LF we found */
+ if (*next)
+ next++;
- trust_size = strcspn(line, "\n");
- principal = xmemdupz(line, trust_size);
+ /* Not all lines are data. Skip empty ones */
+ if (line == end_of_text)
+ continue;
+
+ /* We now know we have an non-empty line. Process it */
+ principal = xmemdupz(line, end_of_text - line);
child_process_init(&ssh_keygen);
strbuf_release(&ssh_keygen_out);
return config_error_nonbool(var);
fmt = get_format_by_name(value);
if (!fmt)
- return error("unsupported value for %s: %s",
+ return error(_("invalid value for '%s': '%s'"),
var, value);
use_format = fmt;
return 0;
free(trust);
if (ret)
- return error("unsupported value for %s: %s", var,
- value);
+ return error(_("invalid value for '%s': '%s'"),
+ var, value);
return 0;
}
struct child_process gpg = CHILD_PROCESS_INIT;
int ret;
size_t bottom;
+ const char *cp;
struct strbuf gpg_status = STRBUF_INIT;
strvec_pushl(&gpg.args,
signature, 1024, &gpg_status, 0);
sigchain_pop(SIGPIPE);
- ret |= !strstr(gpg_status.buf, "\n[GNUPG:] SIG_CREATED ");
+ for (cp = gpg_status.buf;
+ cp && (cp = strstr(cp, "[GNUPG:] SIG_CREATED "));
+ cp++) {
+ if (cp == gpg_status.buf || cp[-1] == '\n')
+ break; /* found */
+ }
+ ret |= !cp;
strbuf_release(&gpg_status);
if (ret)
return error(_("gpg failed to sign the data"));
return graph;
}
+void graph_clear(struct git_graph *graph)
+{
+ if (!graph)
+ return;
+
+ free(graph->columns);
+ free(graph->new_columns);
+ free(graph->mapping);
+ free(graph->old_mapping);
+ free(graph);
+}
+
static void graph_update_state(struct git_graph *graph, enum graph_state s)
{
graph->prev_state = graph->state;
*/
struct git_graph *graph_init(struct rev_info *opt);
+/*
+ * Free a struct git_graph.
+ */
+void graph_clear(struct git_graph *graph);
+
/*
* Update a git_graph with a new commit.
* This will cause the graph to begin outputting lines for the new commit
fwrite(buf, size, 1, stdout);
}
-static struct grep_opt grep_defaults = {
- .relative = 1,
- .pathname = 1,
- .max_depth = -1,
- .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED,
- .colors = {
- [GREP_COLOR_CONTEXT] = "",
- [GREP_COLOR_FILENAME] = GIT_COLOR_MAGENTA,
- [GREP_COLOR_FUNCTION] = "",
- [GREP_COLOR_LINENO] = GIT_COLOR_GREEN,
- [GREP_COLOR_COLUMNNO] = GIT_COLOR_GREEN,
- [GREP_COLOR_MATCH_CONTEXT] = GIT_COLOR_BOLD_RED,
- [GREP_COLOR_MATCH_SELECTED] = GIT_COLOR_BOLD_RED,
- [GREP_COLOR_SELECTED] = "",
- [GREP_COLOR_SEP] = GIT_COLOR_CYAN,
- },
- .only_matching = 0,
- .color = -1,
- .output = std_output,
-};
-
static const char *color_grep_slots[] = {
[GREP_COLOR_CONTEXT] = "context",
[GREP_COLOR_FILENAME] = "filename",
*/
int grep_config(const char *var, const char *value, void *cb)
{
- struct grep_opt *opt = &grep_defaults;
+ struct grep_opt *opt = cb;
const char *slot;
if (userdiff_config(var, value) < 0)
return -1;
- /*
- * The instance of grep_opt that we set up here is copied by
- * grep_init() to be used by each individual invocation.
- * When populating a new field of this structure here, be
- * sure to think about ownership -- e.g., you might need to
- * override the shallow copy in grep_init() with a deep copy.
- */
-
if (!strcmp(var, "grep.extendedregexp")) {
opt->extended_regexp_option = git_config_bool(var, value);
return 0;
return 0;
}
-/*
- * Initialize one instance of grep_opt and copy the
- * default values from the template we read the configuration
- * information in an earlier call to git_config(grep_config).
- */
-void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix)
+void grep_init(struct grep_opt *opt, struct repository *repo)
{
- *opt = grep_defaults;
+ struct grep_opt blank = GREP_OPT_INIT;
+ memcpy(opt, &blank, sizeof(*opt));
opt->repo = repo;
- opt->prefix = prefix;
- opt->prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
opt->pattern_tail = &opt->pattern_list;
opt->header_tail = &opt->header_list;
}
-static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
-{
- /*
- * When committing to the pattern type by setting the relevant
- * fields in grep_opt it's generally not necessary to zero out
- * the fields we're not choosing, since they won't have been
- * set by anything. The extended_regexp_option field is the
- * only exception to this.
- *
- * This is because in the process of parsing grep.patternType
- * & grep.extendedRegexp we set opt->pattern_type_option and
- * opt->extended_regexp_option, respectively. We then
- * internally use opt->extended_regexp_option to see if we're
- * compiling an ERE. It must be unset if that's not actually
- * the case.
- */
- if (pattern_type != GREP_PATTERN_TYPE_ERE &&
- opt->extended_regexp_option)
- opt->extended_regexp_option = 0;
-
- switch (pattern_type) {
- case GREP_PATTERN_TYPE_UNSPECIFIED:
- /* fall through */
-
- case GREP_PATTERN_TYPE_BRE:
- break;
-
- case GREP_PATTERN_TYPE_ERE:
- opt->extended_regexp_option = 1;
- break;
-
- case GREP_PATTERN_TYPE_FIXED:
- opt->fixed = 1;
- break;
-
- case GREP_PATTERN_TYPE_PCRE:
- opt->pcre2 = 1;
- break;
- }
-}
-
-void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
-{
- if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
- grep_set_pattern_type_option(pattern_type, opt);
- else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
- grep_set_pattern_type_option(opt->pattern_type_option, opt);
- else if (opt->extended_regexp_option)
- /*
- * This branch *must* happen after setting from the
- * opt->pattern_type_option above, we don't want
- * grep.extendedRegexp to override grep.patternType!
- */
- grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
-}
-
static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
const char *origin, int no,
enum grep_pat_token t,
if (!opt->ignore_locale && is_utf8_locale() && !literal)
options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF);
-#ifdef GIT_PCRE2_VERSION_10_36_OR_HIGHER
+#ifndef GIT_PCRE2_VERSION_10_36_OR_HIGHER
/* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */
if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS))
options |= PCRE2_NO_START_OPTIMIZE;
int err;
int regflags = REG_NEWLINE;
+ if (opt->pattern_type_option == GREP_PATTERN_TYPE_UNSPECIFIED)
+ opt->pattern_type_option = (opt->extended_regexp_option
+ ? GREP_PATTERN_TYPE_ERE
+ : GREP_PATTERN_TYPE_BRE);
+
p->word_regexp = opt->word_regexp;
p->ignore_case = opt->ignore_case;
- p->fixed = opt->fixed;
+ p->fixed = opt->pattern_type_option == GREP_PATTERN_TYPE_FIXED;
- if (memchr(p->pattern, 0, p->patternlen) && !opt->pcre2)
+ if (opt->pattern_type_option != GREP_PATTERN_TYPE_PCRE &&
+ memchr(p->pattern, 0, p->patternlen))
die(_("given pattern contains NULL byte (via -f <file>). This is only supported with -P under PCRE v2"));
p->is_fixed = is_fixed(p->pattern, p->patternlen);
return;
}
- if (opt->pcre2) {
+ if (opt->pattern_type_option == GREP_PATTERN_TYPE_PCRE) {
compile_pcre2_pattern(p, opt);
return;
}
if (p->ignore_case)
regflags |= REG_ICASE;
- if (opt->extended_regexp_option)
+ if (opt->pattern_type_option == GREP_PATTERN_TYPE_ERE)
regflags |= REG_EXTENDED;
err = regcomp(&p->regexp, p->pattern, regflags);
if (err) {
}
}
+static struct grep_expr *grep_not_expr(struct grep_expr *expr)
+{
+ struct grep_expr *z = xcalloc(1, sizeof(*z));
+ z->node = GREP_NODE_NOT;
+ z->u.unary = expr;
+ return z;
+}
+
+static struct grep_expr *grep_binexp(enum grep_expr_node kind,
+ struct grep_expr *left,
+ struct grep_expr *right)
+{
+ struct grep_expr *z = xcalloc(1, sizeof(*z));
+ z->node = kind;
+ z->u.binary.left = left;
+ z->u.binary.right = right;
+ return z;
+}
+
+static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
+{
+ return grep_binexp(GREP_NODE_OR, left, right);
+}
+
+static struct grep_expr *grep_and_expr(struct grep_expr *left, struct grep_expr *right)
+{
+ return grep_binexp(GREP_NODE_AND, left, right);
+}
+
static struct grep_expr *compile_pattern_or(struct grep_pat **);
static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
{
if (!p->next)
die("--not not followed by pattern expression");
*list = p->next;
- CALLOC_ARRAY(x, 1);
- x->node = GREP_NODE_NOT;
- x->u.unary = compile_pattern_not(list);
- if (!x->u.unary)
+ x = compile_pattern_not(list);
+ if (!x)
die("--not followed by non pattern expression");
- return x;
+ return grep_not_expr(x);
default:
return compile_pattern_atom(list);
}
static struct grep_expr *compile_pattern_and(struct grep_pat **list)
{
struct grep_pat *p;
- struct grep_expr *x, *y, *z;
+ struct grep_expr *x, *y;
x = compile_pattern_not(list);
p = *list;
y = compile_pattern_and(list);
if (!y)
die("--and not followed by pattern expression");
- CALLOC_ARRAY(z, 1);
- z->node = GREP_NODE_AND;
- z->u.binary.left = x;
- z->u.binary.right = y;
- return z;
+ return grep_and_expr(x, y);
}
return x;
}
static struct grep_expr *compile_pattern_or(struct grep_pat **list)
{
struct grep_pat *p;
- struct grep_expr *x, *y, *z;
+ struct grep_expr *x, *y;
x = compile_pattern_and(list);
p = *list;
y = compile_pattern_or(list);
if (!y)
die("not a pattern expression %s", p->pattern);
- CALLOC_ARRAY(z, 1);
- z->node = GREP_NODE_OR;
- z->u.binary.left = x;
- z->u.binary.right = y;
- return z;
+ return grep_or_expr(x, y);
}
return x;
}
return compile_pattern_or(list);
}
-static struct grep_expr *grep_not_expr(struct grep_expr *expr)
-{
- struct grep_expr *z = xcalloc(1, sizeof(*z));
- z->node = GREP_NODE_NOT;
- z->u.unary = expr;
- return z;
-}
-
static struct grep_expr *grep_true_expr(void)
{
struct grep_expr *z = xcalloc(1, sizeof(*z));
return z;
}
-static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
-{
- struct grep_expr *z = xcalloc(1, sizeof(*z));
- z->node = GREP_NODE_OR;
- z->u.binary.left = left;
- z->u.binary.right = right;
- return z;
-}
-
static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
{
struct grep_pat *p;
*/
struct repository *repo;
- const char *prefix;
- int prefix_length;
- regex_t regexp;
int linenum;
int columnnum;
int invert;
int unmatch_name_only;
int count;
int word_regexp;
- int fixed;
int all_match;
int no_body_match;
int body_hit;
int allow_textconv;
int extended;
int use_reflog_filter;
- int pcre2;
int relative;
int pathname;
int null_following_name;
int funcname;
int funcbody;
int extended_regexp_option;
- int pattern_type_option;
+ enum grep_pattern_type pattern_type_option;
int ignore_locale;
char colors[NR_GREP_COLORS][COLOR_MAXLEN];
unsigned pre_context;
void *output_priv;
};
+#define GREP_OPT_INIT { \
+ .relative = 1, \
+ .pathname = 1, \
+ .max_depth = -1, \
+ .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \
+ .colors = { \
+ [GREP_COLOR_CONTEXT] = "", \
+ [GREP_COLOR_FILENAME] = GIT_COLOR_MAGENTA, \
+ [GREP_COLOR_FUNCTION] = "", \
+ [GREP_COLOR_LINENO] = GIT_COLOR_GREEN, \
+ [GREP_COLOR_COLUMNNO] = GIT_COLOR_GREEN, \
+ [GREP_COLOR_MATCH_CONTEXT] = GIT_COLOR_BOLD_RED, \
+ [GREP_COLOR_MATCH_SELECTED] = GIT_COLOR_BOLD_RED, \
+ [GREP_COLOR_SELECTED] = "", \
+ [GREP_COLOR_SEP] = GIT_COLOR_CYAN, \
+ }, \
+ .only_matching = 0, \
+ .color = -1, \
+ .output = std_output, \
+}
+
int grep_config(const char *var, const char *value, void *);
-void grep_init(struct grep_opt *, struct repository *repo, const char *prefix);
-void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt);
+void grep_init(struct grep_opt *, struct repository *repo);
void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
uint32_t mask = catdesc[i].category;
const char *desc = catdesc[i].desc;
- printf("\n%s\n", _(desc));
+ if (i)
+ putchar('\n');
+ puts(_(desc));
print_command_list(cmds, mask, longest);
}
free(cmds);
}
if (other_cmds->cnt) {
- printf_ln(_("git commands available from elsewhere on your $PATH"));
+ puts(_("git commands available from elsewhere on your $PATH"));
putchar('\n');
pretty_print_cmdnames(other_cmds, colopts);
putchar('\n');
void list_common_cmds_help(void)
{
puts(_("These are common Git commands used in various situations:"));
+ putchar('\n');
print_cmd_by_category(common_categories, NULL);
}
return 0;
}
-void list_all_cmds_help(void)
+static void list_all_cmds_help_external_commands(void)
{
struct string_list others = STRING_LIST_INIT_DUP;
- struct string_list alias_list = STRING_LIST_INIT_DUP;
- struct cmdname_help *aliases;
- int i, longest;
-
- printf_ln(_("See 'git help <command>' to read about a specific subcommand"));
- print_cmd_by_category(main_categories, &longest);
+ int i;
list_all_other_cmds(&others);
if (others.nr)
for (i = 0; i < others.nr; i++)
printf(" %s\n", others.items[i].string);
string_list_clear(&others, 0);
+}
+
+static void list_all_cmds_help_aliases(int longest)
+{
+ struct string_list alias_list = STRING_LIST_INIT_DUP;
+ struct cmdname_help *aliases;
+ int i;
git_config(get_alias, &alias_list);
string_list_sort(&alias_list);
string_list_clear(&alias_list, 1);
}
+void list_all_cmds_help(int show_external_commands, int show_aliases)
+{
+ int longest;
+
+ puts(_("See 'git help <command>' to read about a specific subcommand"));
+ putchar('\n');
+ print_cmd_by_category(main_categories, &longest);
+
+ if (show_external_commands)
+ list_all_cmds_help_external_commands();
+ if (show_aliases)
+ list_all_cmds_help_aliases(longest);
+}
+
int is_in_cmdlist(struct cmdnames *c, const char *s)
{
int i;
}
void list_common_cmds_help(void);
-void list_all_cmds_help(void);
+void list_all_cmds_help(int show_external_commands, int show_aliases);
void list_guides_help(void);
void list_all_main_cmds(struct string_list *list);
#include "cache.h"
#include "hook.h"
#include "run-command.h"
+#include "config.h"
const char *find_hook(const char *name)
{
{
return !!find_hook(name);
}
+
+static int pick_next_hook(struct child_process *cp,
+ struct strbuf *out,
+ void *pp_cb,
+ void **pp_task_cb)
+{
+ struct hook_cb_data *hook_cb = pp_cb;
+ const char *hook_path = hook_cb->hook_path;
+
+ if (!hook_path)
+ return 0;
+
+ cp->no_stdin = 1;
+ strvec_pushv(&cp->env_array, hook_cb->options->env.v);
+ cp->stdout_to_stderr = 1;
+ cp->trace2_hook_name = hook_cb->hook_name;
+ cp->dir = hook_cb->options->dir;
+
+ strvec_push(&cp->args, hook_path);
+ strvec_pushv(&cp->args, hook_cb->options->args.v);
+
+ /* Provide context for errors if necessary */
+ *pp_task_cb = (char *)hook_path;
+
+ /*
+ * This pick_next_hook() will be called again, we're only
+ * running one hook, so indicate that no more work will be
+ * done.
+ */
+ hook_cb->hook_path = NULL;
+
+ return 1;
+}
+
+static int notify_start_failure(struct strbuf *out,
+ void *pp_cb,
+ void *pp_task_cp)
+{
+ struct hook_cb_data *hook_cb = pp_cb;
+ const char *hook_path = pp_task_cp;
+
+ hook_cb->rc |= 1;
+
+ strbuf_addf(out, _("Couldn't start hook '%s'\n"),
+ hook_path);
+
+ return 1;
+}
+
+static int notify_hook_finished(int result,
+ struct strbuf *out,
+ void *pp_cb,
+ void *pp_task_cb)
+{
+ struct hook_cb_data *hook_cb = pp_cb;
+
+ hook_cb->rc |= result;
+
+ return 0;
+}
+
+static void run_hooks_opt_clear(struct run_hooks_opt *options)
+{
+ strvec_clear(&options->env);
+ strvec_clear(&options->args);
+}
+
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
+{
+ struct strbuf abs_path = STRBUF_INIT;
+ struct hook_cb_data cb_data = {
+ .rc = 0,
+ .hook_name = hook_name,
+ .options = options,
+ };
+ const char *const hook_path = find_hook(hook_name);
+ int jobs = 1;
+ int ret = 0;
+
+ if (!options)
+ BUG("a struct run_hooks_opt must be provided to run_hooks");
+
+ if (!hook_path && !options->error_if_missing)
+ goto cleanup;
+
+ if (!hook_path) {
+ ret = error("cannot find a hook named %s", hook_name);
+ goto cleanup;
+ }
+
+ cb_data.hook_path = hook_path;
+ if (options->dir) {
+ strbuf_add_absolute_path(&abs_path, hook_path);
+ cb_data.hook_path = abs_path.buf;
+ }
+
+ run_processes_parallel_tr2(jobs,
+ pick_next_hook,
+ notify_start_failure,
+ notify_hook_finished,
+ &cb_data,
+ "hook",
+ hook_name);
+ ret = cb_data.rc;
+cleanup:
+ strbuf_release(&abs_path);
+ run_hooks_opt_clear(options);
+ return ret;
+}
+
+int run_hooks(const char *hook_name)
+{
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+ return run_hooks_opt(hook_name, &opt);
+}
+
+int run_hooks_l(const char *hook_name, ...)
+{
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+ va_list ap;
+ const char *arg;
+
+ va_start(ap, hook_name);
+ while ((arg = va_arg(ap, const char *)))
+ strvec_push(&opt.args, arg);
+ va_end(ap);
+
+ return run_hooks_opt(hook_name, &opt);
+}
#ifndef HOOK_H
#define HOOK_H
+#include "strvec.h"
+
+struct run_hooks_opt
+{
+ /* Environment vars to be set for each hook */
+ struct strvec env;
+
+ /* Args to be passed to each hook */
+ struct strvec args;
+
+ /* Emit an error if the hook is missing */
+ unsigned int error_if_missing:1;
+
+ /**
+ * An optional initial working directory for the hook,
+ * translates to "struct child_process"'s "dir" member.
+ */
+ const char *dir;
+};
+
+#define RUN_HOOKS_OPT_INIT { \
+ .env = STRVEC_INIT, \
+ .args = STRVEC_INIT, \
+}
+
+struct hook_cb_data {
+ /* rc reflects the cumulative failure state */
+ int rc;
+ const char *hook_name;
+ const char *hook_path;
+ struct run_hooks_opt *options;
+};
/*
* Returns the path to the hook file, or NULL if the hook is missing
*/
int hook_exists(const char *hookname);
+/**
+ * Takes a `hook_name`, resolves it to a path with find_hook(), and
+ * runs the hook for you with the options specified in "struct
+ * run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
+ *
+ * Returns the status code of the run hook, or a negative value on
+ * error().
+ */
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
+
+/**
+ * A wrapper for run_hooks_opt() which provides a dummy "struct
+ * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
+ */
+int run_hooks(const char *hook_name);
+
+/**
+ * Like run_hooks(), a wrapper for run_hooks_opt().
+ *
+ * In addition to the wrapping behavior provided by run_hooks(), this
+ * wrapper takes a list of strings terminated by a NULL
+ * argument. These things will be used as positional arguments to the
+ * hook. This function behaves like the old run_hook_le() API.
+ */
+int run_hooks_l(const char *hook_name, ...);
#endif
#include "packfile.h"
#include "object-store.h"
#include "protocol.h"
+#include "date.h"
static const char content_type[] = "Content-Type";
static const char content_length[] = "Content-Length";
*/
#include "cache.h"
#include "config.h"
+#include "date.h"
static struct strbuf git_default_name = STRBUF_INIT;
static struct strbuf git_default_email = STRBUF_INIT;
};
static struct imap_server_conf server = {
- NULL, /* name */
- NULL, /* tunnel */
- NULL, /* host */
- 0, /* port */
- NULL, /* folder */
- NULL, /* user */
- NULL, /* pass */
- 0, /* use_ssl */
- 1, /* ssl_verify */
- 0, /* use_html */
- NULL, /* auth_method */
+ .ssl_verify = 1,
};
struct imap_socket {
struct ll_merge_driver;
-typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
/*
* Built-in low-levels
*/
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv_unused,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
const struct ll_merge_options *opts,
int marker_size)
{
+ enum ll_merge_result ret;
mmfile_t *stolen;
assert(opts);
*/
if (opts->virtual_ancestor) {
stolen = orig;
+ ret = LL_MERGE_OK;
} else {
switch (opts->variant) {
default:
- warning("Cannot merge binary files: %s (%s vs. %s)",
- path, name1, name2);
- /* fallthru */
+ ret = LL_MERGE_BINARY_CONFLICT;
+ stolen = src1;
+ break;
case XDL_MERGE_FAVOR_OURS:
+ ret = LL_MERGE_OK;
stolen = src1;
break;
case XDL_MERGE_FAVOR_THEIRS:
+ ret = LL_MERGE_OK;
stolen = src2;
break;
}
result->size = stolen->size;
stolen->ptr = NULL;
- /*
- * With -Xtheirs or -Xours, we have cleanly merged;
- * otherwise we got a conflict.
- */
- return opts->variant == XDL_MERGE_FAVOR_OURS ||
- opts->variant == XDL_MERGE_FAVOR_THEIRS ?
- 0 : 1;
+ return ret;
}
-static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
const struct ll_merge_options *opts,
int marker_size)
{
+ enum ll_merge_result ret;
xmparam_t xmp;
+ int status;
assert(opts);
if (orig->size > MAX_XDIFF_SIZE ||
xmp.ancestor = orig_name;
xmp.file1 = name1;
xmp.file2 = name2;
- return xdl_merge(orig, src1, src2, &xmp, result);
+ status = xdl_merge(orig, src1, src2, &xmp, result);
+ ret = (status > 0) ? LL_MERGE_CONFLICT : status;
+ return ret;
}
-static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
/*
* User defined low-level merge driver support.
*/
-static int ll_ext_merge(const struct ll_merge_driver *fn,
+static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
+ enum ll_merge_result ret;
assert(opts);
sq_quote_buf(&path_sq, path);
unlink_or_warn(temp[i]);
strbuf_release(&cmd);
strbuf_release(&path_sq);
- return status;
+ ret = (status > 0) ? LL_MERGE_CONFLICT : status;
+ return ret;
}
/*
}
}
-int ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
long xdl_opts;
};
+enum ll_merge_result {
+ LL_MERGE_ERROR = -1,
+ LL_MERGE_OK = 0,
+ LL_MERGE_CONFLICT,
+ LL_MERGE_BINARY_CONFLICT,
+};
+
/**
* Perform a three-way single-file merge in core. This is a thin wrapper
* around `xdl_merge` that takes the path and any merge backend specified in
* `.gitattributes` or `.git/info/attributes` into account.
* Returns 0 for a clean merge.
*/
-int ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
#include "cache.h"
+#include "commit-reach.h"
#include "config.h"
#include "diff.h"
#include "object-store.h"
#include "repository.h"
+#include "tmp-objdir.h"
#include "commit.h"
#include "tag.h"
#include "graph.h"
#include "log-tree.h"
+#include "merge-ort.h"
#include "reflog-walk.h"
#include "refs.h"
#include "string-list.h"
#include "line-log.h"
#include "help.h"
#include "range-diff.h"
+#include "strmap.h"
static struct decoration name_decoration = { "object names" };
static int decoration_loaded;
opt->shown_dashes = 0;
diffcore_std(&opt->diffopt);
- if (diff_queue_is_empty()) {
+ if (diff_queue_is_empty(&opt->diffopt)) {
int saved_fmt = opt->diffopt.output_format;
opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_flush(&opt->diffopt);
return !opt->loginfo;
}
+static void setup_additional_headers(struct diff_options *o,
+ struct strmap *all_headers)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ /*
+ * Make o->additional_path_headers contain the subset of all_headers
+ * that match o->pathspec. If there aren't any that match o->pathspec,
+ * then make o->additional_path_headers be NULL.
+ */
+
+ if (!o->pathspec.nr) {
+ o->additional_path_headers = all_headers;
+ return;
+ }
+
+ o->additional_path_headers = xmalloc(sizeof(struct strmap));
+ strmap_init_with_options(o->additional_path_headers, NULL, 0);
+ strmap_for_each_entry(all_headers, &iter, entry) {
+ if (match_pathspec(the_repository->index, &o->pathspec,
+ entry->key, strlen(entry->key),
+ 0 /* prefix */, NULL /* seen */,
+ 0 /* is_dir */))
+ strmap_put(o->additional_path_headers,
+ entry->key, entry->value);
+ }
+ if (!strmap_get_size(o->additional_path_headers)) {
+ strmap_clear(o->additional_path_headers, 0);
+ FREE_AND_NULL(o->additional_path_headers);
+ }
+}
+
+static void cleanup_additional_headers(struct diff_options *o)
+{
+ if (!o->pathspec.nr) {
+ o->additional_path_headers = NULL;
+ return;
+ }
+ if (!o->additional_path_headers)
+ return;
+
+ strmap_clear(o->additional_path_headers, 0);
+ FREE_AND_NULL(o->additional_path_headers);
+}
+
+static int do_remerge_diff(struct rev_info *opt,
+ struct commit_list *parents,
+ struct object_id *oid,
+ struct commit *commit)
+{
+ struct merge_options o;
+ struct commit_list *bases;
+ struct merge_result res = {0};
+ struct pretty_print_context ctx = {0};
+ struct commit *parent1 = parents->item;
+ struct commit *parent2 = parents->next->item;
+ struct strbuf parent1_desc = STRBUF_INIT;
+ struct strbuf parent2_desc = STRBUF_INIT;
+
+ /* Setup merge options */
+ init_merge_options(&o, the_repository);
+ o.show_rename_progress = 0;
+ o.record_conflict_msgs_as_headers = 1;
+ o.msg_header_prefix = "remerge";
+
+ ctx.abbrev = DEFAULT_ABBREV;
+ format_commit_message(parent1, "%h (%s)", &parent1_desc, &ctx);
+ format_commit_message(parent2, "%h (%s)", &parent2_desc, &ctx);
+ o.branch1 = parent1_desc.buf;
+ o.branch2 = parent2_desc.buf;
+
+ /* Parse the relevant commits and get the merge bases */
+ parse_commit_or_die(parent1);
+ parse_commit_or_die(parent2);
+ bases = get_merge_bases(parent1, parent2);
+
+ /* Re-merge the parents */
+ merge_incore_recursive(&o, bases, parent1, parent2, &res);
+
+ /* Show the diff */
+ setup_additional_headers(&opt->diffopt, res.path_messages);
+ diff_tree_oid(&res.tree->object.oid, oid, "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+
+ /* Cleanup */
+ cleanup_additional_headers(&opt->diffopt);
+ strbuf_release(&parent1_desc);
+ strbuf_release(&parent2_desc);
+ merge_finalize(&o, &res);
+
+ /* Clean up the contents of the temporary object directory */
+ if (opt->remerge_objdir)
+ tmp_objdir_discard_objects(opt->remerge_objdir);
+ else
+ BUG("did a remerge diff without remerge_objdir?!?");
+
+ return !opt->loginfo;
+}
+
/*
* Show the diff of a commit.
*
}
if (is_merge) {
+ int octopus = (parents->next->next != NULL);
+
+ if (opt->remerge_diff) {
+ if (octopus) {
+ show_log(opt);
+ fprintf(opt->diffopt.file,
+ "diff: warning: Skipping remerge-diff "
+ "for octopus merges.\n");
+ return 1;
+ }
+ return do_remerge_diff(opt, parents, oid, commit);
+ }
if (opt->combine_merges)
return do_diff_combined(opt, commit);
if (opt->separate_merges) {
} else if (!strcmp(str, "ignore")) {
/* do nothing */
} else {
- die(_("invalid value '%s' for lsrefs.unborn"), str);
+ die(_("invalid value for '%s': '%s'"),
+ "lsrefs.unborn", str);
}
}
config_read = 1;
#define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block))
+/*
+ * The inner union is an approximation for C11's max_align_t, and the
+ * struct + offsetof computes _Alignof. This can all just be replaced
+ * with _Alignof(max_align_t) if/when C11 is part of the baseline.
+ * Note that _Alignof(X) need not be the same as sizeof(X); it's only
+ * required to be a (possibly trivial) factor. They are the same for
+ * most architectures, but m68k for example has only 2-byte alignment
+ * for its 4-byte and 8-byte types, so using sizeof would waste space.
+ *
+ * Add more types to the union if the current set is insufficient.
+ */
+struct git_max_alignment {
+ char unalign;
+ union {
+ uintmax_t max_align_uintmax;
+ void *max_align_pointer;
+ } aligned;
+};
+#define GIT_MAX_ALIGNMENT offsetof(struct git_max_alignment, aligned)
+
/*
* Allocate a new mp_block and insert it after the block specified in
* `insert_after`. If `insert_after` is NULL, then insert block at the
struct mp_block *p = NULL;
void *r;
- /* round up to a 'uintmax_t' alignment */
- if (len & (sizeof(uintmax_t) - 1))
- len += sizeof(uintmax_t) - (len & (sizeof(uintmax_t) - 1));
+ /* round up to a 'GIT_MAX_ALIGNMENT' alignment */
+ if (len & (GIT_MAX_ALIGNMENT - 1))
+ len += GIT_MAX_ALIGNMENT - (len & (GIT_MAX_ALIGNMENT - 1));
if (pool->mp_block &&
pool->mp_block->end - pool->mp_block->next_free >= len)
mmfile_t *their,
unsigned long *size)
{
- int merge_status;
+ enum ll_merge_result merge_status;
mmbuffer_t res;
/*
istate, NULL);
if (merge_status < 0)
return NULL;
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ path, ".our", ".their");
*size = res.size;
return res.ptr;
const char *fmt, ...)
{
va_list ap;
- struct strbuf *sb = strmap_get(&opt->priv->output, path);
+ struct strbuf *sb, *dest;
+ struct strbuf tmp = STRBUF_INIT;
+
+ if (opt->record_conflict_msgs_as_headers && omittable_hint)
+ return; /* Do not record mere hints in headers */
+ if (opt->priv->call_depth && opt->verbosity < 5)
+ return; /* Ignore messages from inner merges */
+
+ sb = strmap_get(&opt->priv->output, path);
if (!sb) {
sb = xmalloc(sizeof(*sb));
strbuf_init(sb, 0);
strmap_put(&opt->priv->output, path, sb);
}
+ dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
+
va_start(ap, fmt);
- strbuf_vaddf(sb, fmt, ap);
+ if (opt->priv->call_depth) {
+ strbuf_addchars(dest, ' ', 2);
+ strbuf_addstr(dest, "From inner merge:");
+ strbuf_addchars(dest, ' ', opt->priv->call_depth * 2);
+ }
+ strbuf_vaddf(dest, fmt, ap);
va_end(ap);
+ if (opt->record_conflict_msgs_as_headers) {
+ int i_sb = 0, i_tmp = 0;
+
+ /* Start with the specified prefix */
+ if (opt->msg_header_prefix)
+ strbuf_addf(sb, "%s ", opt->msg_header_prefix);
+
+ /* Copy tmp to sb, adding spaces after newlines */
+ strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */
+ for (; i_tmp < tmp.len; i_tmp++, i_sb++) {
+ /* Copy next character from tmp to sb */
+ sb->buf[sb->len + i_sb] = tmp.buf[i_tmp];
+
+ /* If we copied a newline, add a space */
+ if (tmp.buf[i_tmp] == '\n')
+ sb->buf[++i_sb] = ' ';
+ }
+ /* Update length and ensure it's NUL-terminated */
+ sb->len += i_sb;
+ sb->buf[sb->len] = '\0';
+
+ strbuf_release(&tmp);
+ }
+
+ /* Add final newline character to sb */
strbuf_addch(sb, '\n');
}
out->buf[i] = '_';
}
-static char *unique_path(struct strmap *existing_paths,
+static char *unique_path(struct merge_options *opt,
const char *path,
const char *branch)
{
+ char *ret = NULL;
struct strbuf newpath = STRBUF_INIT;
int suffix = 0;
size_t base_len;
+ struct strmap *existing_paths = &opt->priv->paths;
strbuf_addf(&newpath, "%s~", path);
add_flattened_path(&newpath, branch);
strbuf_addf(&newpath, "_%d", suffix++);
}
- return strbuf_detach(&newpath, NULL);
+ /* Track the new path in our memory pool */
+ ret = mem_pool_alloc(&opt->priv->pool, newpath.len + 1);
+ memcpy(ret, newpath.buf, newpath.len + 1);
+ strbuf_release(&newpath);
+ return ret;
}
/*** Function Grouping: functions related to collect_merge_info() ***/
mmfile_t orig, src1, src2;
struct ll_merge_options ll_opts = {0};
char *base, *name1, *name2;
- int merge_status;
+ enum ll_merge_result merge_status;
if (!opt->priv->attr_index.initialized)
initialize_attr_index(opt);
merge_status = ll_merge(result_buf, path, &orig, base,
&src1, name1, &src2, name2,
&opt->priv->attr_index, &ll_opts);
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ path_msg(opt, path, 0,
+ "warning: Cannot merge binary files: %s (%s vs. %s)",
+ path, name1, name2);
free(base);
free(name1);
*/
ci->path_conflict = 1;
if (pair->status == 'A')
- path_msg(opt, new_path, 0,
+ path_msg(opt, new_path, 1,
_("CONFLICT (file location): %s added in %s "
"inside a directory that was renamed in %s, "
"suggesting it should perhaps be moved to "
old_path, branch_with_new_path,
branch_with_dir_rename, new_path);
else
- path_msg(opt, new_path, 0,
+ path_msg(opt, new_path, 1,
_("CONFLICT (file location): %s renamed to %s "
"in %s, inside a directory that was renamed "
"in %s, suggesting it should perhaps be "
struct tree *side1,
struct tree *side2)
{
- struct diff_queue_struct combined;
+ struct diff_queue_struct combined = { 0 };
struct rename_info *renames = &opt->priv->renames;
- int need_dir_renames, s, clean = 1;
+ int need_dir_renames, s, i, clean = 1;
unsigned detection_run = 0;
- memset(&combined, 0, sizeof(combined));
if (!possible_renames(renames))
goto cleanup;
trace2_region_enter("merge", "regular renames", opt->repo);
detection_run |= detect_regular_renames(opt, MERGE_SIDE1);
detection_run |= detect_regular_renames(opt, MERGE_SIDE2);
+ if (renames->needed_limit) {
+ renames->cached_pairs_valid_side = 0;
+ renames->redo_after_renames = 0;
+ }
if (renames->redo_after_renames && detection_run) {
int i, side;
struct diff_filepair *p;
free(renames->pairs[s].queue);
DIFF_QUEUE_CLEAR(&renames->pairs[s]);
}
- if (combined.nr) {
- int i;
- for (i = 0; i < combined.nr; i++)
- pool_diff_free_filepair(&opt->priv->pool,
- combined.queue[i]);
- free(combined.queue);
- }
+ for (i = 0; i < combined.nr; i++)
+ pool_diff_free_filepair(&opt->priv->pool, combined.queue[i]);
+ free(combined.queue);
return clean;
}
*/
df_file_index = (ci->dirmask & (1 << 1)) ? 2 : 1;
branch = (df_file_index == 1) ? opt->branch1 : opt->branch2;
- path = unique_path(&opt->priv->paths, path, branch);
+ path = unique_path(opt, path, branch);
strmap_put(&opt->priv->paths, path, new_ci);
path_msg(opt, path, 0,
/* Insert entries into opt->priv_paths */
assert(rename_a || rename_b);
if (rename_a) {
- a_path = unique_path(&opt->priv->paths,
- path, opt->branch1);
+ a_path = unique_path(opt, path, opt->branch1);
strmap_put(&opt->priv->paths, a_path, ci);
}
if (rename_b)
- b_path = unique_path(&opt->priv->paths,
- path, opt->branch2);
+ b_path = unique_path(opt, path, opt->branch2);
else
b_path = path;
strmap_put(&opt->priv->paths, b_path, new_ci);
struct stat st;
if (!lstat(path, &st)) {
- char *new_name = unique_path(&opt->priv->paths,
+ char *new_name = unique_path(opt,
path,
"cruft");
_("Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s"),
path, new_name);
errs |= rename(path, new_name);
- free(new_name);
}
errs |= checkout_entry(ce, &state, NULL, NULL);
}
struct string_list olist = STRING_LIST_INIT_NODUP;
int i;
+ if (opt->record_conflict_msgs_as_headers)
+ BUG("Either display conflict messages or record them as headers, not both");
+
trace2_region_enter("merge", "display messages", opt->repo);
/* Hack to pre-allocate olist to the desired size */
assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&
opt->recursive_variant <= MERGE_VARIANT_THEIRS);
+ if (opt->msg_header_prefix)
+ assert(opt->record_conflict_msgs_as_headers);
+
/*
* detect_renames, verbosity, buffer_output, and obuf are ignored
* fields that were used by "recursive" rather than "ort" -- but
trace2_region_leave("merge", "process_entries", opt->repo);
/* Set return values */
+ result->path_messages = &opt->priv->output;
result->tree = parse_tree_indirect(&working_tree_oid);
/* existence of conflicted entries implies unclean */
result->clean &= strmap_empty(&opt->priv->conflicted);
struct commit *h2,
struct merge_result *result)
{
- struct commit_list *iter;
+ struct commit *next;
struct commit *merged_merge_bases;
const char *ancestor_name;
struct strbuf merge_base_abbrev = STRBUF_INIT;
ancestor_name = merge_base_abbrev.buf;
}
- for (iter = merge_bases; iter; iter = iter->next) {
+ for (next = pop_commit(&merge_bases); next;
+ next = pop_commit(&merge_bases)) {
const char *saved_b1, *saved_b2;
struct commit *prev = merged_merge_bases;
saved_b2 = opt->branch2;
opt->branch1 = "Temporary merge branch 1";
opt->branch2 = "Temporary merge branch 2";
- merge_ort_internal(opt, NULL, prev, iter->item, result);
+ merge_ort_internal(opt, NULL, prev, next, result);
if (result->clean < 0)
return;
opt->branch1 = saved_b1;
result->tree,
"merged tree");
commit_list_insert(prev, &merged_merge_bases->parents);
- commit_list_insert(iter->item,
- &merged_merge_bases->parents->next);
+ commit_list_insert(next, &merged_merge_bases->parents->next);
clear_or_reinit_internal_opts(opt->priv, 1);
}
struct commit;
struct tree;
+struct strmap;
struct merge_result {
/*
*/
struct tree *tree;
+ /*
+ * Special messages and conflict notices for various paths
+ *
+ * This is a map of pathnames to strbufs. It contains various
+ * warning/conflict/notice messages (possibly multiple per path)
+ * that callers may want to use.
+ */
+ struct strmap *path_messages;
+
/*
* Additional metadata used by merge_switch_to_result() or future calls
* to merge_incore_*(). Includes data needed to update the index (if
mmfile_t orig, src1, src2;
struct ll_merge_options ll_opts = {0};
char *base, *name1, *name2;
- int merge_status;
+ enum ll_merge_result merge_status;
ll_opts.renormalize = opt->renormalize;
ll_opts.extra_marker_size = extra_marker_size;
merge_status = ll_merge(result_buf, a->path, &orig, base,
&src1, name1, &src2, name2,
opt->repo->index, &ll_opts);
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ a->path, name1, name2);
free(base);
free(name1);
assert(opt->priv == NULL);
+ /* Not supported; option specific to merge-ort */
+ assert(!opt->record_conflict_msgs_as_headers);
+ assert(!opt->msg_header_prefix);
+
/* Sanity check on repo state; index must match head */
if (repo_index_has_changes(opt->repo, head, &sb)) {
err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"),
/* miscellaneous control options */
const char *subtree_shift;
unsigned renormalize : 1;
+ unsigned record_conflict_msgs_as_headers : 1;
+ const char *msg_header_prefix;
/* internal fields used by the implementation */
struct merge_options_internal *priv;
#define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
#define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */
#define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */
+#define MIDX_CHUNKID_REVINDEX 0x52494458 /* "RIDX" */
#define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
#define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t))
#define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
pair_chunk(cf, MIDX_CHUNKID_LARGEOFFSETS, &m->chunk_large_offsets);
+ if (git_env_bool("GIT_TEST_MIDX_READ_RIDX", 1))
+ pair_chunk(cf, MIDX_CHUNKID_REVINDEX, &m->chunk_revindex);
+
m->num_objects = ntohl(m->chunk_oid_fanout[255]);
CALLOC_ARRAY(m->pack_names, m->num_packs);
return 0;
}
+static int write_midx_revindex(struct hashfile *f,
+ void *data)
+{
+ struct write_midx_context *ctx = data;
+ uint32_t i;
+
+ for (i = 0; i < ctx->entries_nr; i++)
+ hashwrite_be32(f, ctx->pack_order[i]);
+
+ return 0;
+}
+
struct midx_pack_order_data {
uint32_t nr;
uint32_t pack;
char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
int ret;
+ if (!ctx->entries_nr)
+ BUG("cannot write a bitmap without any objects");
+
if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
options |= BITMAP_OPT_HASH_CACHE;
goto cleanup;
}
+ if (!ctx.entries_nr) {
+ if (flags & MIDX_WRITE_BITMAP)
+ warning(_("refusing to write multi-pack .bitmap without any objects"));
+ flags &= ~(MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP);
+ }
+
cf = init_chunkfile(f);
add_chunk(cf, MIDX_CHUNKID_PACKNAMES, pack_name_concat_len,
(size_t)ctx.num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH,
write_midx_large_offsets);
+ if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) {
+ ctx.pack_order = midx_pack_order(&ctx);
+ add_chunk(cf, MIDX_CHUNKID_REVINDEX,
+ ctx.entries_nr * sizeof(uint32_t),
+ write_midx_revindex);
+ }
+
write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs);
write_chunkfile(cf, &ctx);
finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
free_chunkfile(cf);
- if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))
- ctx.pack_order = midx_pack_order(&ctx);
-
- if (flags & MIDX_WRITE_REV_INDEX)
+ if (flags & MIDX_WRITE_REV_INDEX &&
+ git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0))
write_midx_reverse_index(midx_name.buf, midx_hash, &ctx);
if (flags & MIDX_WRITE_BITMAP) {
if (write_midx_bitmap(midx_name.buf, midx_hash, &ctx,
const unsigned char *chunk_oid_lookup;
const unsigned char *chunk_object_offsets;
const unsigned char *chunk_large_offsets;
+ const unsigned char *chunk_revindex;
const char **pack_names;
struct packed_git **packs;
oid_to_hex(&mp->remote));
}
diff_flush(&opt);
- clear_pathspec(&opt.pathspec);
*num_changes = len;
return changes;
oid_to_hex(&mp->local));
}
diff_flush(&opt);
- clear_pathspec(&opt.pathspec);
}
static void check_notes_merge_worktree(struct notes_merge_options *o)
{
mmbuffer_t result_buf;
mmfile_t base, local, remote;
- int status;
+ enum ll_merge_result status;
read_mmblob(&base, &p->base);
read_mmblob(&local, &p->local);
free(local.ptr);
free(remote.ptr);
+ if (status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ oid_to_hex(&p->obj), o->local_ref, o->remote_ref);
if ((status < 0) || !result_buf.ptr)
die("Failed to execute internal merge");
const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
{
- NULL,
- 0x00000000,
- 0,
- 0,
- 0,
- git_hash_unknown_init,
- git_hash_unknown_clone,
- git_hash_unknown_update,
- git_hash_unknown_final,
- git_hash_unknown_final_oid,
- NULL,
- NULL,
- NULL,
+ .name = NULL,
+ .format_id = 0x00000000,
+ .rawsz = 0,
+ .hexsz = 0,
+ .blksz = 0,
+ .init_fn = git_hash_unknown_init,
+ .clone_fn = git_hash_unknown_clone,
+ .update_fn = git_hash_unknown_update,
+ .final_fn = git_hash_unknown_final,
+ .final_oid_fn = git_hash_unknown_final_oid,
+ .empty_tree = NULL,
+ .empty_blob = NULL,
+ .null_oid = NULL,
},
{
- "sha1",
- GIT_SHA1_FORMAT_ID,
- GIT_SHA1_RAWSZ,
- GIT_SHA1_HEXSZ,
- GIT_SHA1_BLKSZ,
- git_hash_sha1_init,
- git_hash_sha1_clone,
- git_hash_sha1_update,
- git_hash_sha1_final,
- git_hash_sha1_final_oid,
- &empty_tree_oid,
- &empty_blob_oid,
- &null_oid_sha1,
+ .name = "sha1",
+ .format_id = GIT_SHA1_FORMAT_ID,
+ .rawsz = GIT_SHA1_RAWSZ,
+ .hexsz = GIT_SHA1_HEXSZ,
+ .blksz = GIT_SHA1_BLKSZ,
+ .init_fn = git_hash_sha1_init,
+ .clone_fn = git_hash_sha1_clone,
+ .update_fn = git_hash_sha1_update,
+ .final_fn = git_hash_sha1_final,
+ .final_oid_fn = git_hash_sha1_final_oid,
+ .empty_tree = &empty_tree_oid,
+ .empty_blob = &empty_blob_oid,
+ .null_oid = &null_oid_sha1,
},
{
- "sha256",
- GIT_SHA256_FORMAT_ID,
- GIT_SHA256_RAWSZ,
- GIT_SHA256_HEXSZ,
- GIT_SHA256_BLKSZ,
- git_hash_sha256_init,
- git_hash_sha256_clone,
- git_hash_sha256_update,
- git_hash_sha256_final,
- git_hash_sha256_final_oid,
- &empty_tree_oid_sha256,
- &empty_blob_oid_sha256,
- &null_oid_sha256,
+ .name = "sha256",
+ .format_id = GIT_SHA256_FORMAT_ID,
+ .rawsz = GIT_SHA256_RAWSZ,
+ .hexsz = GIT_SHA256_HEXSZ,
+ .blksz = GIT_SHA256_BLKSZ,
+ .init_fn = git_hash_sha256_init,
+ .clone_fn = git_hash_sha256_clone,
+ .update_fn = git_hash_sha256_update,
+ .final_fn = git_hash_sha256_final,
+ .final_oid_fn = git_hash_sha256_final_oid,
+ .empty_tree = &empty_tree_oid_sha256,
+ .empty_blob = &empty_blob_oid_sha256,
+ .null_oid = &null_oid_sha256,
}
};
#include "submodule.h"
#include "midx.h"
#include "commit-reach.h"
+#include "date.h"
static int get_oid_oneline(struct repository *r, const char *, struct object_id *, struct commit_list *);
return 0;
}
+struct ambiguous_output {
+ const struct disambiguate_state *ds;
+ struct strbuf advice;
+ struct strbuf sb;
+};
+
static int show_ambiguous_object(const struct object_id *oid, void *data)
{
- const struct disambiguate_state *ds = data;
- struct strbuf desc = STRBUF_INIT;
+ struct ambiguous_output *state = data;
+ const struct disambiguate_state *ds = state->ds;
+ struct strbuf *advice = &state->advice;
+ struct strbuf *sb = &state->sb;
int type;
+ const char *hash;
if (ds->fn && !ds->fn(ds->repo, oid, ds->cb_data))
return 0;
+ hash = repo_find_unique_abbrev(ds->repo, oid, DEFAULT_ABBREV);
type = oid_object_info(ds->repo, oid, NULL);
+
+ if (type < 0) {
+ /*
+ * TRANSLATORS: This is a line of ambiguous object
+ * output shown when we cannot look up or parse the
+ * object in question. E.g. "deadbeef [bad object]".
+ */
+ strbuf_addf(sb, _("%s [bad object]"), hash);
+ goto out;
+ }
+
+ assert(type == OBJ_TREE || type == OBJ_COMMIT ||
+ type == OBJ_BLOB || type == OBJ_TAG);
+
if (type == OBJ_COMMIT) {
+ struct strbuf date = STRBUF_INIT;
+ struct strbuf msg = STRBUF_INIT;
struct commit *commit = lookup_commit(ds->repo, oid);
+
if (commit) {
struct pretty_print_context pp = {0};
pp.date_mode.type = DATE_SHORT;
- format_commit_message(commit, " %ad - %s", &desc, &pp);
+ format_commit_message(commit, "%ad", &date, &pp);
+ format_commit_message(commit, "%s", &msg, &pp);
}
+
+ /*
+ * TRANSLATORS: This is a line of ambiguous commit
+ * object output. E.g.:
+ *
+ * "deadbeef commit 2021-01-01 - Some Commit Message"
+ */
+ strbuf_addf(sb, _("%s commit %s - %s"), hash, date.buf,
+ msg.buf);
+
+ strbuf_release(&date);
+ strbuf_release(&msg);
} else if (type == OBJ_TAG) {
struct tag *tag = lookup_tag(ds->repo, oid);
- if (!parse_tag(tag) && tag->tag)
- strbuf_addf(&desc, " %s", tag->tag);
+
+ if (!parse_tag(tag) && tag->tag) {
+ /*
+ * TRANSLATORS: This is a line of ambiguous
+ * tag object output. E.g.:
+ *
+ * "deadbeef tag 2022-01-01 - Some Tag Message"
+ *
+ * The second argument is the YYYY-MM-DD found
+ * in the tag.
+ *
+ * The third argument is the "tag" string
+ * from object.c.
+ */
+ strbuf_addf(sb, _("%s tag %s - %s"), hash,
+ show_date(tag->date, 0, DATE_MODE(SHORT)),
+ tag->tag);
+ } else {
+ /*
+ * TRANSLATORS: This is a line of ambiguous
+ * tag object output where we couldn't parse
+ * the tag itself. E.g.:
+ *
+ * "deadbeef [bad tag, could not parse it]"
+ */
+ strbuf_addf(sb, _("%s [bad tag, could not parse it]"),
+ hash);
+ }
+ } else if (type == OBJ_TREE) {
+ /*
+ * TRANSLATORS: This is a line of ambiguous <type>
+ * object output. E.g. "deadbeef tree".
+ */
+ strbuf_addf(sb, _("%s tree"), hash);
+ } else if (type == OBJ_BLOB) {
+ /*
+ * TRANSLATORS: This is a line of ambiguous <type>
+ * object output. E.g. "deadbeef blob".
+ */
+ strbuf_addf(sb, _("%s blob"), hash);
}
- advise(" %s %s%s",
- repo_find_unique_abbrev(ds->repo, oid, DEFAULT_ABBREV),
- type_name(type) ? type_name(type) : "unknown type",
- desc.buf);
- strbuf_release(&desc);
+out:
+ /*
+ * TRANSLATORS: This is line item of ambiguous object output
+ * from describe_ambiguous_object() above. For RTL languages
+ * you'll probably want to swap the "%s" and leading " " space
+ * around.
+ */
+ strbuf_addf(advice, _(" %s\n"), sb->buf);
+
+ strbuf_reset(sb);
return 0;
}
if (!quietly && (status == SHORT_NAME_AMBIGUOUS)) {
struct oid_array collect = OID_ARRAY_INIT;
+ struct ambiguous_output out = {
+ .ds = &ds,
+ .sb = STRBUF_INIT,
+ .advice = STRBUF_INIT,
+ };
error(_("short object ID %s is ambiguous"), ds.hex_pfx);
if (!ds.ambiguous)
ds.fn = NULL;
- advise(_("The candidates are:"));
repo_for_each_abbrev(r, ds.hex_pfx, collect_ambiguous, &collect);
sort_ambiguous_oid_array(r, &collect);
- if (oid_array_for_each(&collect, show_ambiguous_object, &ds))
+ if (oid_array_for_each(&collect, show_ambiguous_object, &out))
BUG("show_ambiguous_object shouldn't return non-zero");
+
+ /*
+ * TRANSLATORS: The argument is the list of ambiguous
+ * objects composed in show_ambiguous_object(). See
+ * its "TRANSLATORS" comments for details.
+ */
+ advise(_("The candidates are:\n%s"), out.advice.buf);
+
oid_array_clear(&collect);
+ strbuf_release(&out.advice);
+ strbuf_release(&out.sb);
}
return status;
const char *cp;
int only_to_die = flags & GET_OID_ONLY_TO_DIE;
- if (only_to_die)
- flags |= GET_OID_QUIETLY;
-
memset(oc, 0, sizeof(*oc));
oc->mode = S_IFINVALID;
strbuf_init(&oc->symlink_path, 0);
ret = get_oid_1(repo, name, namelen, oid, flags);
+ if (!ret && flags & GET_OID_REQUIRE_PATH)
+ die(_("<object>:<path> required, only <object> '%s' given"),
+ name);
if (!ret)
return ret;
/*
{
struct object_context oc;
struct object_id oid;
- get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE,
+ get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY,
prefix, &oid, &oc);
}
QSORT(indexed_commits, indexed_commits_nr, date_compare);
- if (writer.show_progress)
- writer.progress = start_progress("Selecting bitmap commits", 0);
-
if (indexed_commits_nr < 100) {
for (i = 0; i < indexed_commits_nr; ++i)
push_bitmapped_commit(indexed_commits[i]);
return;
}
+ if (writer.show_progress)
+ writer.progress = start_progress("Selecting bitmap commits", 0);
+
for (;;) {
struct commit *chosen = NULL;
cleanup:
munmap(bitmap_git->map, bitmap_git->map_size);
bitmap_git->map_size = 0;
+ bitmap_git->map_pos = 0;
bitmap_git->map = NULL;
+ bitmap_git->midx = NULL;
return -1;
}
munmap(bitmap_git->map, bitmap_git->map_size);
bitmap_git->map = NULL;
bitmap_git->map_size = 0;
+ bitmap_git->map_pos = 0;
+ bitmap_git->pack = NULL;
return -1;
}
{
struct strbuf revindex_name = STRBUF_INIT;
int ret;
+
if (m->revindex_data)
return 0;
+ if (m->chunk_revindex) {
+ /*
+ * If the MIDX `m` has a `RIDX` chunk, then use its contents for
+ * the reverse index instead of trying to load a separate `.rev`
+ * file.
+ *
+ * Note that we do *not* set `m->revindex_map` here, since we do
+ * not want to accidentally call munmap() in the middle of the
+ * MIDX.
+ */
+ trace2_data_string("load_midx_revindex", the_repository,
+ "source", "midx");
+ m->revindex_data = (const uint32_t *)m->chunk_revindex;
+ return 0;
+ }
+
+ trace2_data_string("load_midx_revindex", the_repository,
+ "source", "rev");
+
get_midx_rev_filename(&revindex_name, m);
ret = load_revindex_from_disk(revindex_name.buf,
if (env_workers && *env_workers) {
if (strtol_i(env_workers, 10, num_workers)) {
- die("invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'",
- env_workers);
+ die(_("invalid value for '%s': '%s'"),
+ "GIT_TEST_CHECKOUT_WORKERS", env_workers);
}
if (*num_workers < 1)
*num_workers = online_cpus();
die_message("%s\n", msg); /* The extra \n is intentional */
usage_with_options(usagestr, options);
}
+
+void NORETURN usage_msg_optf(const char * const fmt,
+ const char * const *usagestr,
+ const struct option *options, ...)
+{
+ struct strbuf msg = STRBUF_INIT;
+ va_list ap;
+ va_start(ap, options);
+ strbuf_vaddf(&msg, fmt, ap);
+ va_end(ap);
+
+ usage_msg_opt(msg.buf, usagestr, options);
+}
+
+void die_for_incompatible_opt4(int opt1, const char *opt1_name,
+ int opt2, const char *opt2_name,
+ int opt3, const char *opt3_name,
+ int opt4, const char *opt4_name)
+{
+ int count = 0;
+ const char *options[4];
+
+ if (opt1)
+ options[count++] = opt1_name;
+ if (opt2)
+ options[count++] = opt2_name;
+ if (opt3)
+ options[count++] = opt3_name;
+ if (opt4)
+ options[count++] = opt4_name;
+ switch (count) {
+ case 4:
+ die(_("options '%s', '%s', '%s', and '%s' cannot be used together"),
+ opt1_name, opt2_name, opt3_name, opt4_name);
+ break;
+ case 3:
+ die(_("options '%s', '%s', and '%s' cannot be used together"),
+ options[0], options[1], options[2]);
+ break;
+ case 2:
+ die(_("options '%s' and '%s' cannot be used together"),
+ options[0], options[1]);
+ break;
+ default:
+ break;
+ }
+}
* token to explain the kind of argument this option wants. Does not
* begin in capital letter, and does not end with a full stop.
* Should be wrapped by N_() for translation.
+ * Is automatically enclosed in brackets when printed, unless it
+ * contains any of the following characters: ()<>[]|
+ * E.g. "name" is shown as "<name>" to indicate that a name value
+ * needs to be supplied, not the literal string "name", but
+ * "<start>,<end>" and "(this|that)" are printed verbatim.
*
* `help`::
* the short help associated to what the option does.
const char * const *usagestr,
const struct option *options);
+/**
+ * usage_msg_optf() is like usage_msg_opt() except that the first
+ * argument is a format string, and optional format arguments follow
+ * after the 3rd option.
+ */
+__attribute__((format (printf,1,4)))
+void NORETURN usage_msg_optf(const char *fmt,
+ const char * const *usagestr,
+ const struct option *options, ...);
+
+void die_for_incompatible_opt4(int opt1, const char *opt1_name,
+ int opt2, const char *opt2_name,
+ int opt3, const char *opt3_name,
+ int opt4, const char *opt4_name);
+
+
+static inline void die_for_incompatible_opt3(int opt1, const char *opt1_name,
+ int opt2, const char *opt2_name,
+ int opt3, const char *opt3_name)
+{
+ die_for_incompatible_opt4(opt1, opt1_name,
+ opt2, opt2_name,
+ opt3, opt3_name,
+ 0, "");
+}
+
/*
* Use these assertions for callbacks that expect to be called with NONEG and
* NOARG respectively, and do not otherwise handle the "unset" and "arg"
return r->cached_paths.var; \
}
-struct path_cache {
- const char *squash_msg;
- const char *merge_msg;
- const char *merge_rr;
- const char *merge_mode;
- const char *merge_head;
- const char *merge_autostash;
- const char *auto_merge;
- const char *fetch_head;
- const char *shallow;
-};
-
-#define PATH_CACHE_INIT { 0 }
-
const char *git_path_squash_msg(struct repository *r);
const char *git_path_merge_msg(struct repository *r);
const char *git_path_merge_rr(struct repository *r);
# by searching for it at proper places.
sub _execv_git_cmd { exec('git', @_); }
+sub _is_sig {
+ my ($v, $n) = @_;
+
+ # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot
+ # Git.pm codepath.
+ require POSIX;
+ no strict 'refs';
+ $v == *{"POSIX::$n"}->();
+}
+
# Close pipe to a subprocess.
sub _cmd_close {
my $ctx = shift @_;
} elsif ($? >> 8) {
# The caller should pepper this.
throw Git::Error::Command($ctx, $? >> 8);
+ } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) {
+ # we might e.g. closed a live stream; the command
+ # dying of SIGPIPE would drive us here.
+ } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) {
+ die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe',
+ $?, $? & 127);
+ } elsif ($? & 127) {
+ die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe',
+ $?, $? & 127);
}
- # else we might e.g. closed a live stream; the command
- # dying of SIGPIPE would drive us here.
}
}
#define PRETTY_H
#include "cache.h"
+#include "date.h"
#include "string-list.h"
struct commit;
const char **arg,
char **invalid_arg);
+/*
+ * Like show_date, but pull the timestamp and tz parameters from
+ * the ident_split. It will also sanity-check the values and produce
+ * a well-known sentinel date if they appear bogus.
+ */
+const char *show_ident_date(const struct ident_split *id,
+ const struct date_mode *mode);
+
+
#endif /* PRETTY_H */
static void finish_if_sparse(struct progress *progress)
{
- if (progress &&
- progress->sparse &&
+ if (progress->sparse &&
progress->last_value != progress->total)
display_progress(progress, progress->total);
}
-void stop_progress(struct progress **p_progress)
+static void force_last_update(struct progress *progress, const char *msg)
{
- if (!p_progress)
- BUG("don't provide NULL to stop_progress");
-
- finish_if_sparse(*p_progress);
-
- if (*p_progress) {
- trace2_data_intmax("progress", the_repository, "total_objects",
- (*p_progress)->total);
+ char *buf;
+ struct throughput *tp = progress->throughput;
+
+ if (tp) {
+ uint64_t now_ns = progress_getnanotime(progress);
+ unsigned int misecs, rate;
+ misecs = ((now_ns - progress->start_ns) * 4398) >> 32;
+ rate = tp->curr_total / (misecs ? misecs : 1);
+ throughput_string(&tp->display, tp->curr_total, rate);
+ }
+ progress_update = 1;
+ buf = xstrfmt(", %s.\n", msg);
+ display(progress, progress->last_value, buf);
+ free(buf);
+}
- if ((*p_progress)->throughput)
- trace2_data_intmax("progress", the_repository,
- "total_bytes",
- (*p_progress)->throughput->curr_total);
+static void log_trace2(struct progress *progress)
+{
+ trace2_data_intmax("progress", the_repository, "total_objects",
+ progress->total);
- trace2_region_leave("progress", (*p_progress)->title, the_repository);
- }
+ if (progress->throughput)
+ trace2_data_intmax("progress", the_repository, "total_bytes",
+ progress->throughput->curr_total);
- stop_progress_msg(p_progress, _("done"));
+ trace2_region_leave("progress", progress->title, the_repository);
}
void stop_progress_msg(struct progress **p_progress, const char *msg)
if (!progress)
return;
*p_progress = NULL;
- if (progress->last_value != -1) {
- /* Force the last update */
- char *buf;
- struct throughput *tp = progress->throughput;
-
- if (tp) {
- uint64_t now_ns = progress_getnanotime(progress);
- unsigned int misecs, rate;
- misecs = ((now_ns - progress->start_ns) * 4398) >> 32;
- rate = tp->curr_total / (misecs ? misecs : 1);
- throughput_string(&tp->display, tp->curr_total, rate);
- }
- progress_update = 1;
- buf = xstrfmt(", %s.\n", msg);
- display(progress, progress->last_value, buf);
- free(buf);
- }
+
+ finish_if_sparse(progress);
+ if (progress->last_value != -1)
+ force_last_update(progress, msg);
+ log_trace2(progress);
+
clear_progress_signal();
strbuf_release(&progress->counters_sb);
if (progress->throughput)
#ifndef PROGRESS_H
#define PROGRESS_H
+#include "gettext.h"
struct progress;
struct progress *start_delayed_progress(const char *title, uint64_t total);
struct progress *start_delayed_sparse_progress(const char *title,
uint64_t total);
-void stop_progress(struct progress **progress);
-void stop_progress_msg(struct progress **progress, const char *msg);
-
+void stop_progress_msg(struct progress **p_progress, const char *msg);
+static inline void stop_progress(struct progress **p_progress)
+{
+ stop_progress_msg(p_progress, _("done"));
+}
#endif
char *line, *current_filename = NULL;
ssize_t len;
size_t size;
+ int ret = -1;
strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
"--reverse", "--date-order", "--decorate=no",
if (strbuf_read(&contents, cp.out, 0) < 0) {
error_errno(_("could not read `log` output"));
finish_command(&cp);
- return -1;
+ goto cleanup;
}
if (finish_command(&cp))
- return -1;
+ goto cleanup;
line = contents.buf;
size = contents.len;
CALLOC_ARRAY(util, 1);
if (get_oid(p, &util->oid)) {
error(_("could not parse commit '%s'"), p);
- free(util);
- free(current_filename);
+ FREE_AND_NULL(util);
string_list_clear(list, 1);
- strbuf_release(&buf);
- strbuf_release(&contents);
- return -1;
+ goto cleanup;
}
util->matching = -1;
in_header = 1;
error(_("could not parse first line of `log` output: "
"did not start with 'commit ': '%s'"),
line);
- free(current_filename);
string_list_clear(list, 1);
- strbuf_release(&buf);
- strbuf_release(&contents);
- return -1;
+ goto cleanup;
}
if (starts_with(line, "diff --git")) {
if (len < 0) {
error(_("could not parse git header '%.*s'"),
orig_len, line);
- free(util);
- free(current_filename);
+ FREE_AND_NULL(util);
string_list_clear(list, 1);
- strbuf_release(&buf);
- strbuf_release(&contents);
- return -1;
+ goto cleanup;
}
strbuf_addstr(&buf, " ## ");
if (patch.is_new > 0)
patch.old_mode, patch.new_mode);
strbuf_addstr(&buf, " ##");
+ release_patch(&patch);
} else if (in_header) {
if (starts_with(line, "Author: ")) {
strbuf_addstr(&buf, " ## Metadata ##\n");
strbuf_addch(&buf, '\n');
util->diffsize++;
}
+
+ ret = 0;
+cleanup:
strbuf_release(&contents);
if (util)
strbuf_release(&buf);
free(current_filename);
- return 0;
+ return ret;
}
static int patch_util_cmp(const void *dummy, const struct patch_util *a,
#include "sparse-index.h"
#include "csum-file.h"
#include "promisor-remote.h"
+#include "hook.h"
/* Mask for the name length in ce_flags in the on-disk index */
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
int new_only = option & ADD_CACHE_NEW_ONLY;
- if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
- cache_tree_invalidate_path(istate, ce->name);
-
/*
* If this entry's path sorts after the last entry in the index,
* we can avoid searching for it.
else
pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce), EXPAND_SPARSE);
+ /*
+ * Cache tree path should be invalidated only after index_name_stage_pos,
+ * in case it expands a sparse index.
+ */
+ if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
+ cache_tree_invalidate_path(istate, ce->name);
+
/* existing match? Just replace it. */
if (pos >= 0) {
if (!new_only)
return verify_index_from(repo->index, repo->index_file);
}
-static int has_racy_timestamp(struct index_state *istate)
+int has_racy_timestamp(struct index_state *istate)
{
int entries = istate->cache_nr;
int i;
!is_null_oid(&istate->split_index->base_oid)) {
struct strbuf sb = STRBUF_INIT;
+ if (istate->sparse_index)
+ die(_("cannot write split index for a sparse index"));
+
err = write_link_extension(&sb, istate) < 0 ||
write_index_ext_header(f, eoie_c, CACHE_EXT_LINK,
sb.len) < 0;
else
ret = close_lock_file_gently(lock);
- run_hook_le(NULL, "post-index-change",
+ run_hooks_l("post-index-change",
istate->updated_workdir ? "1" : "0",
istate->updated_skipworktree ? "1" : "0", NULL);
istate->updated_workdir = 0;
char *zone;
timestamp_t timestamp;
long tz;
- struct date_mode date_mode = { DATE_NORMAL };
+ struct date_mode date_mode = DATE_MODE_INIT;
const char *formatp;
/*
goto bad;
v->s = xstrdup(show_date(timestamp, tz, &date_mode));
v->value = timestamp;
+ date_mode_release(&date_mode);
return;
bad:
v->s = xstrdup("");
struct commit;
struct reflog_walk_info;
+struct date_mode;
void init_reflog_walk(struct reflog_walk_info **info);
int add_reflog_for_walk(struct reflog_walk_info *info,
#include "strvec.h"
#include "repository.h"
#include "sigchain.h"
+#include "date.h"
/*
* List of all available backends
struct object_id *oid, int *flags)
{
const char *result;
- int ignore_errno;
result = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- oid, flags, &ignore_errno);
+ oid, flags);
return xstrdup_or_null(result);
}
int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
{
- int ignore_errno;
struct ref_store *refs = get_main_ref_store(the_repository);
if (refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- oid, flags, &ignore_errno))
+ oid, flags))
return 0;
return -1;
}
int refs_ref_exists(struct ref_store *refs, const char *refname)
{
- int ignore_errno;
return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING,
- NULL, NULL, &ignore_errno);
+ NULL, NULL);
}
int ref_exists(const char *refname)
struct object_id *this_result;
int flag;
struct ref_store *refs = get_main_ref_store(repo);
- int ignore_errno;
this_result = refs_found ? &oid_from_ref : oid;
strbuf_reset(&fullref);
strbuf_addf(&fullref, *p, len, str);
r = refs_resolve_ref_unsafe(refs, fullref.buf,
RESOLVE_REF_READING,
- this_result, &flag,
- &ignore_errno);
+ this_result, &flag);
if (r) {
if (!refs_found++)
*ref = xstrdup(r);
for (p = ref_rev_parse_rules; *p; p++) {
struct object_id hash;
const char *ref, *it;
- int ignore_errno;
strbuf_reset(&path);
strbuf_addf(&path, *p, len, str);
ref = refs_resolve_ref_unsafe(refs, path.buf,
RESOLVE_REF_READING,
- oid ? &hash : NULL, NULL,
- &ignore_errno);
+ oid ? &hash : NULL, NULL);
if (!ref)
continue;
if (refs_reflog_exists(refs, path.buf))
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- transaction = ref_store_transaction_begin(refs, &err);
+ transaction = ref_store_transaction_begin(refs, 0, &err);
if (!transaction ||
ref_transaction_delete(transaction, refname, old_oid,
flags, msg, &err) ||
}
struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
+ unsigned int flags,
struct strbuf *err)
{
struct ref_transaction *tr;
CALLOC_ARRAY(tr, 1);
tr->ref_store = refs;
+ tr->flags = flags;
return tr;
}
struct ref_transaction *ref_transaction_begin(struct strbuf *err)
{
- return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
+ return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err);
}
void ref_transaction_free(struct ref_transaction *transaction)
struct strbuf err = STRBUF_INIT;
int ret = 0;
- t = ref_store_transaction_begin(refs, &err);
+ t = ref_store_transaction_begin(refs, 0, &err);
if (!t ||
ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
&err) ||
{
struct object_id oid;
int flag;
- int ignore_errno;
if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
- &oid, &flag, &ignore_errno))
+ &oid, &flag))
return fn("HEAD", &oid, flag, cb_data);
return 0;
type, failure_errno);
}
+int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
+ struct strbuf *referent)
+{
+ struct object_id oid;
+ int ret, failure_errno = 0;
+ unsigned int type = 0;
+
+ if (ref_store->be->read_symbolic_ref)
+ return ref_store->be->read_symbolic_ref(ref_store, refname, referent);
+
+ ret = refs_read_raw_ref(ref_store, refname, &oid, referent, &type, &failure_errno);
+ if (ret || !(type & REF_ISSYMREF))
+ return -1;
+
+ return 0;
+}
+
const char *refs_resolve_ref_unsafe(struct ref_store *refs,
const char *refname,
int resolve_flags,
struct object_id *oid,
- int *flags, int *failure_errno)
+ int *flags)
{
static struct strbuf sb_refname = STRBUF_INIT;
struct object_id unused_oid;
int unused_flags;
int symref_count;
- assert(failure_errno);
-
if (!oid)
oid = &unused_oid;
if (!flags)
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
- !refname_is_safe(refname)) {
- *failure_errno = EINVAL;
+ !refname_is_safe(refname))
return NULL;
- }
/*
* dwim_ref() uses REF_ISBROKEN to distinguish between
for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
unsigned int read_flags = 0;
+ int failure_errno;
if (refs_read_raw_ref(refs, refname, oid, &sb_refname,
- &read_flags, failure_errno)) {
+ &read_flags, &failure_errno)) {
*flags |= read_flags;
/* In reading mode, refs must eventually resolve */
* may show errors besides ENOENT if there are
* similarly-named refs.
*/
- if (*failure_errno != ENOENT &&
- *failure_errno != EISDIR &&
- *failure_errno != ENOTDIR)
+ if (failure_errno != ENOENT &&
+ failure_errno != EISDIR &&
+ failure_errno != ENOTDIR)
return NULL;
oidclr(oid);
}
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
- !refname_is_safe(refname)) {
- *failure_errno = EINVAL;
+ !refname_is_safe(refname))
return NULL;
- }
*flags |= REF_ISBROKEN | REF_BAD_NAME;
}
}
- *failure_errno = ELOOP;
return NULL;
}
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
struct object_id *oid, int *flags)
{
- int ignore_errno;
-
return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
- resolve_flags, oid, flags, &ignore_errno);
+ resolve_flags, oid, flags);
}
int resolve_gitlink_ref(const char *submodule, const char *refname,
{
struct ref_store *refs;
int flags;
- int ignore_errno;
refs = get_submodule_ref_store(submodule);
if (!refs)
return -1;
- if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags,
- &ignore_errno) || is_null_oid(oid))
+ if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||
+ is_null_oid(oid))
return -1;
return 0;
}
const char *hook;
int ret = 0, i;
+ if (transaction->flags & REF_TRANSACTION_SKIP_HOOK)
+ return 0;
+
hook = find_hook("reference-transaction");
if (!hook)
return ret;
return refs->be->initial_transaction_commit(refs, transaction, err);
}
+void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
+ ref_transaction_for_each_queued_update_fn cb,
+ void *cb_data)
+{
+ int i;
+
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+
+ cb(update->refname,
+ (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
+ (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
+ cb_data);
+ }
+}
+
int refs_delete_refs(struct ref_store *refs, const char *logmsg,
struct string_list *refnames, unsigned int flags)
{
* resolved. The function returns NULL for such ref names.
* Caps and underscores refers to the special refs, such as HEAD,
* FETCH_HEAD and friends, that all live outside of the refs/ directory.
- *
- * Callers should not inspect "errno" on failure, but rather pass in a
- * "failure_errno" parameter, on failure the "errno" will indicate the
- * type of failure encountered, but not necessarily one that came from
- * a syscall. We might have faked it up.
*/
#define RESOLVE_REF_READING 0x01
#define RESOLVE_REF_NO_RECURSE 0x02
const char *refname,
int resolve_flags,
struct object_id *oid,
- int *flags, int *failure_errno);
+ int *flags);
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
struct object_id *oid, int *flags);
struct object_id *oid, int *flags);
int read_ref(const char *refname, struct object_id *oid);
+int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
+ struct strbuf *referent);
+
/*
* Return 0 if a reference named refname could be created without
* conflicting with the name of an existing reference. Otherwise,
* struct strbuf err = STRBUF_INIT;
* int ret = 0;
*
- * transaction = ref_store_transaction_begin(refs, &err);
+ * transaction = ref_store_transaction_begin(refs, 0, &err);
* if (!transaction ||
* ref_transaction_update(...) ||
* ref_transaction_create(...) ||
UPDATE_REFS_QUIET_ON_ERR
};
+/*
+ * Skip executing the reference-transaction hook.
+ */
+#define REF_TRANSACTION_SKIP_HOOK (1 << 0)
+
/*
* Begin a reference transaction. The reference transaction must
* be freed by calling ref_transaction_free().
*/
struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
+ unsigned int flags,
struct strbuf *err);
struct ref_transaction *ref_transaction_begin(struct strbuf *err);
int initial_ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err);
+/*
+ * Execute the given callback function for each of the reference updates which
+ * have been queued in the given transaction. `old_oid` and `new_oid` may be
+ * `NULL` pointers depending on whether the update has these object IDs set or
+ * not.
+ */
+typedef void ref_transaction_for_each_queued_update_fn(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ void *cb_data);
+void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
+ ref_transaction_for_each_queued_update_fn cb,
+ void *cb_data);
+
/*
* Free `*transaction` and all associated data.
*/
debug_ref_iterator_begin,
debug_read_raw_ref,
+ NULL,
debug_reflog_iterator_begin,
debug_for_each_reflog_ent,
create_dir_entry(dir->cache, refname.buf,
refname.len));
} else {
- int ignore_errno;
if (!refs_resolve_ref_unsafe(&refs->base,
refname.buf,
RESOLVE_REF_READING,
- &oid, &flag, &ignore_errno)) {
+ &oid, &flag)) {
oidclr(&oid);
flag |= REF_ISBROKEN;
} else if (is_null_oid(&oid)) {
return refs->loose;
}
-static int files_read_raw_ref(struct ref_store *ref_store, const char *refname,
- struct object_id *oid, struct strbuf *referent,
- unsigned int *type, int *failure_errno)
+static int read_ref_internal(struct ref_store *ref_store, const char *refname,
+ struct object_id *oid, struct strbuf *referent,
+ unsigned int *type, int *failure_errno, int skip_packed_refs)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
if (lstat(path, &st) < 0) {
int ignore_errno;
myerr = errno;
- if (myerr != ENOENT)
+ if (myerr != ENOENT || skip_packed_refs)
goto out;
if (refs_read_raw_ref(refs->packed_ref_store, refname, oid,
referent, type, &ignore_errno)) {
* ref is supposed to be, there could still be a
* packed ref:
*/
- if (refs_read_raw_ref(refs->packed_ref_store, refname, oid,
+ if (skip_packed_refs ||
+ refs_read_raw_ref(refs->packed_ref_store, refname, oid,
referent, type, &ignore_errno)) {
myerr = EISDIR;
goto out;
return ret;
}
+static int files_read_raw_ref(struct ref_store *ref_store, const char *refname,
+ struct object_id *oid, struct strbuf *referent,
+ unsigned int *type, int *failure_errno)
+{
+ return read_ref_internal(ref_store, refname, oid, referent, type, failure_errno, 0);
+}
+
+static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
+ struct strbuf *referent)
+{
+ struct object_id oid;
+ int failure_errno, ret;
+ unsigned int type;
+
+ ret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);
+ if (ret)
+ return ret;
+
+ return !(type & REF_ISSYMREF);
+}
+
int parse_loose_ref_contents(const char *buf, struct object_id *oid,
struct strbuf *referent, unsigned int *type,
int *failure_errno)
{
struct strbuf ref_file = STRBUF_INIT;
struct ref_lock *lock;
- int ignore_errno;
files_assert_main_repository(refs, "lock_ref_oid_basic");
assert(err);
}
if (!refs_resolve_ref_unsafe(&refs->base, lock->ref_name, 0,
- &lock->old_oid, NULL, &ignore_errno))
+ &lock->old_oid, NULL))
oidclr(&lock->old_oid);
goto out;
if (check_refname_format(r->name, 0))
return;
- transaction = ref_store_transaction_begin(&refs->base, &err);
+ transaction = ref_store_transaction_begin(&refs->base,
+ REF_TRANSACTION_SKIP_HOOK, &err);
if (!transaction)
goto cleanup;
ref_transaction_add_update(
struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction;
- transaction = ref_store_transaction_begin(refs->packed_ref_store, &err);
+ transaction = ref_store_transaction_begin(refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, &err);
if (!transaction)
return -1;
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
+ struct ref_transaction *transaction = NULL;
struct strbuf err = STRBUF_INIT;
int i, result = 0;
if (packed_refs_lock(refs->packed_ref_store, 0, &err))
goto error;
- if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {
- packed_refs_unlock(refs->packed_ref_store);
+ transaction = ref_store_transaction_begin(refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, &err);
+ if (!transaction)
+ goto error;
+
+ result = packed_refs_delete_refs(refs->packed_ref_store,
+ transaction, msg, refnames, flags);
+ if (result)
goto error;
- }
packed_refs_unlock(refs->packed_ref_store);
result |= error(_("could not remove reference %s"), refname);
}
+ ref_transaction_free(transaction);
strbuf_release(&err);
return result;
else
error(_("could not delete references: %s"), err.buf);
+ ref_transaction_free(transaction);
strbuf_release(&err);
return -1;
}
struct strbuf tmp_renamed_log = STRBUF_INIT;
int log, ret;
struct strbuf err = STRBUF_INIT;
- int ignore_errno;
files_reflog_path(refs, &sb_oldref, oldrefname);
files_reflog_path(refs, &sb_newref, newrefname);
if (!refs_resolve_ref_unsafe(&refs->base, oldrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &orig_oid, &flag, &ignore_errno)) {
+ &orig_oid, &flag)) {
ret = error("refname %s not found", oldrefname);
goto out;
}
*/
if (!copy && refs_resolve_ref_unsafe(&refs->base, newrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- NULL, NULL, &ignore_errno) &&
+ NULL, NULL) &&
refs_delete_ref(&refs->base, NULL, newrefname,
NULL, REF_NO_DEREF)) {
if (errno == EISDIR) {
*/
int head_flag;
const char *head_ref;
- int ignore_errno;
head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD",
RESOLVE_REF_READING,
- NULL, &head_flag,
- &ignore_errno);
+ NULL, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) {
struct strbuf log_err = STRBUF_INIT;
{
struct strbuf err = STRBUF_INIT;
struct object_id new_oid;
- int ignore_errno;
if (logmsg &&
refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL,
- &ignore_errno) &&
+ RESOLVE_REF_READING, &new_oid, NULL) &&
files_log_ref_write(refs, refname, &lock->old_oid,
&new_oid, logmsg, 0, &err)) {
error("%s", err.buf);
(struct files_reflog_iterator *)ref_iterator;
struct dir_iterator *diter = iter->dir_iterator;
int ok;
- int ignore_errno;
while ((ok = dir_iterator_advance(diter)) == ITER_OK) {
int flags;
if (!refs_resolve_ref_unsafe(iter->ref_store,
diter->relative_path, 0,
- &iter->oid, &flags,
- &ignore_errno)) {
+ &iter->oid, &flags)) {
error("bad ref for %s", diter->path.buf);
continue;
}
* the transaction, so we have to read it here
* to record and possibly check old_oid:
*/
- int ignore_errno;
if (!refs_resolve_ref_unsafe(&refs->base,
referent.buf, 0,
- &lock->old_oid, NULL,
- &ignore_errno)) {
+ &lock->old_oid, NULL)) {
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
*/
if (!packed_transaction) {
packed_transaction = ref_store_transaction_begin(
- refs->packed_ref_store, err);
+ refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, err);
if (!packed_transaction) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
&affected_refnames))
BUG("initial ref transaction called with existing refs");
- packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
+ packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, err);
if (!packed_transaction) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
if ((expire_flags & EXPIRE_REFLOGS_UPDATE_REF) &&
!is_null_oid(&cb.last_kept_oid)) {
- int ignore_errno;
int type;
const char *ref;
ref = refs_resolve_ref_unsafe(&refs->base, refname,
RESOLVE_REF_NO_RECURSE,
- NULL, &type,
- &ignore_errno);
+ NULL, &type);
update = !!(ref && !(type & REF_ISSYMREF));
}
files_ref_iterator_begin,
files_read_raw_ref,
+ files_read_symbolic_ref,
files_reflog_iterator_begin,
files_for_each_reflog_ent,
static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
struct string_list *refnames, unsigned int flags)
{
- struct packed_ref_store *refs =
- packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction;
- struct string_list_item *item;
int ret;
- (void)refs; /* We need the check above, but don't use the variable */
-
if (!refnames->nr)
return 0;
* updates into a single transaction.
*/
- transaction = ref_store_transaction_begin(ref_store, &err);
+ transaction = ref_store_transaction_begin(ref_store, 0, &err);
if (!transaction)
return -1;
+ ret = packed_refs_delete_refs(ref_store, transaction,
+ msg, refnames, flags);
+
+ ref_transaction_free(transaction);
+ return ret;
+}
+
+int packed_refs_delete_refs(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ const char *msg,
+ struct string_list *refnames,
+ unsigned int flags)
+{
+ struct strbuf err = STRBUF_INIT;
+ struct string_list_item *item;
+ int ret;
+
+ /* Assert that the ref store refers to a packed backend. */
+ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
+
for_each_string_list_item(item, refnames) {
if (ref_transaction_delete(transaction, item->string, NULL,
flags, msg, &err)) {
error(_("could not delete references: %s"), err.buf);
}
- ref_transaction_free(transaction);
strbuf_release(&err);
return ret;
}
packed_ref_iterator_begin,
packed_read_raw_ref,
+ NULL,
packed_reflog_iterator_begin,
packed_for_each_reflog_ent,
struct repository;
struct ref_transaction;
+struct string_list;
/*
* Support for storing references in a `packed-refs` file.
void packed_refs_unlock(struct ref_store *ref_store);
int packed_refs_is_locked(struct ref_store *ref_store);
+int packed_refs_delete_refs(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ const char *msg,
+ struct string_list *refnames,
+ unsigned int flags);
+
/*
* Return true if `transaction` really needs to be carried out against
* the specified packed_ref_store, or false if it can be skipped
size_t nr;
enum ref_transaction_state state;
void *backend_data;
+ unsigned int flags;
};
/*
struct object_id *oid, struct strbuf *referent,
unsigned int *type, int *failure_errno);
+/*
+ * Read a symbolic reference from the specified reference store. This function
+ * is optional: if not implemented by a backend, then `read_raw_ref_fn` is used
+ * to read the symbolcic reference instead. It is intended to be implemented
+ * only in case the backend can optimize the reading of symbolic references.
+ *
+ * Return 0 on success, or -1 on failure. `referent` will be set to the target
+ * of the symbolic reference on success. This function explicitly does not
+ * distinguish between error cases and the reference not being a symbolic
+ * reference to allow backends to optimize this operation in case symbolic and
+ * non-symbolic references are treated differently.
+ */
+typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refname,
+ struct strbuf *referent);
+
struct ref_storage_be {
struct ref_storage_be *next;
const char *name;
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
+ read_symbolic_ref_fn *read_symbolic_ref;
reflog_iterator_begin_fn *reflog_iterator_begin;
for_each_reflog_ent_fn *for_each_reflog_ent;
#include "refspec.h"
static struct refspec_item s_tag_refspec = {
- 0,
- 1,
- 0,
- 0,
- 0,
- "refs/tags/*",
- "refs/tags/*"
+ .force = 0,
+ .pattern = 1,
+ .matching = 0,
+ .exact_sha1 = 0,
+ .negative = 0,
+ .src = "refs/tags/*",
+ .dst = "refs/tags/*",
};
/* See TAG_REFSPEC for the string version */
return bw->buf[bw->header_off];
}
-/* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on
- success */
+/* Adds the reftable_record to the block. Returns -1 if it does not fit, 0 on
+ success. Returns REFTABLE_API_ERROR if attempting to write a record with
+ empty key. */
int block_writer_add(struct block_writer *w, struct reftable_record *rec)
{
struct strbuf empty = STRBUF_INIT;
int is_restart = 0;
struct strbuf key = STRBUF_INIT;
int n = 0;
+ int err = -1;
reftable_record_key(rec, &key);
+ if (!key.len) {
+ err = REFTABLE_API_ERROR;
+ goto done;
+ }
+
n = reftable_encode_key(&is_restart, out, last, key,
reftable_record_val_type(rec));
if (n < 0)
goto done;
string_view_consume(&out, n);
- if (block_writer_register_restart(w, start.len - out.len, is_restart,
- &key) < 0)
- goto done;
-
- strbuf_release(&key);
- return 0;
-
+ err = block_writer_register_restart(w, start.len - out.len, is_restart,
+ &key);
done:
strbuf_release(&key);
- return -1;
+ return err;
}
int block_writer_finish(struct block_writer *w)
uint32_t full_block_size = table_block_size;
uint8_t typ = block->data[header_off];
uint32_t sz = get_be24(block->data + header_off + 1);
-
+ int err = 0;
uint16_t restart_count = 0;
uint32_t restart_start = 0;
uint8_t *restart_bytes = NULL;
+ uint8_t *uncompressed = NULL;
- if (!reftable_is_block_type(typ))
- return REFTABLE_FORMAT_ERROR;
+ if (!reftable_is_block_type(typ)) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
if (typ == BLOCK_TYPE_LOG) {
int block_header_skip = 4 + header_off;
uLongf src_len = block->len - block_header_skip;
/* Log blocks specify the *uncompressed* size in their header.
*/
- uint8_t *uncompressed = reftable_malloc(sz);
+ uncompressed = reftable_malloc(sz);
/* Copy over the block header verbatim. It's not compressed. */
memcpy(uncompressed, block->data, block_header_skip);
if (Z_OK !=
uncompress2(uncompressed + block_header_skip, &dst_len,
block->data + block_header_skip, &src_len)) {
- reftable_free(uncompressed);
- return REFTABLE_ZLIB_ERROR;
+ err = REFTABLE_ZLIB_ERROR;
+ goto done;
}
- if (dst_len + block_header_skip != sz)
- return REFTABLE_FORMAT_ERROR;
+ if (dst_len + block_header_skip != sz) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
/* We're done with the input data. */
reftable_block_done(block);
block->data = uncompressed;
+ uncompressed = NULL;
block->len = sz;
block->source = malloc_block_source();
full_block_size = src_len + block_header_skip;
br->restart_count = restart_count;
br->restart_bytes = restart_bytes;
- return 0;
+done:
+ reftable_free(uncompressed);
+ return err;
}
static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
if (n < 0)
return -1;
+ if (!key.len)
+ return REFTABLE_FORMAT_ERROR;
+
string_view_consume(&in, n);
n = reftable_record_decode(rec, key, extra, in, it->br->hash_size);
if (n < 0)
int n = reftable_decode_key(key, &extra, empty, in);
if (n < 0)
return n;
+ if (!key->len)
+ return REFTABLE_FORMAT_ERROR;
return 0;
}
done:
strbuf_release(&key);
strbuf_release(&next.last_key);
- reftable_record_destroy(&rec);
+ reftable_record_release(&rec);
return err;
}
struct block_writer bw = {
.last_key = STRBUF_INIT,
};
- struct reftable_ref_record ref = { NULL };
- struct reftable_record rec = { NULL };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ };
int i = 0;
int n;
struct block_reader br = { 0 };
block.source = malloc_block_source();
block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
header_off, hash_size(GIT_SHA1_FORMAT_ID));
- reftable_record_from_ref(&rec, &ref);
+
+ rec.u.ref.refname = "";
+ rec.u.ref.value_type = REFTABLE_REF_DELETION;
+ n = block_writer_add(&bw, &rec);
+ EXPECT(n == REFTABLE_API_ERROR);
for (i = 0; i < N; i++) {
char name[100];
snprintf(name, sizeof(name), "branch%02d", i);
memset(hash, i, sizeof(hash));
- ref.refname = name;
- ref.value_type = REFTABLE_REF_VAL1;
- ref.value.val1 = hash;
+ rec.u.ref.refname = name;
+ rec.u.ref.value_type = REFTABLE_REF_VAL1;
+ rec.u.ref.value.val1 = hash;
names[i] = xstrdup(name);
n = block_writer_add(&bw, &rec);
- ref.refname = NULL;
- ref.value_type = REFTABLE_REF_DELETION;
+ rec.u.ref.refname = NULL;
+ rec.u.ref.value_type = REFTABLE_REF_DELETION;
EXPECT(n == 0);
}
if (r > 0) {
break;
}
- EXPECT_STREQ(names[j], ref.refname);
+ EXPECT_STREQ(names[j], rec.u.ref.refname);
j++;
}
n = block_iter_next(&it, &rec);
EXPECT(n == 0);
- EXPECT_STREQ(names[i], ref.refname);
+ EXPECT_STREQ(names[i], rec.u.ref.refname);
want.len--;
n = block_reader_seek(&br, &it, &want);
n = block_iter_next(&it, &rec);
EXPECT(n == 0);
- EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+ EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
block_iter_close(&it);
}
}
err = fstat(fd, &st);
- if (err < 0)
- return -1;
+ if (err < 0) {
+ close(fd);
+ return REFTABLE_IO_ERROR;
+ }
p = reftable_calloc(sizeof(struct file_block_source));
p->size = st.st_size;
*/
#include "basics.h"
+#include "constants.h"
#include "record.h"
#include "generic.h"
#include "reftable-iterator.h"
int reftable_table_seek_ref(struct reftable_table *tab,
struct reftable_iterator *it, const char *name)
{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
+ struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = (char *)name,
+ } };
return tab->ops->seek_record(tab->table_arg, it, &rec);
}
int reftable_table_seek_log(struct reftable_table *tab,
struct reftable_iterator *it, const char *name)
{
- struct reftable_log_record log = {
- .refname = (char *)name,
- .update_index = ~((uint64_t)0),
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = (char *)name,
+ .update_index = ~((uint64_t)0),
+ } };
return tab->ops->seek_record(tab->table_arg, it, &rec);
}
int reftable_iterator_next_ref(struct reftable_iterator *it,
struct reftable_ref_record *ref)
{
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, ref);
- return iterator_next(it, &rec);
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = *ref,
+ };
+ int err = iterator_next(it, &rec);
+ *ref = rec.u.ref;
+ return err;
}
int reftable_iterator_next_log(struct reftable_iterator *it,
struct reftable_log_record *log)
{
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, log);
- return iterator_next(it, &rec);
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_LOG,
+ .u.log = *log,
+ };
+ int err = iterator_next(it, &rec);
+ *log = rec.u.log;
+ return err;
}
int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
struct reftable_record *rec)
{
struct filtering_ref_iterator *fri = iter_arg;
- struct reftable_ref_record *ref = rec->data;
+ struct reftable_ref_record *ref = &rec->u.ref;
int err = 0;
while (1) {
err = reftable_iterator_next_ref(&fri->it, ref);
static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
{
struct indexed_table_ref_iter *it = p;
- struct reftable_ref_record *ref = rec->data;
+ struct reftable_ref_record *ref = &rec->u.ref;
while (1) {
int err = block_iter_next(&it->cur, rec);
if (err > 0) {
reftable_iterator_destroy(&mi->stack[i]);
- reftable_record_destroy(&rec);
+ reftable_record_release(&rec);
} else {
struct pq_entry e = {
.rec = rec,
static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
size_t idx)
{
- struct reftable_record rec = reftable_new_record(mi->typ);
struct pq_entry e = {
- .rec = rec,
+ .rec = reftable_new_record(mi->typ),
.index = idx,
};
- int err = iterator_next(&mi->stack[idx], &rec);
+ int err = iterator_next(&mi->stack[idx], &e.rec);
if (err < 0)
return err;
if (err > 0) {
reftable_iterator_destroy(&mi->stack[idx]);
- reftable_record_destroy(&rec);
+ reftable_record_release(&e.rec);
return 0;
}
if (err < 0) {
return err;
}
- reftable_record_destroy(&top.rec);
+ reftable_record_release(&top.rec);
}
reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
- reftable_record_destroy(&entry.rec);
+ reftable_record_release(&entry.rec);
strbuf_release(&entry_key);
return 0;
}
struct reftable_iterator *it,
const char *name)
{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = (char *)name,
+ },
};
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
return merged_table_seek_record(mt, it, &rec);
}
struct reftable_iterator *it,
const char *name, uint64_t update_index)
{
- struct reftable_log_record log = {
- .refname = (char *)name,
- .update_index = update_index,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = (char *)name,
+ .update_index = update_index,
+ } };
return merged_table_seek_record(mt, it, &rec);
}
void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
{
int i = 0;
+
if (pq->len == pq->cap) {
pq->cap = 2 * pq->cap + 1;
pq->heap = reftable_realloc(pq->heap,
{
int i = 0;
for (i = 0; i < pq->len; i++) {
- reftable_record_destroy(&pq->heap[i].rec);
+ reftable_record_release(&pq->heap[i].rec);
}
FREE_AND_NULL(pq->heap);
pq->len = pq->cap = 0;
int N = ARRAY_SIZE(names) - 1;
struct merged_iter_pqueue pq = { NULL };
- const char *last = NULL;
+ char *last = NULL;
int i = 0;
for (i = 0; i < N; i++) {
i = 1;
do {
- struct reftable_record rec =
- reftable_new_record(BLOCK_TYPE_REF);
- struct pq_entry e = { 0 };
-
- reftable_record_as_ref(&rec)->refname = names[i];
- e.rec = rec;
+ struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = names[i],
+ } } };
merged_iter_pqueue_add(&pq, e);
merged_iter_pqueue_check(pq);
i = (i * 7) % N;
while (!merged_iter_pqueue_is_empty(pq)) {
struct pq_entry e = merged_iter_pqueue_remove(&pq);
- struct reftable_ref_record *ref =
- reftable_record_as_ref(&e.rec);
-
+ struct reftable_record *rec = &e.rec;
merged_iter_pqueue_check(pq);
+ EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
if (last) {
- EXPECT(strcmp(last, ref->refname) < 0);
+ EXPECT(strcmp(last, rec->u.ref.refname) < 0);
}
- last = ref->refname;
- ref->refname = NULL;
- reftable_free(ref);
+ // this is names[i], so don't dealloc.
+ last = rec->u.ref.refname;
+ rec->u.ref.refname = NULL;
+ reftable_record_release(rec);
}
-
for (i = 0; i < N; i++) {
reftable_free(names[i]);
}
r->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG ||
r->log_offsets.offset > 0);
r->obj_offsets.is_present = r->obj_offsets.offset > 0;
+ if (r->obj_offsets.is_present && !r->object_id_len) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+
err = 0;
done:
return err;
{
int res = block_iter_next(&ti->bi, rec);
if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
- ((struct reftable_ref_record *)rec->data)->update_index +=
- ti->r->min_update_index;
+ rec->u.ref.update_index += ti->r->min_update_index;
}
return res;
err = reader_get_block(r, &block, next_off, guess_block_size);
if (err < 0)
- return err;
+ goto done;
block_size = extract_block_size(block.data, &block_typ, next_off,
r->version);
- if (block_size < 0)
- return block_size;
-
+ if (block_size < 0) {
+ err = block_size;
+ goto done;
+ }
if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
- reftable_block_done(&block);
- return 1;
+ err = 1;
+ goto done;
}
if (block_size > guess_block_size) {
reftable_block_done(&block);
err = reader_get_block(r, &block, next_off, block_size);
if (err < 0) {
- return err;
+ goto done;
}
}
- return block_reader_init(br, &block, header_off, r->block_size,
- hash_size(r->hash_id));
+ err = block_reader_init(br, &block, header_off, r->block_size,
+ hash_size(r->hash_id));
+done:
+ reftable_block_done(&block);
+
+ return err;
}
static int table_iter_next_block(struct table_iter *dest,
done:
block_iter_close(&next.bi);
- reftable_record_destroy(&rec);
+ reftable_record_release(&rec);
strbuf_release(&want_key);
strbuf_release(&got_key);
return err;
struct reftable_iterator *it,
struct reftable_record *rec)
{
- struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
- struct reftable_record want_index_rec = { NULL };
- struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
- struct reftable_record index_result_rec = { NULL };
+ struct reftable_record want_index = {
+ .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT }
+ };
+ struct reftable_record index_result = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = { .last_key = STRBUF_INIT },
+ };
struct table_iter index_iter = TABLE_ITER_INIT;
struct table_iter next = TABLE_ITER_INIT;
int err = 0;
- reftable_record_key(rec, &want_index.last_key);
- reftable_record_from_index(&want_index_rec, &want_index);
- reftable_record_from_index(&index_result_rec, &index_result);
-
+ reftable_record_key(rec, &want_index.u.idx.last_key);
err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
if (err < 0)
goto done;
- err = reader_seek_linear(r, &index_iter, &want_index_rec);
+ err = reader_seek_linear(r, &index_iter, &want_index);
while (1) {
- err = table_iter_next(&index_iter, &index_result_rec);
+ err = table_iter_next(&index_iter, &index_result);
table_iter_block_done(&index_iter);
if (err != 0)
goto done;
- err = reader_table_iter_at(r, &next, index_result.offset, 0);
+ err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
+ 0);
if (err != 0)
goto done;
- err = block_iter_seek(&next.bi, &want_index.last_key);
+ err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
if (err < 0)
goto done;
done:
block_iter_close(&next.bi);
table_iter_close(&index_iter);
- reftable_record_release(&want_index_rec);
- reftable_record_release(&index_result_rec);
+ reftable_record_release(&want_index);
+ reftable_record_release(&index_result);
return err;
}
int reftable_reader_seek_ref(struct reftable_reader *r,
struct reftable_iterator *it, const char *name)
{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = (char *)name,
+ },
};
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
return reader_seek(r, it, &rec);
}
struct reftable_iterator *it, const char *name,
uint64_t update_index)
{
- struct reftable_log_record log = {
- .refname = (char *)name,
- .update_index = update_index,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = (char *)name,
+ .update_index = update_index,
+ } };
return reader_seek(r, it, &rec);
}
void reftable_reader_free(struct reftable_reader *r)
{
+ if (!r)
+ return;
reader_close(r);
reftable_free(r);
}
struct reftable_iterator *it,
uint8_t *oid)
{
- struct reftable_obj_record want = {
- .hash_prefix = oid,
- .hash_prefix_len = r->object_id_len,
+ struct reftable_record want = {
+ .type = BLOCK_TYPE_OBJ,
+ .u.obj = {
+ .hash_prefix = oid,
+ .hash_prefix_len = r->object_id_len,
+ },
};
- struct reftable_record want_rec = { NULL };
struct reftable_iterator oit = { NULL };
- struct reftable_obj_record got = { NULL };
- struct reftable_record got_rec = { NULL };
+ struct reftable_record got = {
+ .type = BLOCK_TYPE_OBJ,
+ .u.obj = { 0 },
+ };
int err = 0;
struct indexed_table_ref_iter *itr = NULL;
/* Look through the reverse index. */
- reftable_record_from_obj(&want_rec, &want);
- err = reader_seek(r, &oit, &want_rec);
+ err = reader_seek(r, &oit, &want);
if (err != 0)
goto done;
/* read out the reftable_obj_record */
- reftable_record_from_obj(&got_rec, &got);
- err = iterator_next(&oit, &got_rec);
+ err = iterator_next(&oit, &got);
if (err < 0)
goto done;
- if (err > 0 ||
- memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+ if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
+ r->object_id_len)) {
/* didn't find it; return empty iterator */
iterator_set_empty(it);
err = 0;
}
err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
- got.offsets, got.offset_len);
+ got.u.obj.offsets,
+ got.u.obj.offset_len);
if (err < 0)
goto done;
- got.offsets = NULL;
+ got.u.obj.offsets = NULL;
iterator_from_indexed_table_ref_iter(it, itr);
done:
reftable_iterator_destroy(&oit);
- reftable_record_release(&got_rec);
+ reftable_record_release(&got);
return err;
}
n = reftable_writer_close(w);
EXPECT(n == 0);
- stats = writer_stats(w);
+ stats = reftable_writer_stats(w);
for (i = 0; i < stats->ref_stats.blocks; i++) {
int off = i * opts.block_size;
if (off == 0) {
n = reftable_writer_close(w);
EXPECT(n == 0);
- stats = writer_stats(w);
+ stats = reftable_writer_stats(w);
EXPECT(stats->log_stats.blocks > 0);
reftable_writer_free(w);
w = NULL;
reader_close(&rd);
}
+static void test_log_zlib_corruption(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ };
+ struct reftable_iterator it = { 0 };
+ struct reftable_reader rd = { 0 };
+ struct reftable_block_source source = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ const struct reftable_stats *stats = NULL;
+ uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
+ uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
+ char message[100] = { 0 };
+ int err, i, n;
+
+ struct reftable_log_record log = {
+ .refname = "refname",
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .new_hash = hash1,
+ .old_hash = hash2,
+ .name = "My Name",
+ .email = "myname@invalid",
+ .message = message,
+ },
+ },
+ };
+
+ for (i = 0; i < sizeof(message) - 1; i++)
+ message[i] = (uint8_t)(rand() % 64 + ' ');
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ err = reftable_writer_add_log(w, &log);
+ EXPECT_ERR(err);
+
+ n = reftable_writer_close(w);
+ EXPECT(n == 0);
+
+ stats = reftable_writer_stats(w);
+ EXPECT(stats->log_stats.blocks > 0);
+ reftable_writer_free(w);
+ w = NULL;
+
+ /* corrupt the data. */
+ buf.buf[50] ^= 0x99;
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = init_reader(&rd, &source, "file.log");
+ EXPECT_ERR(err);
+
+ err = reftable_reader_seek_log(&rd, &it, "refname");
+ EXPECT(err == REFTABLE_ZLIB_ERROR);
+
+ reftable_iterator_destroy(&it);
+
+ /* cleanup. */
+ strbuf_release(&buf);
+ reader_close(&rd);
+}
+
static void test_table_read_write_sequential(void)
{
char **names;
strbuf_release(&buf);
}
+static void test_write_object_id_min_length(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 75,
+ };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ uint8_t hash[GIT_SHA1_RAWSZ] = {42};
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = hash,
+ };
+ int err;
+ int i;
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ /* Write the same hash in many refs. If there is only 1 hash, the
+ * disambiguating prefix is length 0 */
+ for (i = 0; i < 256; i++) {
+ char name[256];
+ snprintf(name, sizeof(name), "ref%05d", i);
+ ref.refname = name;
+ err = reftable_writer_add_ref(w, &ref);
+ EXPECT_ERR(err);
+ }
+
+ err = reftable_writer_close(w);
+ EXPECT_ERR(err);
+ EXPECT(reftable_writer_stats(w)->object_id_len == 2);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void test_write_object_id_length(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 75,
+ };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ uint8_t hash[GIT_SHA1_RAWSZ] = {42};
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = hash,
+ };
+ int err;
+ int i;
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ /* Write the same hash in many refs. If there is only 1 hash, the
+ * disambiguating prefix is length 0 */
+ for (i = 0; i < 256; i++) {
+ char name[256];
+ snprintf(name, sizeof(name), "ref%05d", i);
+ ref.refname = name;
+ ref.value.val1[15] = i;
+ err = reftable_writer_add_ref(w, &ref);
+ EXPECT_ERR(err);
+ }
+
+ err = reftable_writer_close(w);
+ EXPECT_ERR(err);
+ EXPECT(reftable_writer_stats(w)->object_id_len == 16);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void test_write_empty_key(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ struct reftable_ref_record ref = {
+ .refname = "",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_DELETION,
+ };
+ int err;
+
+ reftable_writer_set_limits(w, 1, 1);
+ err = reftable_writer_add_ref(w, &ref);
+ EXPECT(err == REFTABLE_API_ERROR);
+
+ err = reftable_writer_close(w);
+ EXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
static void test_write_key_order(void)
{
struct reftable_write_options opts = { 0 };
err = reftable_writer_add_ref(w, &refs[0]);
EXPECT_ERR(err);
err = reftable_writer_add_ref(w, &refs[1]);
- printf("%d\n", err);
EXPECT(err == REFTABLE_API_ERROR);
reftable_writer_close(w);
reftable_writer_free(w);
int readwrite_test_main(int argc, const char *argv[])
{
+ RUN_TEST(test_log_zlib_corruption);
RUN_TEST(test_corrupt_table);
RUN_TEST(test_corrupt_table_empty);
RUN_TEST(test_log_write_read);
RUN_TEST(test_table_read_write_seek_index);
RUN_TEST(test_table_refs_for_no_index);
RUN_TEST(test_table_refs_for_obj_index);
+ RUN_TEST(test_write_empty_key);
RUN_TEST(test_write_empty_table);
RUN_TEST(test_log_overflow);
+ RUN_TEST(test_write_object_id_length);
+ RUN_TEST(test_write_object_id_min_length);
return 0;
}
#include "reftable-error.h"
#include "basics.h"
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
int get_var_int(uint64_t *dest, struct string_view *in)
{
int ptr = 0;
return 0;
}
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
{
switch (rec->value_type) {
case REFTABLE_REF_VAL1:
}
}
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec)
{
switch (rec->value_type) {
case REFTABLE_REF_VAL2:
}
}
-void reftable_ref_record_print(struct reftable_ref_record *ref,
- uint32_t hash_id)
+static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref,
+ int hash_size)
{
- char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+ char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
switch (ref->value_type) {
case REFTABLE_REF_SYMREF:
printf("=> %s", ref->value.symref);
break;
case REFTABLE_REF_VAL2:
- hex_format(hex, ref->value.val2.value, hash_size(hash_id));
+ hex_format(hex, ref->value.val2.value, hash_size);
printf("val 2 %s", hex);
hex_format(hex, ref->value.val2.target_value,
- hash_size(hash_id));
+ hash_size);
printf("(T %s)", hex);
break;
case REFTABLE_REF_VAL1:
- hex_format(hex, ref->value.val1, hash_size(hash_id));
+ hex_format(hex, ref->value.val1, hash_size);
printf("val 1 %s", hex);
break;
case REFTABLE_REF_DELETION:
printf("}\n");
}
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
+ uint32_t hash_id) {
+ reftable_ref_record_print_sz(ref, hash_size(hash_id));
+}
+
static void reftable_ref_record_release_void(void *rec)
{
reftable_ref_record_release(rec);
(const struct reftable_ref_record *)p);
}
+
+static int reftable_ref_record_equal_void(const void *a,
+ const void *b, int hash_size)
+{
+ struct reftable_ref_record *ra = (struct reftable_ref_record *) a;
+ struct reftable_ref_record *rb = (struct reftable_ref_record *) b;
+ return reftable_ref_record_equal(ra, rb, hash_size);
+}
+
+static void reftable_ref_record_print_void(const void *rec,
+ int hash_size)
+{
+ reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size);
+}
+
static struct reftable_record_vtable reftable_ref_record_vtable = {
.key = &reftable_ref_record_key,
.type = BLOCK_TYPE_REF,
.decode = &reftable_ref_record_decode,
.release = &reftable_ref_record_release_void,
.is_deletion = &reftable_ref_record_is_deletion_void,
+ .equal = &reftable_ref_record_equal_void,
+ .print = &reftable_ref_record_print_void,
};
static void reftable_obj_record_key(const void *r, struct strbuf *dest)
memset(obj, 0, sizeof(struct reftable_obj_record));
}
+static void reftable_obj_record_print(const void *rec, int hash_size)
+{
+ const struct reftable_obj_record *obj = rec;
+ char hex[GIT_MAX_HEXSZ + 1] = { 0 };
+ struct strbuf offset_str = STRBUF_INIT;
+ int i;
+
+ for (i = 0; i < obj->offset_len; i++)
+ strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]);
+ hex_format(hex, obj->hash_prefix, obj->hash_prefix_len);
+ printf("prefix %s (len %d), offsets [%s]\n",
+ hex, obj->hash_prefix_len, offset_str.buf);
+ strbuf_release(&offset_str);
+}
+
static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
int hash_size)
{
(const struct reftable_obj_record *)src_rec;
reftable_obj_record_release(obj);
- *obj = *src;
- obj->hash_prefix = reftable_malloc(obj->hash_prefix_len);
- memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
+ obj->hash_prefix = reftable_malloc(src->hash_prefix_len);
+ obj->hash_prefix_len = src->hash_prefix_len;
+ if (src->hash_prefix_len)
+ memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
- obj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));
- COPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);
+ obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t));
+ obj->offset_len = src->offset_len;
+ COPY_ARRAY(obj->offsets, src->offsets, src->offset_len);
}
static uint8_t reftable_obj_record_val_type(const void *rec)
return 0;
}
+static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size)
+{
+ struct reftable_obj_record *ra = (struct reftable_obj_record *) a;
+ struct reftable_obj_record *rb = (struct reftable_obj_record *) b;
+
+ if (ra->hash_prefix_len != rb->hash_prefix_len
+ || ra->offset_len != rb->offset_len)
+ return 0;
+
+ if (ra->hash_prefix_len &&
+ memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len))
+ return 0;
+ if (ra->offset_len &&
+ memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t)))
+ return 0;
+
+ return 1;
+}
+
static struct reftable_record_vtable reftable_obj_record_vtable = {
.key = &reftable_obj_record_key,
.type = BLOCK_TYPE_OBJ,
.encode = &reftable_obj_record_encode,
.decode = &reftable_obj_record_decode,
.release = &reftable_obj_record_release,
- .is_deletion = not_a_deletion,
+ .is_deletion = ¬_a_deletion,
+ .equal = &reftable_obj_record_equal_void,
+ .print = &reftable_obj_record_print,
};
-void reftable_log_record_print(struct reftable_log_record *log,
- uint32_t hash_id)
+static void reftable_log_record_print_sz(struct reftable_log_record *log,
+ int hash_size)
{
- char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+ char hex[GIT_MAX_HEXSZ + 1] = { 0 };
switch (log->value_type) {
case REFTABLE_LOG_DELETION:
- printf("log{%s(%" PRIu64 ") delete", log->refname,
+ printf("log{%s(%" PRIu64 ") delete\n", log->refname,
log->update_index);
break;
case REFTABLE_LOG_UPDATE:
printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
- log->refname, log->update_index, log->value.update.name,
- log->value.update.email, log->value.update.time,
+ log->refname, log->update_index,
+ log->value.update.name ? log->value.update.name : "",
+ log->value.update.email ? log->value.update.email : "",
+ log->value.update.time,
log->value.update.tz_offset);
- hex_format(hex, log->value.update.old_hash, hash_size(hash_id));
+ hex_format(hex, log->value.update.old_hash, hash_size);
printf("%s => ", hex);
- hex_format(hex, log->value.update.new_hash, hash_size(hash_id));
- printf("%s\n\n%s\n}\n", hex, log->value.update.message);
+ hex_format(hex, log->value.update.new_hash, hash_size);
+ printf("%s\n\n%s\n}\n", hex,
+ log->value.update.message ? log->value.update.message : "");
break;
}
}
+void reftable_log_record_print(struct reftable_log_record *log,
+ uint32_t hash_id)
+{
+ reftable_log_record_print_sz(log, hash_size(hash_id));
+}
+
static void reftable_log_record_key(const void *r, struct strbuf *dest)
{
const struct reftable_log_record *rec =
return !memcmp(a, b, sz);
}
-int reftable_log_record_equal(struct reftable_log_record *a,
- struct reftable_log_record *b, int hash_size)
+static int reftable_log_record_equal_void(const void *a,
+ const void *b, int hash_size)
+{
+ return reftable_log_record_equal((struct reftable_log_record *) a,
+ (struct reftable_log_record *) b,
+ hash_size);
+}
+
+int reftable_log_record_equal(const struct reftable_log_record *a,
+ const struct reftable_log_record *b, int hash_size)
{
if (!(null_streq(a->refname, b->refname) &&
a->update_index == b->update_index &&
(const struct reftable_log_record *)p);
}
+static void reftable_log_record_print_void(const void *rec, int hash_size)
+{
+ reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size);
+}
+
static struct reftable_record_vtable reftable_log_record_vtable = {
.key = &reftable_log_record_key,
.type = BLOCK_TYPE_LOG,
.decode = &reftable_log_record_decode,
.release = &reftable_log_record_release_void,
.is_deletion = &reftable_log_record_is_deletion_void,
+ .equal = &reftable_log_record_equal_void,
+ .print = &reftable_log_record_print_void,
};
-struct reftable_record reftable_new_record(uint8_t typ)
-{
- struct reftable_record rec = { NULL };
- switch (typ) {
- case BLOCK_TYPE_REF: {
- struct reftable_ref_record *r =
- reftable_calloc(sizeof(struct reftable_ref_record));
- reftable_record_from_ref(&rec, r);
- return rec;
- }
-
- case BLOCK_TYPE_OBJ: {
- struct reftable_obj_record *r =
- reftable_calloc(sizeof(struct reftable_obj_record));
- reftable_record_from_obj(&rec, r);
- return rec;
- }
- case BLOCK_TYPE_LOG: {
- struct reftable_log_record *r =
- reftable_calloc(sizeof(struct reftable_log_record));
- reftable_record_from_log(&rec, r);
- return rec;
- }
- case BLOCK_TYPE_INDEX: {
- struct reftable_index_record empty = { .last_key =
- STRBUF_INIT };
- struct reftable_index_record *r =
- reftable_calloc(sizeof(struct reftable_index_record));
- *r = empty;
- reftable_record_from_index(&rec, r);
- return rec;
- }
- }
- abort();
- return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
- void *p = rec->data;
- rec->data = NULL;
- return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
- reftable_record_release(rec);
- reftable_free(reftable_record_yield(rec));
-}
-
static void reftable_index_record_key(const void *r, struct strbuf *dest)
{
const struct reftable_index_record *rec = r;
return start.len - in.len;
}
+static int reftable_index_record_equal(const void *a, const void *b, int hash_size)
+{
+ struct reftable_index_record *ia = (struct reftable_index_record *) a;
+ struct reftable_index_record *ib = (struct reftable_index_record *) b;
+
+ return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key);
+}
+
+static void reftable_index_record_print(const void *rec, int hash_size)
+{
+ const struct reftable_index_record *idx = rec;
+ /* TODO: escape null chars? */
+ printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset);
+}
+
static struct reftable_record_vtable reftable_index_record_vtable = {
.key = &reftable_index_record_key,
.type = BLOCK_TYPE_INDEX,
.decode = &reftable_index_record_decode,
.release = &reftable_index_record_release,
.is_deletion = ¬_a_deletion,
+ .equal = &reftable_index_record_equal,
+ .print = &reftable_index_record_print,
};
void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
{
- rec->ops->key(rec->data, dest);
+ reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
}
uint8_t reftable_record_type(struct reftable_record *rec)
{
- return rec->ops->type;
+ return rec->type;
}
int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
int hash_size)
{
- return rec->ops->encode(rec->data, dest, hash_size);
+ return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+ dest, hash_size);
}
void reftable_record_copy_from(struct reftable_record *rec,
struct reftable_record *src, int hash_size)
{
- assert(src->ops->type == rec->ops->type);
+ assert(src->type == rec->type);
- rec->ops->copy_from(rec->data, src->data, hash_size);
+ reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+ reftable_record_data(src),
+ hash_size);
}
uint8_t reftable_record_val_type(struct reftable_record *rec)
{
- return rec->ops->val_type(rec->data);
+ return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
}
int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
uint8_t extra, struct string_view src, int hash_size)
{
- return rec->ops->decode(rec->data, key, extra, src, hash_size);
+ return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+ key, extra, src, hash_size);
}
void reftable_record_release(struct reftable_record *rec)
{
- rec->ops->release(rec->data);
+ reftable_record_vtable(rec)->release(reftable_record_data(rec));
}
int reftable_record_is_deletion(struct reftable_record *rec)
{
- return rec->ops->is_deletion(rec->data);
+ return reftable_record_vtable(rec)->is_deletion(
+ reftable_record_data(rec));
}
-void reftable_record_from_ref(struct reftable_record *rec,
- struct reftable_ref_record *ref_rec)
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size)
{
- assert(!rec->ops);
- rec->data = ref_rec;
- rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
- struct reftable_obj_record *obj_rec)
-{
- assert(!rec->ops);
- rec->data = obj_rec;
- rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
- struct reftable_index_record *index_rec)
-{
- assert(!rec->ops);
- rec->data = index_rec;
- rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
- struct reftable_log_record *log_rec)
-{
- assert(!rec->ops);
- rec->data = log_rec;
- rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
- assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
- return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
- assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
- return rec->data;
+ if (a->type != b->type)
+ return 0;
+ return reftable_record_vtable(a)->equal(
+ reftable_record_data(a), reftable_record_data(b), hash_size);
}
static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
return a == b;
}
-int reftable_ref_record_equal(struct reftable_ref_record *a,
- struct reftable_ref_record *b, int hash_size)
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+ const struct reftable_ref_record *b, int hash_size)
{
assert(hash_size > 0);
- if (!(0 == strcmp(a->refname, b->refname) &&
- a->update_index == b->update_index &&
- a->value_type == b->value_type))
+ if (!null_streq(a->refname, b->refname))
+ return 0;
+
+ if (a->update_index != b->update_index ||
+ a->value_type != b->value_type)
return 0;
switch (a->value_type) {
s->buf += n;
s->len -= n;
}
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+ switch (rec->type) {
+ case BLOCK_TYPE_REF:
+ return &rec->u.ref;
+ case BLOCK_TYPE_LOG:
+ return &rec->u.log;
+ case BLOCK_TYPE_INDEX:
+ return &rec->u.idx;
+ case BLOCK_TYPE_OBJ:
+ return &rec->u.obj;
+ }
+ abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+ switch (rec->type) {
+ case BLOCK_TYPE_REF:
+ return &reftable_ref_record_vtable;
+ case BLOCK_TYPE_LOG:
+ return &reftable_log_record_vtable;
+ case BLOCK_TYPE_INDEX:
+ return &reftable_index_record_vtable;
+ case BLOCK_TYPE_OBJ:
+ return &reftable_obj_record_vtable;
+ }
+ abort();
+}
+
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+ struct reftable_record clean = {
+ .type = typ,
+ };
+
+ /* the following is involved, but the naive solution (just return
+ * `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage
+ * clean.u.obj.offsets pointer on Windows VS CI. Go figure.
+ */
+ switch (typ) {
+ case BLOCK_TYPE_OBJ:
+ {
+ struct reftable_obj_record obj = { 0 };
+ clean.u.obj = obj;
+ break;
+ }
+ case BLOCK_TYPE_INDEX:
+ {
+ struct reftable_index_record idx = {
+ .last_key = STRBUF_INIT,
+ };
+ clean.u.idx = idx;
+ break;
+ }
+ case BLOCK_TYPE_REF:
+ {
+ struct reftable_ref_record ref = { 0 };
+ clean.u.ref = ref;
+ break;
+ }
+ case BLOCK_TYPE_LOG:
+ {
+ struct reftable_log_record log = { 0 };
+ clean.u.log = log;
+ break;
+ }
+ }
+ return clean;
+}
+
+void reftable_record_print(struct reftable_record *rec, int hash_size)
+{
+ printf("'%c': ", rec->type);
+ reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size);
+}
/* is this a tombstone? */
int (*is_deletion)(const void *rec);
-};
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
- void *data;
- struct reftable_record_vtable *ops;
+ /* Are two records equal? This assumes they have the same type. Returns 0 for non-equal. */
+ int (*equal)(const void *a, const void *b, int hash_size);
+
+ /* Print on stdout, for debugging. */
+ void (*print)(const void *rec, int hash_size);
};
/* returns true for recognized block types. Block start with the block type. */
int reftable_is_block_type(uint8_t typ);
-/* creates a malloced record of the given type. Dispose with record_destroy */
+/* return an initialized record for the given type */
struct reftable_record reftable_new_record(uint8_t typ);
/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
int offset_len;
};
-/* see struct record_vtable */
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_new_record() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+ uint8_t type;
+ union {
+ struct reftable_ref_record ref;
+ struct reftable_log_record log;
+ struct reftable_obj_record obj;
+ struct reftable_index_record idx;
+ } u;
+};
+/* see struct record_vtable */
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
+void reftable_record_print(struct reftable_record *rec, int hash_size);
void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
uint8_t reftable_record_type(struct reftable_record *rec);
void reftable_record_copy_from(struct reftable_record *rec,
int hash_size);
int reftable_record_is_deletion(struct reftable_record *rec);
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
void reftable_record_release(struct reftable_record *rec);
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
- struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
- struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
- struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
- struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
/* for qsort. */
int reftable_ref_record_compare_name(const void *a, const void *b);
static void test_copy(struct reftable_record *rec)
{
- struct reftable_record copy =
- reftable_new_record(reftable_record_type(rec));
+ struct reftable_record copy = { 0 };
+ uint8_t typ;
+
+ typ = reftable_record_type(rec);
+ copy = reftable_new_record(typ);
reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ);
/* do it twice to catch memory leaks */
reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ);
- switch (reftable_record_type(©)) {
- case BLOCK_TYPE_REF:
- EXPECT(reftable_ref_record_equal(reftable_record_as_ref(©),
- reftable_record_as_ref(rec),
- GIT_SHA1_RAWSZ));
- break;
- case BLOCK_TYPE_LOG:
- EXPECT(reftable_log_record_equal(reftable_record_as_log(©),
- reftable_record_as_log(rec),
- GIT_SHA1_RAWSZ));
- break;
- }
- reftable_record_destroy(©);
+ EXPECT(reftable_record_equal(rec, ©, GIT_SHA1_RAWSZ));
+
+ puts("testing print coverage:\n");
+ reftable_record_print(©, GIT_SHA1_RAWSZ);
+
+ reftable_record_release(©);
}
static void test_varint_roundtrip(void)
int i = 0;
for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
- struct reftable_ref_record in = { NULL };
- struct reftable_ref_record out = { NULL };
- struct reftable_record rec_out = { NULL };
+ struct reftable_record in = {
+ .type = BLOCK_TYPE_REF,
+ };
+ struct reftable_record out = { .type = BLOCK_TYPE_REF };
struct strbuf key = STRBUF_INIT;
- struct reftable_record rec = { NULL };
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
.buf = buffer,
.len = sizeof(buffer),
};
-
int n, m;
- in.value_type = i;
+ in.u.ref.value_type = i;
switch (i) {
case REFTABLE_REF_DELETION:
break;
case REFTABLE_REF_VAL1:
- in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
- set_hash(in.value.val1, 1);
+ in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+ set_hash(in.u.ref.value.val1, 1);
break;
case REFTABLE_REF_VAL2:
- in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
- set_hash(in.value.val2.value, 1);
- in.value.val2.target_value =
+ in.u.ref.value.val2.value =
reftable_malloc(GIT_SHA1_RAWSZ);
- set_hash(in.value.val2.target_value, 2);
+ set_hash(in.u.ref.value.val2.value, 1);
+ in.u.ref.value.val2.target_value =
+ reftable_malloc(GIT_SHA1_RAWSZ);
+ set_hash(in.u.ref.value.val2.target_value, 2);
break;
case REFTABLE_REF_SYMREF:
- in.value.symref = xstrdup("target");
+ in.u.ref.value.symref = xstrdup("target");
break;
}
- in.refname = xstrdup("refs/heads/master");
+ in.u.ref.refname = xstrdup("refs/heads/master");
- reftable_record_from_ref(&rec, &in);
- test_copy(&rec);
+ test_copy(&in);
- EXPECT(reftable_record_val_type(&rec) == i);
+ EXPECT(reftable_record_val_type(&in) == i);
- reftable_record_key(&rec, &key);
- n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+ reftable_record_key(&in, &key);
+ n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
EXPECT(n > 0);
/* decode into a non-zero reftable_record to test for leaks. */
-
- reftable_record_from_ref(&rec_out, &out);
- m = reftable_record_decode(&rec_out, key, i, dest,
- GIT_SHA1_RAWSZ);
+ m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
EXPECT(n == m);
- EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
- reftable_record_release(&rec_out);
+ EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+ GIT_SHA1_RAWSZ));
+ reftable_record_release(&in);
strbuf_release(&key);
- reftable_ref_record_release(&in);
+ reftable_record_release(&out);
}
}
static void test_reftable_log_record_roundtrip(void)
{
int i;
- struct reftable_log_record in[2] = {
+
+ struct reftable_log_record in[] = {
{
.refname = xstrdup("refs/heads/master"),
.update_index = 42,
.refname = xstrdup("refs/heads/master"),
.update_index = 22,
.value_type = REFTABLE_LOG_DELETION,
+ },
+ {
+ .refname = xstrdup("branch"),
+ .update_index = 33,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+ .new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+ /* rest of fields left empty. */
+ },
+ },
}
};
set_test_hash(in[0].value.update.new_hash, 1);
set_test_hash(in[0].value.update.old_hash, 2);
+ set_test_hash(in[2].value.update.new_hash, 3);
+ set_test_hash(in[2].value.update.old_hash, 4);
for (i = 0; i < ARRAY_SIZE(in); i++) {
- struct reftable_record rec = { NULL };
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
struct strbuf key = STRBUF_INIT;
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
.len = sizeof(buffer),
};
/* populate out, to check for leaks. */
- struct reftable_log_record out = {
- .refname = xstrdup("old name"),
- .value_type = REFTABLE_LOG_UPDATE,
- .value = {
- .update = {
- .new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
- .old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
- .name = xstrdup("old name"),
- .email = xstrdup("old@email"),
- .message = xstrdup("old message"),
+ struct reftable_record out = {
+ .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = xstrdup("old name"),
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+ .old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+ .name = xstrdup("old name"),
+ .email = xstrdup("old@email"),
+ .message = xstrdup("old message"),
+ },
},
},
};
- struct reftable_record rec_out = { NULL };
int n, m, valtype;
- reftable_record_from_log(&rec, &in[i]);
+ rec.u.log = in[i];
test_copy(&rec);
n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
EXPECT(n >= 0);
- reftable_record_from_log(&rec_out, &out);
valtype = reftable_record_val_type(&rec);
- m = reftable_record_decode(&rec_out, key, valtype, dest,
+ m = reftable_record_decode(&out, key, valtype, dest,
GIT_SHA1_RAWSZ);
EXPECT(n == m);
- EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+ EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
+ GIT_SHA1_RAWSZ));
reftable_log_record_release(&in[i]);
strbuf_release(&key);
- reftable_record_release(&rec_out);
+ reftable_record_release(&out);
}
}
} };
int i = 0;
for (i = 0; i < ARRAY_SIZE(recs); i++) {
- struct reftable_obj_record in = recs[i];
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
.buf = buffer,
.len = sizeof(buffer),
};
- struct reftable_record rec = { NULL };
+ struct reftable_record in = {
+ .type = BLOCK_TYPE_OBJ,
+ .u.obj = recs[i],
+ };
struct strbuf key = STRBUF_INIT;
- struct reftable_obj_record out = { NULL };
- struct reftable_record rec_out = { NULL };
+ struct reftable_record out = { .type = BLOCK_TYPE_OBJ };
int n, m;
uint8_t extra;
- reftable_record_from_obj(&rec, &in);
- test_copy(&rec);
- reftable_record_key(&rec, &key);
- n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+ test_copy(&in);
+ reftable_record_key(&in, &key);
+ n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
EXPECT(n > 0);
- extra = reftable_record_val_type(&rec);
- reftable_record_from_obj(&rec_out, &out);
- m = reftable_record_decode(&rec_out, key, extra, dest,
+ extra = reftable_record_val_type(&in);
+ m = reftable_record_decode(&out, key, extra, dest,
GIT_SHA1_RAWSZ);
EXPECT(n == m);
- EXPECT(in.hash_prefix_len == out.hash_prefix_len);
- EXPECT(in.offset_len == out.offset_len);
-
- EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
- in.hash_prefix_len));
- EXPECT(0 == memcmp(in.offsets, out.offsets,
- sizeof(uint64_t) * in.offset_len));
+ EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
strbuf_release(&key);
- reftable_record_release(&rec_out);
+ reftable_record_release(&out);
}
}
static void test_reftable_index_record_roundtrip(void)
{
- struct reftable_index_record in = {
- .offset = 42,
- .last_key = STRBUF_INIT,
+ struct reftable_record in = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = {
+ .offset = 42,
+ .last_key = STRBUF_INIT,
+ },
};
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
.len = sizeof(buffer),
};
struct strbuf key = STRBUF_INIT;
- struct reftable_record rec = { NULL };
- struct reftable_index_record out = { .last_key = STRBUF_INIT };
- struct reftable_record out_rec = { NULL };
+ struct reftable_record out = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = { .last_key = STRBUF_INIT },
+ };
int n, m;
uint8_t extra;
- strbuf_addstr(&in.last_key, "refs/heads/master");
- reftable_record_from_index(&rec, &in);
- reftable_record_key(&rec, &key);
- test_copy(&rec);
+ strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
+ reftable_record_key(&in, &key);
+ test_copy(&in);
- EXPECT(0 == strbuf_cmp(&key, &in.last_key));
- n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+ EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
+ n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
EXPECT(n > 0);
- extra = reftable_record_val_type(&rec);
- reftable_record_from_index(&out_rec, &out);
- m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+ extra = reftable_record_val_type(&in);
+ m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
EXPECT(m == n);
- EXPECT(in.offset == out.offset);
+ EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
- reftable_record_release(&out_rec);
+ reftable_record_release(&out);
strbuf_release(&key);
- strbuf_release(&in.last_key);
+ strbuf_release(&in.u.idx.last_key);
}
int record_test_main(int argc, const char *argv[])
/* Returns the first hash, or NULL if `rec` is not of type
* REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec);
/* Returns the second hash, or NULL if `rec` is not of type
* REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec);
/* returns whether 'ref' represents a deletion */
int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
/* prints a reftable_ref_record onto stdout. Useful for debugging. */
-void reftable_ref_record_print(struct reftable_ref_record *ref,
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
uint32_t hash_id);
/* frees and nulls all pointer values inside `ref`. */
void reftable_ref_record_release(struct reftable_ref_record *ref);
/* returns whether two reftable_ref_records are the same. Useful for testing. */
-int reftable_ref_record_equal(struct reftable_ref_record *a,
- struct reftable_ref_record *b, int hash_size);
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+ const struct reftable_ref_record *b, int hash_size);
/* reftable_log_record holds a reflog entry */
struct reftable_log_record {
void reftable_log_record_release(struct reftable_log_record *log);
/* returns whether two records are equal. Useful for testing. */
-int reftable_log_record_equal(struct reftable_log_record *a,
- struct reftable_log_record *b, int hash_size);
+int reftable_log_record_equal(const struct reftable_log_record *a,
+ const struct reftable_log_record *b, int hash_size);
/* dumps a reftable_log_record on stdout, for debugging/testing. */
void reftable_log_record_print(struct reftable_log_record *log,
This struct becomes invalid when the writer is freed.
*/
-const struct reftable_stats *writer_stats(struct reftable_writer *w);
+const struct reftable_stats *reftable_writer_stats(struct reftable_writer *w);
/* reftable_writer_free deallocates memory for the writer */
void reftable_writer_free(struct reftable_writer *w);
+++ /dev/null
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "basics.h"
-#include "record.h"
-#include "generic.h"
-#include "reftable-iterator.h"
-#include "reftable-generic.h"
-
-int reftable_table_seek_ref(struct reftable_table *tab,
- struct reftable_iterator *it, const char *name)
-{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
- return tab->ops->seek_record(tab->table_arg, it, &rec);
-}
-
-int reftable_table_read_ref(struct reftable_table *tab, const char *name,
- struct reftable_ref_record *ref)
-{
- struct reftable_iterator it = { NULL };
- int err = reftable_table_seek_ref(tab, &it, name);
- if (err)
- goto done;
-
- err = reftable_iterator_next_ref(&it, ref);
- if (err)
- goto done;
-
- if (strcmp(ref->refname, name) ||
- reftable_ref_record_is_deletion(ref)) {
- reftable_ref_record_release(ref);
- err = 1;
- goto done;
- }
-
-done:
- reftable_iterator_destroy(&it);
- return err;
-}
-
-uint64_t reftable_table_max_update_index(struct reftable_table *tab)
-{
- return tab->ops->max_update_index(tab->table_arg);
-}
-
-uint64_t reftable_table_min_update_index(struct reftable_table *tab)
-{
- return tab->ops->min_update_index(tab->table_arg);
-}
-
-uint32_t reftable_table_hash_id(struct reftable_table *tab)
-{
- return tab->ops->hash_id(tab->table_arg);
-}
-
-void reftable_iterator_destroy(struct reftable_iterator *it)
-{
- if (!it->ops) {
- return;
- }
- it->ops->close(it->iter_arg);
- it->ops = NULL;
- FREE_AND_NULL(it->iter_arg);
-}
-
-int reftable_iterator_next_ref(struct reftable_iterator *it,
- struct reftable_ref_record *ref)
-{
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, ref);
- return iterator_next(it, &rec);
-}
-
-int reftable_iterator_next_log(struct reftable_iterator *it,
- struct reftable_log_record *log)
-{
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, log);
- return iterator_next(it, &rec);
-}
-
-int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
-{
- return it->ops->next(it->iter_arg, rec);
-}
-
-static int empty_iterator_next(void *arg, struct reftable_record *rec)
-{
- return 1;
-}
-
-static void empty_iterator_close(void *arg)
-{
-}
-
-static struct reftable_iterator_vtable empty_vtable = {
- .next = &empty_iterator_next,
- .close = &empty_iterator_close,
-};
-
-void iterator_set_empty(struct reftable_iterator *it)
-{
- assert(!it->ops);
- it->iter_arg = NULL;
- it->ops = &empty_vtable;
-}
struct strbuf new_table_path = STRBUF_INIT;
int err = 0;
int have_lock = 0;
- int lock_file_fd = 0;
+ int lock_file_fd = -1;
int compact_count = last - first + 1;
char **listp = NULL;
char **delete_on_success =
}
/* Don't want to write to the lock for now. */
close(lock_file_fd);
- lock_file_fd = 0;
+ lock_file_fd = -1;
have_lock = 1;
err = stack_uptodate(st);
goto done;
}
err = close(lock_file_fd);
- lock_file_fd = 0;
+ lock_file_fd = -1;
if (err < 0) {
err = REFTABLE_IO_ERROR;
unlink(new_table_path.buf);
listp++;
}
free_names(subtable_locks);
- if (lock_file_fd > 0) {
+ if (lock_file_fd >= 0) {
close(lock_file_fd);
- lock_file_fd = 0;
+ lock_file_fd = -1;
}
if (have_lock) {
unlink(lock_file_name.buf);
EXPECT(0 == strcmp(want[i], names[i]));
}
free_names(names);
- remove(fn);
+ (void) remove(fn);
}
static void test_parse_names(void)
EXPECT_ERR(err);
err = reftable_stack_auto_compact(st);
+ EXPECT_ERR(err);
EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
}
#include "hash.h" /* hash ID, sizes.*/
#include "dir.h" /* remove_dir_recursively, for tests.*/
-#include <zlib.h>
-
-#ifdef NO_UNCOMPRESS2
-/*
- * This is uncompress2, which is only available in zlib >= 1.2.9
- * (released as of early 2017)
- */
-int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
- uLong *sourceLen);
-#endif
-
int hash_size(uint32_t id);
#endif
void reftable_writer_free(struct reftable_writer *w)
{
+ if (!w)
+ return;
reftable_free(w->block);
reftable_free(w);
}
writer_reinit_block_writer(w, reftable_record_type(rec));
err = block_writer_add(w->block_writer, rec);
- if (err < 0) {
+ if (err == -1) {
/* we are writing into memory, so an error can only mean it
* doesn't fit. */
err = REFTABLE_ENTRY_TOO_BIG_ERROR;
goto done;
}
- err = 0;
done:
strbuf_release(&key);
return err;
int reftable_writer_add_ref(struct reftable_writer *w,
struct reftable_ref_record *ref)
{
- struct reftable_record rec = { NULL };
- struct reftable_ref_record copy = *ref;
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = *ref,
+ };
int err = 0;
if (ref->refname == NULL)
ref->update_index > w->max_update_index)
return REFTABLE_API_ERROR;
- reftable_record_from_ref(&rec, ©);
- copy.update_index -= w->min_update_index;
+ rec.u.ref.update_index -= w->min_update_index;
err = writer_add_record(w, &rec);
if (err < 0)
static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
struct reftable_log_record *log)
{
- struct reftable_record rec = { NULL };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_LOG,
+ .u.log = *log,
+ };
if (w->block_writer &&
block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
int err = writer_finish_public_section(w);
w->next -= w->pending_padding;
w->pending_padding = 0;
-
- reftable_record_from_log(&rec, log);
return writer_add_record(w, &rec);
}
w->index_len = 0;
w->index_cap = 0;
for (i = 0; i < idx_len; i++) {
- struct reftable_record rec = { NULL };
- reftable_record_from_index(&rec, idx + i);
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = idx[i],
+ };
if (block_writer_add(w->block_writer, &rec) == 0) {
continue;
}
{
struct write_record_arg *arg = void_arg;
struct obj_index_tree_node *entry = key;
- struct reftable_obj_record obj_rec = {
- .hash_prefix = (uint8_t *)entry->hash.buf,
- .hash_prefix_len = arg->w->stats.object_id_len,
- .offsets = entry->offsets,
- .offset_len = entry->offset_len,
- };
- struct reftable_record rec = { NULL };
+ struct reftable_record
+ rec = { .type = BLOCK_TYPE_OBJ,
+ .u.obj = {
+ .hash_prefix = (uint8_t *)entry->hash.buf,
+ .hash_prefix_len = arg->w->stats.object_id_len,
+ .offsets = entry->offsets,
+ .offset_len = entry->offset_len,
+ } };
if (arg->err < 0)
goto done;
- reftable_record_from_obj(&rec, &obj_rec);
arg->err = block_writer_add(arg->w->block_writer, &rec);
if (arg->err == 0)
goto done;
arg->err = block_writer_add(arg->w->block_writer, &rec);
if (arg->err == 0)
goto done;
- obj_rec.offset_len = 0;
+
+ rec.u.obj.offset_len = 0;
arg->err = block_writer_add(arg->w->block_writer, &rec);
/* Should be able to write into a fresh block. */
static int writer_dump_object_index(struct reftable_writer *w)
{
struct write_record_arg closure = { .w = w };
- struct common_prefix_arg common = { NULL };
+ struct common_prefix_arg common = {
+ .max = 1, /* obj_id_len should be >= 2. */
+ };
if (w->obj_index_tree) {
infix_walk(w->obj_index_tree, &update_common, &common);
}
return writer_flush_nonempty_block(w);
}
-const struct reftable_stats *writer_stats(struct reftable_writer *w)
+const struct reftable_stats *reftable_writer_stats(struct reftable_writer *w)
{
return &w->stats;
}
{
struct strbuf buf = STRBUF_INIT;
int nongit;
+ int ret = 1;
setup_git_directory_gently(&nongit);
if (argc < 2) {
error(_("remote-curl: usage: git remote-curl <remote> [<url>]"));
- return 1;
+ goto cleanup;
}
options.verbosity = 1;
if (strbuf_getline_lf(&buf, stdin) == EOF) {
if (ferror(stdin))
error(_("remote-curl: error reading command stream from git"));
- return 1;
+ goto cleanup;
}
if (buf.len == 0)
break;
break;
} else {
error(_("remote-curl: unknown command '%s' from git"), buf.buf);
- return 1;
+ goto cleanup;
}
strbuf_reset(&buf);
} while (1);
http_cleanup();
+ ret = 0;
+cleanup:
+ strbuf_release(&buf);
- return 0;
+ return ret;
}
repo->remote_state->current_branch = NULL;
if (startup_info->have_repository) {
- int ignore_errno;
const char *head_ref = refs_resolve_ref_unsafe(
- get_main_ref_store(repo), "HEAD", 0, NULL, &flag, &ignore_errno);
+ get_main_ref_store(repo), "HEAD", 0, NULL, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
skip_prefix(head_ref, "refs/heads/", &head_ref)) {
repo->remote_state->current_branch = make_branch(
return branch->push_tracking_ref;
}
-static int ignore_symref_update(const char *refname)
+static int ignore_symref_update(const char *refname, struct strbuf *scratch)
{
- int flag;
-
- if (!resolve_ref_unsafe(refname, 0, NULL, &flag))
- return 0; /* non-existing refs are OK */
- return (flag & REF_ISSYMREF);
+ return !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);
}
/*
static struct ref *get_expanded_map(const struct ref *remote_refs,
const struct refspec_item *refspec)
{
+ struct strbuf scratch = STRBUF_INIT;
const struct ref *ref;
struct ref *ret = NULL;
struct ref **tail = &ret;
for (ref = remote_refs; ref; ref = ref->next) {
char *expn_name = NULL;
+ strbuf_reset(&scratch);
+
if (strchr(ref->name, '^'))
continue; /* a dereference item */
if (match_name_with_pattern(refspec->src, ref->name,
refspec->dst, &expn_name) &&
- !ignore_symref_update(expn_name)) {
+ !ignore_symref_update(expn_name, &scratch)) {
struct ref *cpy = copy_ref(ref);
cpy->peer_ref = alloc_ref(expn_name);
free(expn_name);
}
+ strbuf_release(&scratch);
return ret;
}
/* Defaults */
r->settings.index_version = -1;
r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
- r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
+ r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE;
/* Booleans config or default, cascades to other settings */
repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0);
}
if (!repo_config_get_string(r, "fetch.negotiationalgorithm", &strval)) {
+ int fetch_default = r->settings.fetch_negotiation_algorithm;
if (!strcasecmp(strval, "skipping"))
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
else if (!strcasecmp(strval, "noop"))
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP;
+ else if (!strcasecmp(strval, "consecutive"))
+ r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE;
+ else if (!strcasecmp(strval, "default"))
+ r->settings.fetch_negotiation_algorithm = fetch_default;
+ else
+ die("unknown fetch negotiation algorithm '%s'", strval);
}
/*
return ret;
}
+static void repo_clear_path_cache(struct repo_path_cache *cache)
+{
+ FREE_AND_NULL(cache->squash_msg);
+ FREE_AND_NULL(cache->squash_msg);
+ FREE_AND_NULL(cache->merge_msg);
+ FREE_AND_NULL(cache->merge_rr);
+ FREE_AND_NULL(cache->merge_mode);
+ FREE_AND_NULL(cache->merge_head);
+ FREE_AND_NULL(cache->merge_autostash);
+ FREE_AND_NULL(cache->auto_merge);
+ FREE_AND_NULL(cache->fetch_head);
+ FREE_AND_NULL(cache->shallow);
+}
+
void repo_clear(struct repository *repo)
{
FREE_AND_NULL(repo->gitdir);
remote_state_clear(repo->remote_state);
FREE_AND_NULL(repo->remote_state);
}
+
+ repo_clear_path_cache(&repo->cached_paths);
}
int repo_read_index(struct repository *repo)
if (repo->settings.command_requires_full_index)
ensure_full_index(repo->index);
+ /*
+ * If sparse checkouts are in use, check whether paths with the
+ * SKIP_WORKTREE attribute are missing from the worktree; if not,
+ * clear that attribute for that path.
+ */
+ clear_skip_worktree_from_present_files(repo->index);
+
return res;
}
};
enum fetch_negotiation_setting {
- FETCH_NEGOTIATION_DEFAULT,
+ FETCH_NEGOTIATION_CONSECUTIVE,
FETCH_NEGOTIATION_SKIPPING,
FETCH_NEGOTIATION_NOOP,
};
int core_multi_pack_index;
};
+struct repo_path_cache {
+ char *squash_msg;
+ char *merge_msg;
+ char *merge_rr;
+ char *merge_mode;
+ char *merge_head;
+ char *merge_autostash;
+ char *auto_merge;
+ char *fetch_head;
+ char *shallow;
+};
+
struct repository {
/* Environment */
/*
/*
* Contains path to often used file names.
*/
- struct path_cache cached_paths;
+ struct repo_path_cache cached_paths;
/*
* Path to the repository's graft file.
const struct rerere_id *id, const char *path,
mmfile_t *cur, mmbuffer_t *result)
{
- int ret;
+ enum ll_merge_result ret;
mmfile_t base = {NULL, 0}, other = {NULL, 0};
if (read_mmfile(&base, rerere_path(id, "preimage")) ||
- read_mmfile(&other, rerere_path(id, "postimage")))
- ret = 1;
- else
+ read_mmfile(&other, rerere_path(id, "postimage"))) {
+ ret = LL_MERGE_CONFLICT;
+ } else {
/*
* A three-way merge. Note that this honors user-customizable
* low-level merge driver settings.
*/
ret = ll_merge(result, path, &base, NULL, cur, "", &other, "",
istate, NULL);
+ }
free(base.ptr);
free(other.ptr);
#include "tree-walk.h"
#include "tree.h"
#include "unpack-trees.h"
+#include "hook.h"
-int reset_head(struct repository *r, struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head,
- const char *default_reflog_action)
+static int update_refs(const struct reset_head_opts *opts,
+ const struct object_id *oid,
+ const struct object_id *head)
{
- unsigned detach_head = flags & RESET_HEAD_DETACH;
- unsigned reset_hard = flags & RESET_HEAD_HARD;
- unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
- unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
- unsigned update_orig_head = flags & RESET_ORIG_HEAD;
- struct object_id head_oid;
+ unsigned detach_head = opts->flags & RESET_HEAD_DETACH;
+ unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
+ const struct object_id *orig_head = opts->orig_head;
+ const char *switch_to_branch = opts->branch;
+ const char *reflog_branch = opts->branch_msg;
+ const char *reflog_head = opts->head_msg;
+ const char *reflog_orig_head = opts->orig_head_msg;
+ const char *default_reflog_action = opts->default_reflog_action;
+ struct object_id *old_orig = NULL, oid_old_orig;
+ struct strbuf msg = STRBUF_INIT;
+ const char *reflog_action;
+ size_t prefix_len;
+ int ret;
+
+ if ((update_orig_head && !reflog_orig_head) || !reflog_head) {
+ if (!default_reflog_action)
+ BUG("default_reflog_action must be given when reflog messages are omitted");
+ reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
+ strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action :
+ default_reflog_action);
+ }
+ prefix_len = msg.len;
+
+ if (update_orig_head) {
+ if (!get_oid("ORIG_HEAD", &oid_old_orig))
+ old_orig = &oid_old_orig;
+ if (head) {
+ if (!reflog_orig_head) {
+ strbuf_addstr(&msg, "updating ORIG_HEAD");
+ reflog_orig_head = msg.buf;
+ }
+ update_ref(reflog_orig_head, "ORIG_HEAD",
+ orig_head ? orig_head : head,
+ old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
+ } else if (old_orig)
+ delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+ }
+
+ if (!reflog_head) {
+ strbuf_setlen(&msg, prefix_len);
+ strbuf_addstr(&msg, "updating HEAD");
+ reflog_head = msg.buf;
+ }
+ if (!switch_to_branch)
+ ret = update_ref(reflog_head, "HEAD", oid, head,
+ detach_head ? REF_NO_DEREF : 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ else {
+ ret = update_ref(reflog_branch ? reflog_branch : reflog_head,
+ switch_to_branch, oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ if (!ret)
+ ret = create_symref("HEAD", switch_to_branch,
+ reflog_head);
+ }
+ if (!ret && run_hook)
+ run_hooks_l("post-checkout",
+ oid_to_hex(head ? head : null_oid()),
+ oid_to_hex(oid), "1", NULL);
+ strbuf_release(&msg);
+ return ret;
+}
+
+int reset_head(struct repository *r, const struct reset_head_opts *opts)
+{
+ const struct object_id *oid = opts->oid;
+ const char *switch_to_branch = opts->branch;
+ unsigned reset_hard = opts->flags & RESET_HEAD_HARD;
+ unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;
+ unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
+ struct object_id *head = NULL, head_oid;
struct tree_desc desc[2] = { { NULL }, { NULL } };
struct lock_file lock = LOCK_INIT;
struct unpack_trees_options unpack_tree_opts = { 0 };
struct tree *tree;
- const char *reflog_action;
- struct strbuf msg = STRBUF_INIT;
- size_t prefix_len;
- struct object_id *orig = NULL, oid_orig,
- *old_orig = NULL, oid_old_orig;
+ const char *action;
int ret = 0, nr = 0;
if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
BUG("Not a fully qualified branch: '%s'", switch_to_branch);
+ if (opts->orig_head_msg && !update_orig_head)
+ BUG("ORIG_HEAD reflog message given without updating ORIG_HEAD");
+
+ if (opts->branch_msg && !opts->branch)
+ BUG("branch reflog message given without a branch");
+
if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
ret = -1;
goto leave_reset_head;
}
- if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
+ if (!get_oid("HEAD", &head_oid)) {
+ head = &head_oid;
+ } else if (!oid || !reset_hard) {
ret = error(_("could not determine HEAD revision"));
goto leave_reset_head;
}
oid = &head_oid;
if (refs_only)
- goto reset_head_refs;
+ return update_refs(opts, oid, head);
+ action = reset_hard ? "reset" : "checkout";
setup_unpack_trees_porcelain(&unpack_tree_opts, action);
unpack_tree_opts.head_idx = 1;
unpack_tree_opts.src_index = r->index;
unpack_tree_opts.merge = 1;
unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
- if (!detach_head)
+ if (reset_hard)
unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
if (repo_read_index_unmerged(r) < 0) {
goto leave_reset_head;
}
-reset_head_refs:
- reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
- strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action);
- prefix_len = msg.len;
-
- if (update_orig_head) {
- if (!get_oid("ORIG_HEAD", &oid_old_orig))
- old_orig = &oid_old_orig;
- if (!get_oid("HEAD", &oid_orig)) {
- orig = &oid_orig;
- if (!reflog_orig_head) {
- strbuf_addstr(&msg, "updating ORIG_HEAD");
- reflog_orig_head = msg.buf;
- }
- update_ref(reflog_orig_head, "ORIG_HEAD", orig,
- old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
- } else if (old_orig)
- delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
- }
-
- if (!reflog_head) {
- strbuf_setlen(&msg, prefix_len);
- strbuf_addstr(&msg, "updating HEAD");
- reflog_head = msg.buf;
- }
- if (!switch_to_branch)
- ret = update_ref(reflog_head, "HEAD", oid, orig,
- detach_head ? REF_NO_DEREF : 0,
- UPDATE_REFS_MSG_ON_ERR);
- else {
- ret = update_ref(reflog_head, switch_to_branch, oid,
- NULL, 0, UPDATE_REFS_MSG_ON_ERR);
- if (!ret)
- ret = create_symref("HEAD", switch_to_branch,
- reflog_head);
- }
- if (run_hook)
- run_hook_le(NULL, "post-checkout",
- oid_to_hex(orig ? orig : null_oid()),
- oid_to_hex(oid), "1", NULL);
+ if (oid != &head_oid || update_orig_head || switch_to_branch)
+ ret = update_refs(opts, oid, head);
leave_reset_head:
- strbuf_release(&msg);
rollback_lock_file(&lock);
clear_unpack_trees_porcelain(&unpack_tree_opts);
while (nr)
#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
+/* Request a detached checkout */
#define RESET_HEAD_DETACH (1<<0)
+/* Request a reset rather than a checkout */
#define RESET_HEAD_HARD (1<<1)
+/* Run the post-checkout hook */
#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
+/* Only update refs, do not touch the worktree */
#define RESET_HEAD_REFS_ONLY (1<<3)
+/* Update ORIG_HEAD as well as HEAD */
#define RESET_ORIG_HEAD (1<<4)
-int reset_head(struct repository *r, struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head,
- const char *default_reflog_action);
+struct reset_head_opts {
+ /*
+ * The commit to checkout/reset to. Defaults to HEAD.
+ */
+ const struct object_id *oid;
+ /*
+ * Optional value to set ORIG_HEAD. Defaults to HEAD.
+ */
+ const struct object_id *orig_head;
+ /*
+ * Optional branch to switch to.
+ */
+ const char *branch;
+ /*
+ * Flags defined above.
+ */
+ unsigned flags;
+ /*
+ * Optional reflog message for branch, defaults to head_msg.
+ */
+ const char *branch_msg;
+ /*
+ * Optional reflog message for HEAD, if this omitted but oid or branch
+ * are given then default_reflog_action must be given.
+ */
+ const char *head_msg;
+ /*
+ * Optional reflog message for ORIG_HEAD, if this omitted and flags
+ * contains RESET_ORIG_HEAD then default_reflog_action must be given.
+ */
+ const char *orig_head_msg;
+ /*
+ * Action to use in default reflog messages, only required if a ref is
+ * being updated and the reflog messages above are omitted.
+ */
+ const char *default_reflog_action;
+};
+
+int reset_head(struct repository *r, const struct reset_head_opts *opts);
#endif
stack->nr = stack->alloc = 0;
}
-static void mark_one_parent_uninteresting(struct commit *commit,
+static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit,
struct commit_stack *pending)
{
struct commit_list *l;
* wasn't uninteresting), in which case we need
* to mark its parents recursively too..
*/
- for (l = commit->parents; l; l = l->next)
+ for (l = commit->parents; l; l = l->next) {
commit_stack_push(pending, l->item);
+ if (revs && revs->exclude_first_parent_only)
+ break;
+ }
}
-void mark_parents_uninteresting(struct commit *commit)
+void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit)
{
struct commit_stack pending = COMMIT_STACK_INIT;
struct commit_list *l;
- for (l = commit->parents; l; l = l->next)
- mark_one_parent_uninteresting(l->item, &pending);
+ for (l = commit->parents; l; l = l->next) {
+ mark_one_parent_uninteresting(revs, l->item, &pending);
+ if (revs && revs->exclude_first_parent_only)
+ break;
+ }
while (pending.nr > 0)
- mark_one_parent_uninteresting(commit_stack_pop(&pending),
+ mark_one_parent_uninteresting(revs, commit_stack_pop(&pending),
&pending);
commit_stack_clear(&pending);
if (repo_parse_commit(revs->repo, commit) < 0)
die("unable to parse commit %s", name);
if (flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(revs, commit);
if (!revs->topo_order || !generation_numbers_enabled(the_repository))
revs->limited = 1;
if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
continue;
if (p->parents)
- mark_parents_uninteresting(p);
+ mark_parents_uninteresting(revs, p);
if (p->object.flags & SEEN)
continue;
p->object.flags |= (SEEN | NOT_USER_GIVEN);
commit_list_insert_by_date(p, list);
if (queue)
prio_queue_put(queue, p);
+ if (revs->exclude_first_parent_only)
+ break;
}
return 0;
}
if (process_parents(revs, commit, &original_list, NULL) < 0)
return -1;
if (obj->flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(revs, commit);
slop = still_interesting(original_list, date, slop, &interesting_cache);
if (slop)
continue;
revs->commit_format = CMIT_FMT_DEFAULT;
revs->expand_tabs_in_log_default = 8;
- grep_init(&revs->grep_filter, revs->repo, prefix);
+ grep_init(&revs->grep_filter, revs->repo);
revs->grep_filter.status_only = 1;
repo_diff_setup(revs->repo, &revs->diffopt);
return argcount;
} else if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
+ } else if (!strcmp(arg, "--exclude-first-parent-only")) {
+ revs->exclude_first_parent_only = 1;
} else if (!strcmp(arg, "--ancestry-path")) {
revs->ancestry_path = 1;
revs->simplify_history = 0;
revs->pretty_given = 1;
revs->abbrev_commit = 1;
} else if (!strcmp(arg, "--graph")) {
- revs->topo_order = 1;
- revs->rewrite_parents = 1;
+ graph_clear(revs->graph);
revs->graph = graph_init(revs);
+ } else if (!strcmp(arg, "--no-graph")) {
+ graph_clear(revs->graph);
+ revs->graph = NULL;
} else if (!strcmp(arg, "--encode-email-headers")) {
revs->encode_email_headers = 1;
} else if (!strcmp(arg, "--no-encode-email-headers")) {
unkv[(*unkc)++] = arg;
return opts;
}
- if (revs->graph && revs->track_linear)
- die(_("options '%s' and '%s' cannot be used together"), "--show-linear-break", "--graph");
return 1;
}
ctx->argc -= n;
}
+void revision_opts_finish(struct rev_info *revs)
+{
+ if (revs->graph && revs->track_linear)
+ die(_("options '%s' and '%s' cannot be used together"), "--show-linear-break", "--graph");
+
+ if (revs->graph) {
+ revs->topo_order = 1;
+ revs->rewrite_parents = 1;
+ }
+}
+
static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
void *cb_data, const char *term)
{
break;
}
}
+ revision_opts_finish(revs);
if (prune_data.nr) {
/*
diff_setup_done(&revs->diffopt);
- grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
- &revs->grep_filter);
if (!is_encoding_utf8(get_log_output_encoding()))
revs->grep_filter.ignore_locale = 1;
compile_grep_patterns(&revs->grep_filter);
return;
if (c->object.flags & UNINTERESTING)
- mark_parents_uninteresting(c);
+ mark_parents_uninteresting(revs, c);
for (p = c->parents; p; p = p->next)
test_flag_and_insert(&info->explore_queue, p->item, TOPO_WALK_EXPLORED);
bisect:1,
ancestry_path:1,
first_parent_only:1,
+ exclude_first_parent_only:1,
line_level_traverse:1,
tree_blobs_in_commit_order:1,
combine_merges:1,
combined_all_paths:1,
dense_combined_merges:1,
- first_parent_merges:1;
+ first_parent_merges:1,
+ remerge_diff:1;
/* Format info */
int show_notes;
/* misc. flags related to '--no-kept-objects' */
unsigned keep_pack_cache_flags;
+
+ /* Location where temporary objects for remerge-diff are written. */
+ struct tmp_objdir *remerge_objdir;
};
int ref_excluded(struct string_list *, const char *path);
#define REVARG_COMMITTISH 02
int handle_revision_arg(const char *arg, struct rev_info *revs,
int flags, unsigned revarg_opt);
+void revision_opts_finish(struct rev_info *revs);
/**
* Reset the flags used by the revision walking api. You can use this to do
void put_revision_mark(const struct rev_info *revs,
const struct commit *commit);
-void mark_parents_uninteresting(struct commit *commit);
+void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit);
void mark_tree_uninteresting(struct repository *r, struct tree *tree);
void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees);
#endif
}
-int run_hook_ve(const char *const *env, const char *name, va_list args)
-{
- struct child_process hook = CHILD_PROCESS_INIT;
- const char *p;
-
- p = find_hook(name);
- if (!p)
- return 0;
-
- strvec_push(&hook.args, p);
- while ((p = va_arg(args, const char *)))
- strvec_push(&hook.args, p);
- if (env)
- strvec_pushv(&hook.env_array, (const char **)env);
- hook.no_stdin = 1;
- hook.stdout_to_stderr = 1;
- hook.trace2_hook_name = name;
-
- return run_command(&hook);
-}
-
-int run_hook_le(const char *const *env, const char *name, ...)
-{
- va_list args;
- int ret;
-
- va_start(args, name);
- ret = run_hook_ve(env, name, args);
- va_end(args);
-
- return ret;
-}
-
struct io_pump {
/* initialized by caller */
int fd;
*/
int run_command(struct child_process *);
-/**
- * Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
- * The further arguments correspond to the hook arguments.
- * The last argument has to be NULL to terminate the arguments list.
- * If the hook does not exist or is not executable, the return
- * value will be zero.
- * If it is executable, the hook will be executed and the exit
- * status of the hook is returned.
- * On execution, .stdout_to_stderr and .no_stdin will be set.
- */
-LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
-
/*
* Trigger an auto-gc
*/
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
struct ref_store *refs;
- int resolve_errno;
commit = lookup_commit(r, oid);
if (!commit)
diff_setup_done(&rev.diffopt);
refs = get_main_ref_store(the_repository);
- head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL,
- &resolve_errno);
- if (!head) {
- errno = resolve_errno;
- die_errno(_("unable to resolve HEAD after creating commit"));
- }
+ head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
+ if (!head)
+ die(_("unable to resolve HEAD after creating commit"));
if (!strcmp(head, "HEAD"))
head = _("detached HEAD");
else
return error(_("invalid key: %s"), key);
if (!error_flag)
- return error(_("invalid value for %s: %s"), key, value);
+ return error(_("invalid value for '%s': '%s'"), key, value);
return 0;
}
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
strbuf_addf(&msg, "rebase (label) '%.*s'", len, name);
- transaction = ref_store_transaction_begin(refs, &err);
+ transaction = ref_store_transaction_begin(refs, 0, &err);
if (!transaction) {
error("%s", err.buf);
ret = -1;
return -1;
}
-void create_autostash(struct repository *r, const char *path,
- const char *default_reflog_action)
+void create_autostash(struct repository *r, const char *path)
{
struct strbuf buf = STRBUF_INIT;
struct lock_file lock_file = LOCK_INIT;
if (has_unstaged_changes(r, 1) ||
has_uncommitted_changes(r, 1)) {
struct child_process stash = CHILD_PROCESS_INIT;
+ struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };
struct object_id oid;
strvec_pushl(&stash.args,
path);
write_file(path, "%s", oid_to_hex(&oid));
printf(_("Created autostash: %s\n"), buf.buf);
- if (reset_head(r, NULL, "reset --hard",
- NULL, RESET_HEAD_HARD, NULL, NULL,
- default_reflog_action) < 0)
+ if (reset_head(r, &ropts) < 0)
die(_("could not reset --hard"));
-
if (discard_index(r->index) < 0 ||
repo_read_index(r) < 0)
die(_("could not read index"));
return apply_save_autostash_oid(stash_oid, 1);
}
-static int run_git_checkout(struct repository *r, struct replay_opts *opts,
- const char *commit, const char *action)
-{
- struct child_process cmd = CHILD_PROCESS_INIT;
- int ret;
-
- cmd.git_cmd = 1;
-
- if (startup_info->original_cwd) {
- cmd.dir = startup_info->original_cwd;
- strvec_pushf(&cmd.env_array, "%s=%s",
- GIT_WORK_TREE_ENVIRONMENT, r->worktree);
- }
- strvec_push(&cmd.args, "checkout");
- strvec_push(&cmd.args, commit);
- strvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
-
- if (opts->verbose)
- ret = run_command(&cmd);
- else
- ret = run_command_silent_on_success(&cmd);
-
- if (!ret)
- discard_index(r->index);
-
- return ret;
-}
-
static int checkout_onto(struct repository *r, struct replay_opts *opts,
const char *onto_name, const struct object_id *onto,
const struct object_id *orig_head)
{
- const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
-
- if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
+ struct reset_head_opts ropts = {
+ .oid = onto,
+ .orig_head = orig_head,
+ .flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
+ .head_msg = reflog_message(opts, "start", "checkout %s",
+ onto_name),
+ .default_reflog_action = "rebase"
+ };
+ if (reset_head(r, &ropts)) {
apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("could not detach HEAD"));
}
- return update_ref(NULL, "ORIG_HEAD", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ return 0;
}
static int stopped_at_head(struct repository *r)
const struct commit *current_head,
const struct object_id *new_head);
-void create_autostash(struct repository *r, const char *path,
- const char *default_reflog_action);
+void create_autostash(struct repository *r, const char *path);
int save_autostash(const char *path);
int apply_autostash(const char *path);
int apply_autostash_oid(const char *stash_oid);
return config_error_nonbool(var);
format = hash_algo_by_name(value);
if (format == GIT_HASH_UNKNOWN)
- return error("invalid value for 'extensions.objectformat'");
+ return error(_("invalid value for '%s': '%s'"),
+ "extensions.objectformat", value);
data->hash_algo = format;
return EXTENSION_OK;
}
if (!commit)
return 0;
commit->object.flags |= UNINTERESTING;
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(NULL, commit);
return 0;
}
--- /dev/null
+### Remove GNU make implicit rules
+
+## This speeds things up since we don't need to look for and stat() a
+## "foo.c,v" every time a rule referring to "foo.c" is in play. See
+## "make -p -f/dev/null | grep ^%::'".
+%:: %,v
+%:: RCS/%,v
+%:: RCS/%
+%:: s.%
+%:: SCCS/s.%
+
+## Likewise delete default $(SUFFIXES). See:
+##
+## info make --index-search=.SUFFIXES
+.SUFFIXES:
+
+### Flags affecting all rules
+
+# A GNU make extension since gmake 3.72 (released in late 1994) to
+# remove the target of rules if commands in those rules fail. The
+# default is to only do that if make itself receives a signal. Affects
+# all targets, see:
+#
+# info make --index-search=.DELETE_ON_ERROR
+.DELETE_ON_ERROR:
+
+### Global variables
+
+## comma, empty, space: handy variables as these tokens are either
+## special or can be hard to spot among other Makefile syntax.
+comma := ,
+empty :=
+space := $(empty) $(empty)
+
+### Quieting
+## common
+QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1 =
+
+ifneq ($(findstring w,$(MAKEFLAGS)),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring s,$(MAKEFLAGS)),s)
+ifndef V
+## common
+ QUIET_SUBDIR0 = +@subdir=
+ QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
+ $(MAKE) $(PRINT_DIR) -C $$subdir
+
+ QUIET = @
+ QUIET_GEN = @echo ' ' GEN $@;
+
+ QUIET_MKDIR_P_PARENT = @echo $(wspfx_SQ) MKDIR -p $(@D);
+
+## Used in "Makefile"
+ QUIET_CC = @echo ' ' CC $@;
+ QUIET_AR = @echo ' ' AR $@;
+ QUIET_LINK = @echo ' ' LINK $@;
+ QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
+ QUIET_LNCP = @echo ' ' LN/CP $@;
+ QUIET_XGETTEXT = @echo ' ' XGETTEXT $@;
+ QUIET_MSGFMT = @echo ' ' MSGFMT $@;
+ QUIET_GCOV = @echo ' ' GCOV $@;
+ QUIET_SP = @echo ' ' SP $<;
+ QUIET_HDR = @echo ' ' HDR $(<:hcc=h);
+ QUIET_RC = @echo ' ' RC $@;
+ QUIET_SPATCH = @echo ' ' SPATCH $<;
+
+## Used in "Documentation/Makefile"
+ QUIET_ASCIIDOC = @echo ' ' ASCIIDOC $@;
+ QUIET_XMLTO = @echo ' ' XMLTO $@;
+ QUIET_DB2TEXI = @echo ' ' DB2TEXI $@;
+ QUIET_MAKEINFO = @echo ' ' MAKEINFO $@;
+ QUIET_DBLATEX = @echo ' ' DBLATEX $@;
+ QUIET_XSLTPROC = @echo ' ' XSLTPROC $@;
+ QUIET_GEN = @echo ' ' GEN $@;
+ QUIET_STDERR = 2> /dev/null
+
+ QUIET_LINT_GITLINK = @echo ' ' LINT GITLINK $<;
+ QUIET_LINT_MANSEC = @echo ' ' LINT MAN SEC $<;
+ QUIET_LINT_MANEND = @echo ' ' LINT MAN END $<;
+
+ export V
+endif
+endif
+
+### Templates
+
+## mkdir_p_parent: lazily "mkdir -p" the path needed for a $@
+## file. Uses $(wildcard) to avoid the "mkdir -p" if it's not
+## needed.
+##
+## Is racy, but in a good way; we might redundantly (and safely)
+## "mkdir -p" when running in parallel, but won't need to exhaustively create
+## individual rules for "a" -> "prefix" -> "dir" -> "file" if given a
+## "a/prefix/dir/file". This can instead be inserted at the start of
+## the "a/prefix/dir/file" rule.
+define mkdir_p_parent_template
+$(if $(wildcard $(@D)),,$(QUIET_MKDIR_P_PARENT)$(shell mkdir -p $(@D)))
+endef
int set_sparse_index_config(struct repository *repo, int enable)
{
- int res;
- char *config_path = repo_git_path(repo, "config.worktree");
- res = git_config_set_in_file_gently(config_path,
- "index.sparse",
- enable ? "true" : NULL);
- free(config_path);
-
+ int res = repo_config_set_worktree_gently(repo,
+ "index.sparse",
+ enable ? "true" : "false");
prepare_repo_settings(repo);
repo->settings.sparse_index = enable;
return res;
/*
* The sparse index is not (yet) integrated with a split index.
*/
- if (istate->split_index)
+ if (istate->split_index || git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
return 0;
/*
* The GIT_TEST_SPARSE_INDEX environment variable triggers the
ensure_full_index(istate);
}
+static int path_found(const char *path, const char **dirname, size_t *dir_len,
+ int *dir_found)
+{
+ struct stat st;
+ char *newdir;
+ char *tmp;
+
+ /*
+ * If dirname corresponds to a directory that doesn't exist, and this
+ * path starts with dirname, then path can't exist.
+ */
+ if (!*dir_found && !memcmp(path, *dirname, *dir_len))
+ return 0;
+
+ /*
+ * If path itself exists, return 1.
+ */
+ if (!lstat(path, &st))
+ return 1;
+
+ /*
+ * Otherwise, path does not exist so we'll return 0...but we'll first
+ * determine some info about its parent directory so we can avoid
+ * lstat calls for future cache entries.
+ */
+ newdir = strrchr(path, '/');
+ if (!newdir)
+ return 0; /* Didn't find a parent dir; just return 0 now. */
+
+ /*
+ * If path starts with directory (which we already lstat'ed and found),
+ * then no need to lstat parent directory again.
+ */
+ if (*dir_found && *dirname && memcmp(path, *dirname, *dir_len))
+ return 0;
+
+ /* Free previous dirname, and cache path's dirname */
+ *dirname = path;
+ *dir_len = newdir - path + 1;
+
+ tmp = xstrndup(path, *dir_len);
+ *dir_found = !lstat(tmp, &st);
+ free(tmp);
+
+ return 0;
+}
+
+void clear_skip_worktree_from_present_files(struct index_state *istate)
+{
+ const char *last_dirname = NULL;
+ size_t dir_len = 0;
+ int dir_found = 1;
+
+ int i;
+
+ if (!core_apply_sparse_checkout ||
+ sparse_expect_files_outside_of_patterns)
+ return;
+
+restart:
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+
+ if (ce_skip_worktree(ce) &&
+ path_found(ce->name, &last_dirname, &dir_len, &dir_found)) {
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ ensure_full_index(istate);
+ goto restart;
+ }
+ ce->ce_flags &= ~CE_SKIP_WORKTREE;
+ }
+ }
+}
+
/*
* This static global helps avoid infinite recursion between
* expand_to_path() and index_file_exists().
#define SPARSE_INDEX_MEMORY_ONLY (1 << 0)
int convert_to_sparse(struct index_state *istate, int flags);
void ensure_correct_sparsity(struct index_state *istate);
+void clear_skip_worktree_from_present_files(struct index_state *istate);
/*
* Some places in the codebase expect to search for a specific path.
struct split_index *init_split_index(struct index_state *istate)
{
if (!istate->split_index) {
+ if (istate->sparse_index)
+ die(_("cannot use split index with a sparse index"));
+
CALLOC_ARRAY(istate->split_index, 1);
istate->split_index->refcount = 1;
}
int (*cmp)(const void *, const void *))
{
const size_t size = st_mult(n, s);
- char buf[1024];
-
- if (size < sizeof(buf)) {
- /* The temporary array fits on the small on-stack buffer. */
- msort_with_tmp(b, n, s, cmp, buf);
- } else {
- /* It's somewhat large, so malloc it. */
- char *tmp = xmalloc(size);
- msort_with_tmp(b, n, s, cmp, tmp);
- free(tmp);
- }
+ char *tmp;
+
+ tmp = xmalloc(size);
+ msort_with_tmp(b, n, s, cmp, tmp);
+ free(tmp);
}
#include "refs.h"
#include "string-list.h"
#include "utf8.h"
+#include "date.h"
int starts_with(const char *str, const char *prefix)
{
#include "strbuf.h"
#include "object-store.h"
#include "parse-options.h"
+#include "tree-walk.h"
/*
* submodule cache lookup structure
else if (parse_submodule_update_strategy(value,
&submodule->update_strategy) < 0 ||
submodule->update_strategy.type == SM_UPDATE_COMMAND)
- die(_("invalid value for %s"), var);
+ die(_("invalid value for '%s'"), var);
} else if (!strcmp(item.buf, "shallow")) {
if (!me->overwrite && submodule->recommend_shallow != -1)
warn_multiple_config(me->treeish_name, submodule->name,
return config_from(r->submodule_cache, treeish_name, path, lookup_path);
}
+/**
+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'
+ * and appends submodule entries to 'out'. The submodule_cache expects
+ * a root-level treeish_name and paths, so keep track of these values
+ * with 'root_tree' and 'prefix'.
+ */
+static void traverse_tree_submodules(struct repository *r,
+ const struct object_id *root_tree,
+ char *prefix,
+ const struct object_id *treeish_name,
+ struct submodule_entry_list *out)
+{
+ struct tree_desc tree;
+ struct submodule_tree_entry *st_entry;
+ struct name_entry *name_entry;
+ char *tree_path = NULL;
+
+ name_entry = xmalloc(sizeof(*name_entry));
+
+ fill_tree_descriptor(r, &tree, treeish_name);
+ while (tree_entry(&tree, name_entry)) {
+ if (prefix)
+ tree_path =
+ mkpathdup("%s/%s", prefix, name_entry->path);
+ else
+ tree_path = xstrdup(name_entry->path);
+
+ if (S_ISGITLINK(name_entry->mode) &&
+ is_tree_submodule_active(r, root_tree, tree_path)) {
+ st_entry = xmalloc(sizeof(*st_entry));
+ st_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));
+ *st_entry->name_entry = *name_entry;
+ st_entry->submodule =
+ submodule_from_path(r, root_tree, tree_path);
+ st_entry->repo = xmalloc(sizeof(*st_entry->repo));
+ if (repo_submodule_init(st_entry->repo, r, tree_path,
+ root_tree))
+ FREE_AND_NULL(st_entry->repo);
+
+ ALLOC_GROW(out->entries, out->entry_nr + 1,
+ out->entry_alloc);
+ out->entries[out->entry_nr++] = *st_entry;
+ } else if (S_ISDIR(name_entry->mode))
+ traverse_tree_submodules(r, root_tree, tree_path,
+ &name_entry->oid, out);
+ free(tree_path);
+ }
+}
+
+void submodules_of_tree(struct repository *r,
+ const struct object_id *treeish_name,
+ struct submodule_entry_list *out)
+{
+ CALLOC_ARRAY(out->entries, 0);
+ out->entry_nr = 0;
+ out->entry_alloc = 0;
+
+ traverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);
+}
+
void submodule_free(struct repository *r)
{
if (r->submodule_cache)
#include "hashmap.h"
#include "submodule.h"
#include "strbuf.h"
+#include "tree-walk.h"
/**
* The submodule config cache API allows to read submodule
void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);
void update_clone_config_from_gitmodules(int *max_jobs);
+/*
+ * Submodule entry that contains relevant information about a
+ * submodule in a tree.
+ */
+struct submodule_tree_entry {
+ /* The submodule's tree entry. */
+ struct name_entry *name_entry;
+ /*
+ * A struct repository corresponding to the submodule. May be
+ * NULL if the submodule has not been updated.
+ */
+ struct repository *repo;
+ /*
+ * A struct submodule containing the submodule config in the
+ * tree's .gitmodules.
+ */
+ const struct submodule *submodule;
+};
+
+struct submodule_entry_list {
+ struct submodule_tree_entry *entries;
+ int entry_nr;
+ int entry_alloc;
+};
+
+/**
+ * Given a treeish, return all submodules in the tree and its subtrees,
+ * but excluding nested submodules. Callers that require nested
+ * submodules are expected to recurse into the submodules themselves.
+ */
+void submodules_of_tree(struct repository *r,
+ const struct object_id *treeish_name,
+ struct submodule_entry_list *ret);
#endif /* SUBMODULE_CONFIG_H */
* ie, the config looks like: "[submodule] active\n".
* Since that is an invalid pathspec, we should inform the user.
*/
-int is_submodule_active(struct repository *repo, const char *path)
+int is_tree_submodule_active(struct repository *repo,
+ const struct object_id *treeish_name,
+ const char *path)
{
int ret = 0;
char *key = NULL;
const struct string_list *sl;
const struct submodule *module;
- module = submodule_from_path(repo, null_oid(), path);
+ module = submodule_from_path(repo, treeish_name, path);
/* early return if there isn't a path->module mapping */
if (!module)
return ret;
}
+int is_submodule_active(struct repository *repo, const char *path)
+{
+ return is_tree_submodule_active(repo, null_oid(), path);
+}
+
int is_submodule_populated_gently(const char *path, int *return_error_code)
{
int ret = 0;
struct option;
int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
const char *arg, int unset);
+int is_tree_submodule_active(struct repository *repo,
+ const struct object_id *treeish_name,
+ const char *path);
int is_submodule_active(struct repository *repo, const char *path);
/*
* Determine if a submodule has been populated at a given 'path' by checking if
+# Import tree-wide shared Makefile behavior and libraries
+include ../shared.mak
+
# Run tests
#
# Copyright (c) 2005 Junio C Hamano
--- /dev/null
+#include "test-tool.h"
+#include "git-compat-util.h"
+
+
+int cmd__csprng(int argc, const char **argv)
+{
+ unsigned long count;
+ unsigned char buf[1024];
+
+ if (argc > 2) {
+ fprintf(stderr, "usage: %s [<size>]\n", argv[0]);
+ return 2;
+ }
+
+ count = (argc == 2) ? strtoul(argv[1], NULL, 0) : -1L;
+
+ while (count) {
+ unsigned long chunk = count < sizeof(buf) ? count : sizeof(buf);
+ if (csprng_bytes(buf, chunk) < 0) {
+ perror("failed to read");
+ return 5;
+ }
+ if (fwrite(buf, chunk, 1, stdout) != chunk)
+ return 1;
+ count -= chunk;
+ }
+
+ return 0;
+}
#include "test-tool.h"
#include "cache.h"
+#include "date.h"
static const char *usage_msg = "\n"
" test-tool date relative [time_t]...\n"
static void show_dates(const char **argv, const char *format)
{
- struct date_mode mode;
+ struct date_mode mode = DATE_MODE_INIT;
parse_date_format(format, &mode);
for (; *argv; argv++) {
printf("%s -> %s\n", *argv, show_date(t, tz, &mode));
}
+
+ date_mode_release(&mode);
}
static void parse_dates(const char **argv)
*
* Reads instructions from standard input, one instruction per line:
*
+ * "start <total>[ <title>]" - Call start_progress(title, total),
+ * Uses the default title of "Working hard"
+ * if the " <title>" is omitted.
* "progress <items>" - Call display_progress() with the given item count
* as parameter.
* "throughput <bytes> <millis> - Call display_throughput() with the given
* specify the time elapsed since the
* start_progress() call.
* "update" - Set the 'progress_update' flag.
+ * "stop" - Call stop_progress().
*
* See 't0500-progress-display.sh' for examples.
*/
#include "parse-options.h"
#include "progress.h"
#include "strbuf.h"
+#include "string-list.h"
int cmd__progress(int argc, const char **argv)
{
- int total = 0;
- const char *title;
+ const char *const default_title = "Working hard";
+ struct string_list titles = STRING_LIST_INIT_DUP;
struct strbuf line = STRBUF_INIT;
- struct progress *progress;
+ struct progress *progress = NULL;
const char *usage[] = {
- "test-tool progress [--total=<n>] <progress-title>",
+ "test-tool progress <stdin",
NULL
};
struct option options[] = {
- OPT_INTEGER(0, "total", &total, "total number of items"),
OPT_END(),
};
argc = parse_options(argc, argv, NULL, options, usage, 0);
- if (argc != 1)
- die("need a title for the progress output");
- title = argv[0];
+ if (argc)
+ usage_with_options(usage, options);
progress_testing = 1;
- progress = start_progress(title, total);
while (strbuf_getline(&line, stdin) != EOF) {
char *end;
- if (skip_prefix(line.buf, "progress ", (const char **) &end)) {
+ if (skip_prefix(line.buf, "start ", (const char **) &end)) {
+ uint64_t total = strtoull(end, &end, 10);
+ const char *title;
+
+ /*
+ * We can't use "end + 1" as an argument to
+ * start_progress(), it doesn't xstrdup() its
+ * "title" argument. We need to hold onto a
+ * valid "char *" for it until the end.
+ */
+ if (!*end)
+ title = default_title;
+ else if (*end == ' ')
+ title = string_list_insert(&titles, end + 1)->string;
+ else
+ die("invalid input: '%s'\n", line.buf);
+
+ progress = start_progress(title, total);
+ } else if (skip_prefix(line.buf, "progress ", (const char **) &end)) {
uint64_t item_count = strtoull(end, &end, 10);
if (*end != '\0')
die("invalid input: '%s'\n", line.buf);
die("invalid input: '%s'\n", line.buf);
progress_test_ns = test_ms * 1000 * 1000;
display_throughput(progress, byte_count);
- } else if (!strcmp(line.buf, "update"))
+ } else if (!strcmp(line.buf, "update")) {
progress_test_force_update();
- else
+ } else if (!strcmp(line.buf, "stop")) {
+ stop_progress(&progress);
+ } else {
die("invalid input: '%s'\n", line.buf);
+ }
}
- stop_progress(&progress);
+ strbuf_release(&line);
+ string_list_clear(&titles, 0);
return 0;
}
int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags);
int flags;
const char *ref;
- int ignore_errno;
ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- &oid, &flags, &ignore_errno);
+ &oid, &flags);
printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags);
return ref ? 0 : 1;
}
int cmd__reftable(int argc, const char **argv)
{
+ /* test from simple to complex. */
basics_test_main(argc, argv);
+ record_test_main(argc, argv);
block_test_main(argc, argv);
- merged_test_main(argc, argv);
+ tree_test_main(argc, argv);
pq_test_main(argc, argv);
- record_test_main(argc, argv);
- refname_test_main(argc, argv);
readwrite_test_main(argc, argv);
+ merged_test_main(argc, argv);
stack_test_main(argc, argv);
- tree_test_main(argc, argv);
+ refname_test_main(argc, argv);
return 0;
}
struct strbuf out = STRBUF_INIT;
struct strvec args = STRVEC_INIT;
struct option options[] = {
- OPT_INTEGER('n', "trials", &trials, "Number of trials"),
- OPT_INTEGER('s', "skip", &skip, "Skip <n> trials"),
- OPT_BOOL('m', "msys2", &msys2, "Test quoting for MSYS2's sh"),
+ OPT_INTEGER('n', "trials", &trials, "number of trials"),
+ OPT_INTEGER('s', "skip", &skip, "skip <n> trials"),
+ OPT_BOOL('m', "msys2", &msys2, "test quoting for MSYS2's sh"),
OPT_END()
};
const char * const usage[] = {
{ "chmtime", cmd__chmtime },
{ "config", cmd__config },
{ "crontab", cmd__crontab },
+ { "csprng", cmd__csprng },
{ "ctype", cmd__ctype },
{ "date", cmd__date },
{ "delta", cmd__delta },
int cmd__chmtime(int argc, const char **argv);
int cmd__config(int argc, const char **argv);
int cmd__crontab(int argc, const char **argv);
+int cmd__csprng(int argc, const char **argv);
int cmd__ctype(int argc, const char **argv);
int cmd__date(int argc, const char **argv);
int cmd__delta(int argc, const char **argv);
+# Import tree-wide shared Makefile behavior and libraries
+include ../../shared.mak
+
-include ../../config.mak
export GIT_TEST_OPTIONS
# Helpers for scripts testing bitmap functionality; see t5310 for
# example usage.
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
# Compare a file containing rev-list bitmap traversal output to its non-bitmap
# counterpart. You can't just use test_cmp for this, because the two produce
# subtly different output:
midx_checksum () {
test-tool read-midx --checksum "$1"
}
+
+# midx_pack_source <obj>
+midx_pack_source () {
+ test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
+}
+
+test_rev_exists () {
+ commit="$1"
+ kind="$2"
+
+ test_expect_success "reverse index exists ($kind)" '
+ GIT_TRACE2_EVENT=$(pwd)/event.trace \
+ git rev-list --test-bitmap "$commit" &&
+
+ if test "rev" = "$kind"
+ then
+ test_path_is_file $midx-$(midx_checksum $objdir).rev
+ fi &&
+ grep "\"category\":\"load_midx_revindex\",\"key\":\"source\",\"value\":\"$kind\"" event.trace
+ '
+}
+
+midx_bitmap_core () {
+ rev_kind="${1:-midx}"
+
+ setup_bitmap_history
+
+ test_expect_success 'create single-pack midx with bitmaps' '
+ git repack -ad &&
+ git multi-pack-index write --bitmap &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+ '
+
+ test_rev_exists HEAD "$rev_kind"
+
+ basic_bitmap_tests
+
+ test_expect_success 'create new additional packs' '
+ for i in $(test_seq 1 16)
+ do
+ test_commit "$i" &&
+ git repack -d || return 1
+ done &&
+
+ git checkout -b other2 HEAD~8 &&
+ for i in $(test_seq 1 8)
+ do
+ test_commit "side-$i" &&
+ git repack -d || return 1
+ done &&
+ git checkout second
+ '
+
+ test_expect_success 'create multi-pack midx with bitmaps' '
+ git multi-pack-index write --bitmap &&
+
+ ls $objdir/pack/pack-*.pack >packs &&
+ test_line_count = 25 packs &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+ '
+
+ test_rev_exists HEAD "$rev_kind"
+
+ basic_bitmap_tests
+
+ test_expect_success '--no-bitmap is respected when bitmaps exist' '
+ git multi-pack-index write --bitmap &&
+
+ test_commit respect--no-bitmap &&
+ git repack -d &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+ git multi-pack-index write --no-bitmap &&
+
+ test_path_is_file $midx &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).rev
+ '
+
+ test_expect_success 'setup midx with base from later pack' '
+ # Write a and b so that "a" is a delta on top of base "b", since Git
+ # prefers to delete contents out of a base rather than add to a shorter
+ # object.
+ test_seq 1 128 >a &&
+ test_seq 1 130 >b &&
+
+ git add a b &&
+ git commit -m "initial commit" &&
+
+ a=$(git rev-parse HEAD:a) &&
+ b=$(git rev-parse HEAD:b) &&
+
+ # In the first pack, "a" is stored as a delta to "b".
+ p1=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $a
+ $b
+ EOF
+ ) &&
+
+ # In the second pack, "a" is missing, and "b" is not a delta nor base to
+ # any other object.
+ p2=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $b
+ $(git rev-parse HEAD)
+ $(git rev-parse HEAD^{tree})
+ EOF
+ ) &&
+
+ git prune-packed &&
+ # Use the second pack as the preferred source, so that "b" occurs
+ # earlier in the MIDX object order, rendering "a" unusable for pack
+ # reuse.
+ git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
+
+ have_delta $a $b &&
+ test $(midx_pack_source $a) != $(midx_pack_source $b)
+ '
+
+ rev_list_tests 'full bitmap with backwards delta'
+
+ test_expect_success 'clone with bitmaps enabled' '
+ git clone --no-local --bare . clone-reverse-delta.git &&
+ test_when_finished "rm -fr clone-reverse-delta.git" &&
+
+ git rev-parse HEAD >expect &&
+ git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'changing the preferred pack does not corrupt bitmaps' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit A &&
+ test_commit B &&
+
+ git rev-list --objects --no-object-names HEAD^ >A.objects &&
+ git rev-list --objects --no-object-names HEAD^.. >B.objects &&
+
+ A=$(git pack-objects $objdir/pack/pack <A.objects) &&
+ B=$(git pack-objects $objdir/pack/pack <B.objects) &&
+
+ cat >indexes <<-EOF &&
+ pack-$A.idx
+ pack-$B.idx
+ EOF
+
+ git multi-pack-index write --bitmap --stdin-packs \
+ --preferred-pack=pack-$A.pack <indexes &&
+ git rev-list --test-bitmap A &&
+
+ git multi-pack-index write --bitmap --stdin-packs \
+ --preferred-pack=pack-$B.pack <indexes &&
+ git rev-list --test-bitmap A
+ )
+ '
+}
+
+midx_bitmap_partial_tests () {
+ rev_kind="${1:-midx}"
+
+ test_expect_success 'setup partial bitmaps' '
+ test_commit packed &&
+ git repack &&
+ test_commit loose &&
+ git multi-pack-index write --bitmap 2>err &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+ '
+
+ test_rev_exists HEAD~ "$rev_kind"
+
+ basic_bitmap_tests HEAD~
+}
# > lib-gpg/ownertrust
mkdir "$GNUPGHOME" &&
chmod 0700 "$GNUPGHOME" &&
- (gpgconf --kill gpg-agent || : ) &&
+ (gpgconf --kill all || : ) &&
gpg --homedir "${GNUPGHOME}" --import \
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
gpg --homedir "${GNUPGHOME}" --import-ownertrust \
--passphrase-fd 0 --pinentry-mode loopback \
--import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
- gpgsm --homedir "${GNUPGHOME}" -K |
- grep fingerprint: |
- cut -d" " -f4 |
- tr -d "\\n" >"${GNUPGHOME}/trustlist.txt" &&
+ gpgsm --homedir "${GNUPGHOME}" -K --with-colons |
+ awk -F ":" "/^fpr:/ {printf \"%s S relax\\n\", \$10}" \
+ >"${GNUPGHOME}/trustlist.txt" &&
+ (gpgconf --reload all || : ) &&
- echo " S relax" >>"${GNUPGHOME}/trustlist.txt" &&
echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
-u committer@example.com -o /dev/null --sign -
'
mkdir Z
for a in N D M
do
- for b in N D M
- do
- p=$a$b
+ for b in N D M
+ do
+ p=$a$b
echo This is $p from the original tree. >$p
echo This is Z/$p from the original tree. >Z/$p
- test_expect_success \
- "adding test file $p and Z/$p" \
- 'git update-index --add $p &&
- git update-index --add Z/$p'
+ test_expect_success "adding test file $p and Z/$p" '
+ git update-index --add $p &&
+ git update-index --add Z/$p
+ '
done
done
echo This is SS from the original tree. >SS
-test_expect_success \
- 'adding test file SS' \
- 'git update-index --add SS'
+test_expect_success 'adding test file SS' '
+ git update-index --add SS
+'
cat >TT <<\EOF
This is a trivial merge sample text.
Branch A is expected to upcase this word, here.
the word, expected to be upcased by Branch B.
This concludes the trivial merge sample file.
EOF
-test_expect_success \
- 'adding test file TT' \
- 'git update-index --add TT'
-test_expect_success \
- 'prepare initial tree' \
- 'tree_O=$(git write-tree)'
+test_expect_success 'adding test file TT' '
+ git update-index --add TT
+'
+test_expect_success 'prepare initial tree' '
+ tree_O=$(git write-tree)
+'
################################################################
# Branch A and B makes the changes according to the above matrix.
to_remove=$(echo D? Z/D?)
rm -f $to_remove
-test_expect_success \
- 'change in branch A (removal)' \
- 'git update-index --remove $to_remove'
+test_expect_success 'change in branch A (removal)' '
+ git update-index --remove $to_remove
+'
for p in M? Z/M?
do
- echo This is modified $p in the branch A. >$p
- test_expect_success \
- 'change in branch A (modification)' \
- "git update-index $p"
+ echo This is modified $p in the branch A. >$p
+ test_expect_success 'change in branch A (modification)' '
+ git update-index $p
+ '
done
for p in AN AA Z/AN Z/AA
do
- echo This is added $p in the branch A. >$p
- test_expect_success \
- 'change in branch A (addition)' \
- "git update-index --add $p"
+ echo This is added $p in the branch A. >$p
+ test_expect_success 'change in branch A (addition)' '
+ git update-index --add $p
+ '
done
echo This is SS from the modified tree. >SS
echo This is LL from the modified tree. >LL
-test_expect_success \
- 'change in branch A (addition)' \
- 'git update-index --add LL &&
- git update-index SS'
+test_expect_success 'change in branch A (addition)' '
+ git update-index --add LL &&
+ git update-index SS
+'
mv TT TT-
sed -e '/Branch A/s/word/WORD/g' <TT- >TT
rm -f TT-
-test_expect_success \
- 'change in branch A (edit)' \
- 'git update-index TT'
+test_expect_success 'change in branch A (edit)' '
+ git update-index TT
+'
mkdir DF
echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
-test_expect_success \
- 'change in branch A (change file to directory)' \
- 'git update-index --add DF/DF'
+test_expect_success 'change in branch A (change file to directory)' '
+ git update-index --add DF/DF
+'
-test_expect_success \
- 'recording branch A tree' \
- 'tree_A=$(git write-tree)'
+test_expect_success 'recording branch A tree' '
+ tree_A=$(git write-tree)
+'
################################################################
# Branch B
rm -rf [NDMASLT][NDMASLT] Z DF
mkdir Z
-test_expect_success \
- 'reading original tree and checking out' \
- 'git read-tree $tree_O &&
- git checkout-index -a'
+test_expect_success 'reading original tree and checking out' '
+ git read-tree $tree_O &&
+ git checkout-index -a
+'
to_remove=$(echo ?D Z/?D)
rm -f $to_remove
-test_expect_success \
- 'change in branch B (removal)' \
- "git update-index --remove $to_remove"
+test_expect_success 'change in branch B (removal)' '
+ git update-index --remove $to_remove
+'
for p in ?M Z/?M
do
- echo This is modified $p in the branch B. >$p
- test_expect_success \
- 'change in branch B (modification)' \
- "git update-index $p"
+ echo This is modified $p in the branch B. >$p
+ test_expect_success 'change in branch B (modification)' '
+ git update-index $p
+ '
done
for p in NA AA Z/NA Z/AA
do
- echo This is added $p in the branch B. >$p
- test_expect_success \
- 'change in branch B (addition)' \
- "git update-index --add $p"
+ echo This is added $p in the branch B. >$p
+ test_expect_success 'change in branch B (addition)' '
+ git update-index --add $p
+ '
done
echo This is SS from the modified tree. >SS
echo This is LL from the modified tree. >LL
-test_expect_success \
- 'change in branch B (addition and modification)' \
- 'git update-index --add LL &&
- git update-index SS'
+test_expect_success 'change in branch B (addition and modification)' '
+ git update-index --add LL &&
+ git update-index SS
+'
mv TT TT-
sed -e '/Branch B/s/word/WORD/g' <TT- >TT
rm -f TT-
-test_expect_success \
- 'change in branch B (modification)' \
- 'git update-index TT'
+test_expect_success 'change in branch B (modification)' '
+ git update-index TT
+'
echo Branch B makes a file at DF. >DF
-test_expect_success \
- 'change in branch B (addition of a file to conflict with directory)' \
- 'git update-index --add DF'
-
-test_expect_success \
- 'recording branch B tree' \
- 'tree_B=$(git write-tree)'
-
-test_expect_success \
- 'keep contents of 3 trees for easy access' \
- 'rm -f .git/index &&
- git read-tree $tree_O &&
- mkdir .orig-O &&
- git checkout-index --prefix=.orig-O/ -f -q -a &&
- rm -f .git/index &&
- git read-tree $tree_A &&
- mkdir .orig-A &&
- git checkout-index --prefix=.orig-A/ -f -q -a &&
- rm -f .git/index &&
- git read-tree $tree_B &&
- mkdir .orig-B &&
- git checkout-index --prefix=.orig-B/ -f -q -a'
+test_expect_success 'change in branch B (addition of a file to conflict with directory)' '
+ git update-index --add DF
+'
+
+test_expect_success 'recording branch B tree' '
+ tree_B=$(git write-tree)
+'
+
+test_expect_success 'keep contents of 3 trees for easy access' '
+ rm -f .git/index &&
+ git read-tree $tree_O &&
+ mkdir .orig-O &&
+ git checkout-index --prefix=.orig-O/ -f -q -a &&
+ rm -f .git/index &&
+ git read-tree $tree_A &&
+ mkdir .orig-A &&
+ git checkout-index --prefix=.orig-A/ -f -q -a &&
+ rm -f .git/index &&
+ git read-tree $tree_B &&
+ mkdir .orig-B &&
+ git checkout-index --prefix=.orig-B/ -f -q -a
+'
+# Import tree-wide shared Makefile behavior and libraries
+include ../../shared.mak
+
-include ../../config.mak
export GIT_TEST_OPTIONS
test_perf_on_all git diff --cached
test_perf_on_all git blame $SPARSE_CONE/a
test_perf_on_all git blame $SPARSE_CONE/f3/a
+test_perf_on_all git checkout-index -f --all
+test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
test_done
. ./test-lib.sh
check_config () {
- if test -d "$1" && test -f "$1/config" && test -d "$1/refs"
+ if test_path_is_dir "$1" &&
+ test_path_is_file "$1/config" && test_path_is_dir "$1/refs"
then
: happy
else
#!/bin/sh
test_description='test date parsing and printing'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# arbitrary reference time: 2009-08-30 19:20:00
'
test_expect_success 'invalid usage' '
+ test_expect_code 129 git help -a add &&
+ test_expect_code 129 git help --all add &&
+
test_expect_code 129 git help -g add &&
test_expect_code 129 git help -a -c &&
test_expect_code 129 git help --config-sections-for-completion add
'
+for opt in '-a' '-g' '-c' '--config-for-completion' '--config-sections-for-completion'
+do
+ test_expect_success "invalid usage of '$opt' with [-i|-m|-w]" '
+ git help $opt &&
+ test_expect_code 129 git help $opt -i &&
+ test_expect_code 129 git help $opt -m &&
+ test_expect_code 129 git help $opt -w
+ '
+
+ if test "$opt" = "-a"
+ then
+ continue
+ fi
+
+ test_expect_success "invalid usage of '$opt' with --no-external-commands" '
+ test_expect_code 129 git help $opt --no-external-commands
+ '
+
+ test_expect_success "invalid usage of '$opt' with --no-aliases" '
+ test_expect_code 129 git help $opt --no-external-commands
+ '
+done
+
test_expect_success "works for commands and guides by default" '
configure_help &&
git help status &&
test_cmp human.munged sections
'
+test_section_spacing () {
+ cat >expect &&
+ "$@" >out &&
+ grep -E "(^[^ ]|^$)" out >actual
+}
+
+test_section_spacing_trailer () {
+ test_section_spacing "$@" &&
+ test_expect_code 1 git >out &&
+ sed -n '/list available subcommands/,$p' <out >>expect
+}
+
+
+for cmd in git "git help"
+do
+ test_expect_success "'$cmd' section spacing" '
+ test_section_spacing_trailer git help <<-\EOF &&
+ usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
+
+ These are common Git commands used in various situations:
+
+ start a working area (see also: git help tutorial)
+
+ work on the current change (see also: git help everyday)
+
+ examine the history and state (see also: git help revisions)
+
+ grow, mark and tweak your common history
+
+ collaborate (see also: git help workflows)
+
+ EOF
+ test_cmp expect actual
+ '
+done
+
+test_expect_success "'git help -a' section spacing" '
+ test_section_spacing \
+ git help -a --no-external-commands --no-aliases <<-\EOF &&
+ See '\''git help <command>'\'' to read about a specific subcommand
+
+ Main Porcelain Commands
+
+ Ancillary Commands / Manipulators
+
+ Ancillary Commands / Interrogators
+
+ Interacting with Others
+
+ Low-level Commands / Manipulators
+
+ Low-level Commands / Interrogators
+
+ Low-level Commands / Syncing Repositories
+
+ Low-level Commands / Internal Helpers
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "'git help -g' section spacing" '
+ test_section_spacing_trailer git help -g <<-\EOF &&
+ The Git concept guides are:
+
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success 'generate builtin list' '
+ mkdir -p sub &&
git --list-cmds=builtins >builtins
'
while read builtin
do
test_expect_success "$builtin can handle -h" '
- test_expect_code 129 git $builtin -h >output 2>&1 &&
+ (
+ GIT_CEILING_DIRECTORIES=$(pwd) &&
+ export GIT_CEILING_DIRECTORIES &&
+ test_expect_code 129 git -C sub $builtin -h >output 2>&1
+ ) &&
test_i18ngrep usage output
'
done <builtins
grep c12252ceda8be8994d5fa0290a47231c1d16aae3 actual &&
printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha1 >actual &&
grep 32d10c7b8cf96570ca04ce37f2a19d84240d3a89 actual &&
- perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \
+ perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" |
test-tool sha1 >actual &&
grep 34aa973cd4c4daa4f61eeb2bdbad27316534016f actual &&
printf "blob 0\0" | test-tool sha1 >actual &&
printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha256 >actual &&
grep 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 actual &&
# Try to exercise the chunking code by turning autoflush on.
- perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \
+ perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" |
test-tool sha256 >actual &&
grep cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 actual &&
- perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | \
+ perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" |
test-tool sha256 >actual &&
grep e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35 actual &&
printf "blob 0\0" | test-tool sha256 >actual &&
# auto: core.autocrlf=false and core.eol unset(or native) uses native eol
checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ # core.autocrlf false, .gitattributes sets eol
+ checkout_files "" "$id" "lf" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ checkout_files "" "$id" "crlf" false "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+ # core.autocrlf true, .gitattributes sets eol
+ checkout_files "" "$id" "lf" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ checkout_files "" "$id" "crlf" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
done
# The rest of the tests are unique; do the usual linting.
test_description='Windows named pipes'
. ./test-lib.sh
+if ! test_have_prereq MINGW
+then
+ skip_all='skipping Windows-specific tests'
+ test_done
+fi
-test_expect_success MINGW 'o_append write to named pipe' '
+test_expect_success 'o_append write to named pipe' '
GIT_TRACE="$(pwd)/expect" git status >/dev/null 2>&1 &&
{ test-tool windows-named-pipe t0051 >actual 2>&1 & } &&
pid=$! &&
test_description='progress display'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
show_cr () {
EOF
cat >in <<-\EOF &&
+ start 0
update
progress 1
update
progress 4
update
progress 5
+ stop
EOF
- test-tool progress "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 3
progress 1
progress 2
progress 3
+ stop
EOF
- test-tool progress --total=3 "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 100000 Working hard.......2.........3.........4.........5.........6
progress 100
progress 1000
progress 10000
progress 100000
+ stop
EOF
- test-tool progress --total=100000 \
- "Working hard.......2.........3.........4.........5.........6" \
- <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 100000 Working hard.......2.........3.........4.........5.........6
update
progress 1
update
progress 2
progress 10000
progress 100000
+ stop
EOF
- test-tool progress --total=100000 \
- "Working hard.......2.........3.........4.........5.........6" \
- <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 100000 Working hard.......2.........3.........4.........5.........6
progress 25000
progress 50000
progress 75000
progress 100000
+ stop
EOF
- test-tool progress --total=100000 \
- "Working hard.......2.........3.........4.........5.........6" \
- <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 100000 Working hard.......2.........3.........4.........5.........6.........7.........
progress 25000
progress 50000
progress 75000
progress 100000
+ stop
EOF
- test-tool progress --total=100000 \
- "Working hard.......2.........3.........4.........5.........6.........7........." \
- <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 1000
progress 100
progress 200
progress 1
progress 1000
+ stop
EOF
- test-tool progress --total=1000 "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 0
throughput 102400 1000
update
progress 10
throughput 409600 4000
update
progress 40
+ stop
EOF
- test-tool progress "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 40
throughput 102400 1000
progress 10
throughput 204800 2000
progress 30
throughput 409600 4000
progress 40
+ stop
EOF
- test-tool progress --total=40 "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 0
throughput 409600 1000
update
progress 1
throughput 1638400 4000
update
progress 4
+ stop
EOF
- test-tool progress "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
EOF
cat >in <<-\EOF &&
+ start 0
throughput 1 1000
update
progress 1
throughput 3145728 3000
update
progress 3
+ stop
EOF
- test-tool progress "Working hard" <in 2>stderr &&
+ test-tool progress <in 2>stderr &&
show_cr <stderr >out &&
test_cmp expect out
test_expect_success 'progress generates traces' '
cat >in <<-\EOF &&
+ start 40
throughput 102400 1000
update
progress 10
throughput 409600 4000
update
progress 40
+ stop
EOF
- GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool progress --total=40 \
- "Working hard" <in 2>stderr &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool progress \
+ <in 2>stderr &&
# t0212/parse_events.perl intentionally omits regions and data.
test_region progress "Working hard" trace.event &&
grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event
'
+test_expect_success 'progress generates traces: stop / start' '
+ cat >in <<-\EOF &&
+ start 0
+ stop
+ EOF
+
+ GIT_TRACE2_EVENT="$PWD/trace-startstop.event" test-tool progress \
+ <in 2>stderr &&
+ test_region progress "Working hard" trace-startstop.event
+'
+
+test_expect_success 'progress generates traces: start without stop' '
+ cat >in <<-\EOF &&
+ start 0
+ EOF
+
+ GIT_TRACE2_EVENT="$PWD/trace-start.event" \
+ LSAN_OPTIONS=detect_leaks=0 \
+ test-tool progress \
+ <in 2>stderr &&
+ grep region_enter.*progress trace-start.event &&
+ ! grep region_leave.*progress trace-start.event
+'
+
+test_expect_success 'progress generates traces: stop without start' '
+ cat >in <<-\EOF &&
+ stop
+ EOF
+
+ GIT_TRACE2_EVENT="$PWD/trace-stop.event" test-tool progress \
+ <in 2>stderr &&
+ ! grep region_enter.*progress trace-stop.event &&
+ ! grep region_leave.*progress trace-stop.event
+'
+
+test_expect_success 'progress generates traces: start with active progress bar (no stops)' '
+ cat >in <<-\EOF &&
+ start 0 One
+ start 0 Two
+ EOF
+
+ GIT_TRACE2_EVENT="$PWD/trace-2start.event" \
+ LSAN_OPTIONS=detect_leaks=0 \
+ test-tool progress \
+ <in 2>stderr &&
+ grep region_enter.*progress.*One trace-2start.event &&
+ grep region_enter.*progress.*Two trace-2start.event &&
+ ! grep region_leave trace-2start.event
+'
+
test_done
. ./test-lib.sh
+test_cmdmode_usage () {
+ test_expect_code 129 "$@" 2>err &&
+ grep "^error:.*is incompatible with" err
+}
+
+for switches in \
+ '-e -p' \
+ '-p -t' \
+ '-t -s' \
+ '-s --textconv' \
+ '--textconv --filters' \
+ '--batch-all-objects -e'
+do
+ test_expect_success "usage: cmdmode $switches" '
+ test_cmdmode_usage git cat-file $switches
+ '
+done
+
+test_incompatible_usage () {
+ test_expect_code 129 "$@" 2>err &&
+ grep -E "^(fatal|error):.*(requires|incompatible with|needs)" err
+}
+
+for opt in --batch --batch-check
+do
+ test_expect_success "usage: incompatible options: --path with $opt" '
+ test_incompatible_usage git cat-file --path=foo $opt
+ '
+done
+
+test_missing_usage () {
+ test_expect_code 129 "$@" 2>err &&
+ grep -E "^fatal:.*required" err
+}
+
+short_modes="-e -p -t -s"
+cw_modes="--textconv --filters"
+
+for opt in $cw_modes
+do
+ test_expect_success "usage: $opt requires another option" '
+ test_missing_usage git cat-file $opt
+ '
+done
+
+for opt in $short_modes
+do
+ test_expect_success "usage: $opt requires another option" '
+ test_missing_usage git cat-file $opt
+ '
+
+ for opt2 in --batch \
+ --batch-check \
+ --follow-symlinks \
+ "--path=foo HEAD:some-path.txt"
+ do
+ test_expect_success "usage: incompatible options: $opt and $opt2" '
+ test_incompatible_usage git cat-file $opt $opt2
+ '
+ done
+done
+
+test_too_many_arguments () {
+ test_expect_code 129 "$@" 2>err &&
+ grep -E "^fatal: too many arguments$" err
+}
+
+for opt in $short_modes $cw_modes
+do
+ args="one two three"
+ test_expect_success "usage: too many arguments: $opt $args" '
+ test_too_many_arguments git cat-file $opt $args
+ '
+
+ for opt2 in --buffer --follow-symlinks
+ do
+ test_expect_success "usage: incompatible arguments: $opt with batch option $opt2" '
+ test_incompatible_usage git cat-file $opt $opt2
+ '
+ done
+done
+
+for opt in --buffer \
+ --follow-symlinks \
+ --batch-all-objects
+do
+ test_expect_success "usage: bad option combination: $opt without batch mode" '
+ test_incompatible_usage git cat-file $opt &&
+ test_incompatible_usage git cat-file $opt commit HEAD
+ '
+done
+
echo_without_newline () {
printf '%s' "$*"
}
}
maybe_remove_timestamp () {
- if test -z "$2"; then
- echo_without_newline "$1"
- else
- echo_without_newline "$(printf '%s\n' "$1" | sed -e 's/ [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$//')"
- fi
+ if test -z "$2"; then
+ echo_without_newline "$1"
+ else
+ echo_without_newline "$(printf '%s\n' "$1" | remove_timestamp)"
+ fi
+}
+
+remove_timestamp () {
+ sed -e 's/ [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$//'
}
+
run_tests () {
type=$1
sha1=$2
test_cmp expect actual
'
+ for opt in --buffer --no-buffer
+ do
+ test -z "$content" ||
+ test_expect_success "--batch-command $opt output of $type content is correct" '
+ maybe_remove_timestamp "$batch_output" $no_ts >expect &&
+ maybe_remove_timestamp "$(test_write_lines "contents $sha1" |
+ git cat-file --batch-command $opt)" $no_ts >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "--batch-command $opt output of $type info is correct" '
+ echo "$sha1 $type $size" >expect &&
+ test_write_lines "info $sha1" |
+ git cat-file --batch-command $opt >actual &&
+ test_cmp expect actual
+ '
+ done
+
test_expect_success "custom --batch-check format" '
echo "$type $sha1" >expect &&
echo $sha1 | git cat-file --batch-check="%(objecttype) %(objectname)" >actual &&
test_cmp expect actual
'
+ test_expect_success "custom --batch-command format" '
+ echo "$type $sha1" >expect &&
+ echo "info $sha1" | git cat-file --batch-command="%(objecttype) %(objectname)" >actual &&
+ test_cmp expect actual
+ '
+
test_expect_success '--batch-check with %(rest)' '
echo "$type this is some extra content" >expect &&
echo "$sha1 this is some extra content" |
run_tests 'blob' $hello_sha1 $hello_size "$hello_content" "$hello_content"
+test_expect_success '--batch-command --buffer with flush for blob info' '
+ echo "$hello_sha1 blob $hello_size" >expect &&
+ test_write_lines "info $hello_sha1" "flush" |
+ GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT=1 \
+ git cat-file --batch-command --buffer >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--batch-command --buffer without flush for blob info' '
+ touch output &&
+ test_write_lines "info $hello_sha1" |
+ GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT=1 \
+ git cat-file --batch-command --buffer >>output &&
+ test_must_be_empty output
+'
+
test_expect_success '--batch-check without %(rest) considers whole line' '
echo "$hello_sha1 blob $hello_size" >expect &&
git update-index --add --cacheinfo 100644 $hello_sha1 "white space" &&
"Reach a blob from a tag pointing to it" \
"test '$hello_content' = \"\$(git cat-file blob $tag_sha1)\""
-for batch in batch batch-check
+for batch in batch batch-check batch-command
do
for opt in t s e p
do
"$(echo_without_newline "$batch_check_input" | git cat-file --batch-check)"
'
+test_expect_success '--batch-command with multiple info calls gives correct format' '
+ cat >expect <<-EOF &&
+ $hello_sha1 blob $hello_size
+ $tree_sha1 tree $tree_size
+ $commit_sha1 commit $commit_size
+ $tag_sha1 tag $tag_size
+ deadbeef missing
+ EOF
+
+ git cat-file --batch-command --buffer >actual <<-EOF &&
+ info $hello_sha1
+ info $tree_sha1
+ info $commit_sha1
+ info $tag_sha1
+ info deadbeef
+ EOF
+
+ test_cmp expect actual
+'
+
+test_expect_success '--batch-command with multiple command calls gives correct format' '
+ remove_timestamp >expect <<-EOF &&
+ $hello_sha1 blob $hello_size
+ $hello_content
+ $commit_sha1 commit $commit_size
+ $commit_content
+ $tag_sha1 tag $tag_size
+ $tag_content
+ deadbeef missing
+ EOF
+
+ git cat-file --batch-command --buffer >actual_raw <<-EOF &&
+ contents $hello_sha1
+ contents $commit_sha1
+ contents $tag_sha1
+ contents deadbeef
+ flush
+ EOF
+
+ remove_timestamp <actual_raw >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'setup blobs which are likely to delta' '
test-tool genrandom foo 10240 >foo &&
{ cat foo && echo plus; } >foo-plus &&
echo "$orig commit $orig_size" >expect &&
test_cmp expect actual
'
+test_expect_success 'batch-command empty command' '
+ echo "" >cmd &&
+ test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
+ grep "^fatal:.*empty command in input.*" err
+'
+
+test_expect_success 'batch-command whitespace before command' '
+ echo " info deadbeef" >cmd &&
+ test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
+ grep "^fatal:.*whitespace before command.*" err
+'
+
+test_expect_success 'batch-command unknown command' '
+ echo unknown_command >cmd &&
+ test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
+ grep "^fatal:.*unknown command.*" err
+'
+
+test_expect_success 'batch-command missing arguments' '
+ echo "info" >cmd &&
+ test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
+ grep "^fatal:.*info requires arguments.*" err
+'
+
+test_expect_success 'batch-command flush with arguments' '
+ echo "flush arg" >cmd &&
+ test_expect_code 128 git cat-file --batch-command --buffer <cmd 2>err &&
+ grep "^fatal:.*flush takes no arguments.*" err
+'
+
+test_expect_success 'batch-command flush without --buffer' '
+ echo "flush" >cmd &&
+ test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
+ grep "^fatal:.*flush is only for --buffer mode.*" err
+'
test_done
test_description="git hash-object"
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
echo_without_newline() {
test ! -f init.t
'
+test_expect_success 'read-tree will not throw away dirty changes, non-sparse' '
+ echo "/*" >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed -m -u HEAD &&
+
+ echo dirty >init.t &&
+ read_tree_u_must_fail -m -u HEAD^ &&
+ test_path_is_file init.t &&
+ grep -q dirty init.t
+'
+
+test_expect_success 'read-tree will not throw away dirty changes, sparse' '
+ echo "/*" >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed -m -u HEAD &&
+
+ echo dirty >init.t &&
+ echo sub/added >.git/info/sparse-checkout &&
+ read_tree_u_must_fail -m -u HEAD^ &&
+ test_path_is_file init.t &&
+ grep -q dirty init.t
+'
+
test_expect_success 'read-tree updates worktree, dirty case' '
echo sub/added >.git/info/sparse-checkout &&
git checkout -f top &&
echo dirty >init.t &&
- read_tree_u_must_succeed -m -u HEAD^ &&
+ read_tree_u_must_fail -m -u HEAD^ &&
grep -q dirty init.t &&
rm init.t
'
test "$(cat b)" = "modified"
'
+test_expect_success 'skip-worktree on files outside sparse patterns' '
+ git sparse-checkout disable &&
+ git sparse-checkout set --no-cone "a*" &&
+ git checkout-index --all --ignore-skip-worktree-bits &&
+
+ git ls-files -t >output &&
+ ! grep ^S output >actual &&
+ test_must_be_empty actual &&
+
+ test_config sparse.expectFilesOutsideOfPatterns true &&
+ cat <<-\EOF >expect &&
+ S b
+ S c
+ EOF
+ git ls-files -t >output &&
+ grep ^S output >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'in partial clone, sparse checkout only fetches needed blobs' '
test_create_repo server &&
git clone "file://$(pwd)/server" client &&
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+GIT_TEST_SPLIT_INDEX=false
+export GIT_TEST_SPLIT_INDEX
+
. ./test-lib.sh
list_files() {
check_files repo a
'
+test_expect_success 'git sparse-checkout init in empty repo' '
+ test_when_finished rm -rf empty-repo blank-template &&
+ git init --template= empty-repo &&
+ git -C empty-repo sparse-checkout init
+'
+
test_expect_success 'git sparse-checkout list after init' '
git -C repo sparse-checkout list >actual &&
cat >expect <<-\EOF &&
cd bad-patterns &&
git sparse-checkout init &&
git sparse-checkout add dir &&
- git config core.sparseCheckoutCone true &&
+ git config --worktree core.sparseCheckoutCone true &&
test_must_fail git sparse-checkout add dir 2>err &&
grep "existing sparse-checkout patterns do not use cone mode" err
)
'
test_expect_success 'set enables config' '
- git init empty-config &&
+ git init worktree-config &&
(
- cd empty-config &&
+ cd worktree-config &&
test_commit test file &&
test_path_is_missing .git/config.worktree &&
git sparse-checkout set nothing &&
check_files repo "a folder1 folder2"
'
+test_expect_success 'worktree: add copies sparse-checkout patterns' '
+ cat repo/.git/info/sparse-checkout >old &&
+ test_when_finished cp old repo/.git/info/sparse-checkout &&
+ test_when_finished git -C repo worktree remove ../worktree &&
+ git -C repo sparse-checkout set --no-cone "/*" &&
+ git -C repo worktree add --quiet ../worktree 2>err &&
+ test_must_be_empty err &&
+ new="$(git -C worktree rev-parse --git-path info/sparse-checkout)" &&
+ test_path_is_file "$new" &&
+ test_cmp repo/.git/info/sparse-checkout "$new" &&
+ git -C worktree sparse-checkout set --cone &&
+ test_cmp_config -C worktree true core.sparseCheckoutCone &&
+ test_must_fail git -C repo core.sparseCheckoutCone
+'
+
test_expect_success 'cone mode: match patterns' '
git -C repo config --worktree core.sparseCheckoutCone true &&
rm -rf repo/a repo/folder1 repo/folder2 &&
'
test_expect_success 'sparse-index enabled and disabled' '
- (
- sane_unset GIT_TEST_SPLIT_INDEX &&
- git -C repo update-index --no-split-index &&
-
- git -C repo sparse-checkout init --cone --sparse-index &&
- test_cmp_config -C repo true index.sparse &&
- git -C repo ls-files --sparse >sparse &&
- git -C repo sparse-checkout disable &&
- git -C repo ls-files --sparse >full &&
-
- cat >expect <<-\EOF &&
- @@ -1,4 +1,7 @@
- a
- -deep/
- -folder1/
- -folder2/
- +deep/a
- +deep/deeper1/a
- +deep/deeper1/deepest/a
- +deep/deeper2/a
- +folder1/a
- +folder2/a
- EOF
+ git -C repo sparse-checkout init --cone --sparse-index &&
+ test_cmp_config -C repo true index.sparse &&
+ git -C repo ls-files --sparse >sparse &&
+ git -C repo sparse-checkout disable &&
+ git -C repo ls-files --sparse >full &&
- diff -u sparse full | tail -n +3 >actual &&
- test_cmp expect actual &&
+ cat >expect <<-\EOF &&
+ @@ -1,4 +1,7 @@
+ a
+ -deep/
+ -folder1/
+ -folder2/
+ +deep/a
+ +deep/deeper1/a
+ +deep/deeper1/deepest/a
+ +deep/deeper2/a
+ +folder1/a
+ +folder2/a
+ EOF
+
+ diff -u sparse full | tail -n +3 >actual &&
+ test_cmp expect actual &&
- git -C repo config --list >config &&
- ! grep index.sparse config
- )
+ git -C repo config --list >config &&
+ test_cmp_config -C repo false index.sparse
'
test_expect_success 'cone mode: init and set' '
git -C tweak sparse-checkout disable
'
+test_expect_success 'reapply can handle config options' '
+ git -C repo sparse-checkout init --cone --no-sparse-index &&
+ git -C repo config --worktree --list >actual &&
+ cat >expect <<-\EOF &&
+ core.sparsecheckout=true
+ core.sparsecheckoutcone=true
+ index.sparse=false
+ EOF
+ test_cmp expect actual &&
+
+ git -C repo sparse-checkout reapply --no-cone --no-sparse-index &&
+ git -C repo config --worktree --list >actual &&
+ cat >expect <<-\EOF &&
+ core.sparsecheckout=true
+ core.sparsecheckoutcone=false
+ index.sparse=false
+ EOF
+ test_cmp expect actual &&
+
+ git -C repo sparse-checkout reapply --cone --sparse-index &&
+ git -C repo config --worktree --list >actual &&
+ cat >expect <<-\EOF &&
+ core.sparsecheckout=true
+ core.sparsecheckoutcone=true
+ index.sparse=true
+ EOF
+ test_cmp expect actual &&
+
+ git -C repo sparse-checkout disable
+'
+
test_expect_success 'cone mode: set with core.ignoreCase=true' '
rm repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout init --cone &&
'
test_expect_success 'different sparse-checkouts with worktrees' '
+ git -C repo sparse-checkout set --cone deep folder1 &&
git -C repo worktree add --detach ../worktree &&
- check_files worktree "a deep folder1 folder2" &&
- git -C worktree sparse-checkout init --cone &&
- git -C repo sparse-checkout set folder1 &&
- git -C worktree sparse-checkout set deep/deeper1 &&
- check_files repo a folder1 &&
- check_files worktree a deep
+ check_files worktree "a deep folder1" &&
+ git -C repo sparse-checkout set --cone folder1 &&
+ git -C worktree sparse-checkout set --cone deep/deeper1 &&
+ check_files repo "a folder1" &&
+ check_files worktree "a deep"
'
test_expect_success 'set using filename keeps file on-disk' '
- git -C repo sparse-checkout set a deep &&
+ git -C repo sparse-checkout set --skip-checks a deep &&
cat >expect <<-\EOF &&
/*
!/*/
git -C escaped reset --hard $COMMIT &&
check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
git -C escaped sparse-checkout init --cone &&
- git -C escaped sparse-checkout set zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
+ git -C escaped sparse-checkout set --skip-checks zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
cat >expect <<-\EOF &&
/*
!/*/
grep "warning: disabling cone pattern matching" err
'
+test_expect_success 'set from subdir pays attention to prefix' '
+ git -C repo sparse-checkout disable &&
+ git -C repo/deep sparse-checkout set --cone deeper2 ../folder1 &&
+
+ git -C repo sparse-checkout list >actual &&
+
+ cat >expect <<-\EOF &&
+ deep/deeper2
+ folder1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'add from subdir pays attention to prefix' '
+ git -C repo sparse-checkout set --cone deep/deeper2 &&
+ git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 &&
+
+ git -C repo sparse-checkout list >actual &&
+
+ cat >expect <<-\EOF &&
+ deep/deeper1/deepest
+ deep/deeper2
+ folder1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'set from subdir in non-cone mode throws an error' '
+ git -C repo sparse-checkout disable &&
+ test_must_fail git -C repo/deep sparse-checkout set --no-cone deeper2 ../folder1 2>error &&
+
+ grep "run from the toplevel directory in non-cone mode" error
+'
+
+test_expect_success 'set from subdir in non-cone mode throws an error' '
+ git -C repo sparse-checkout set --no-cone deep/deeper2 &&
+ test_must_fail git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 2>error &&
+
+ grep "run from the toplevel directory in non-cone mode" error
+'
+
+test_expect_success 'by default, cone mode will error out when passed files' '
+ git -C repo sparse-checkout reapply --cone &&
+ test_must_fail git -C repo sparse-checkout add .gitignore 2>error &&
+
+ grep ".gitignore.*is not a directory" error
+'
+
+test_expect_success 'by default, non-cone mode will warn on individual files' '
+ git -C repo sparse-checkout reapply --no-cone &&
+ git -C repo sparse-checkout add .gitignore 2>warning &&
+
+ grep "pass a leading slash before paths.*if you want a single file" warning
+'
+
test_done
write_script edit-contents <<-\EOF &&
echo text >>$1
EOF
- run_on_sparse ../edit-contents folder1/a &&
+ run_on_all ../edit-contents folder1/a &&
run_on_all ../edit-contents folder1/new &&
test_sparse_match git status --porcelain=v2 &&
test_sparse_match test_must_fail git add folder1/a &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
- test_sparse_match test_must_fail git add --refresh folder1/a &&
- grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_all_match git add --refresh folder1/a &&
+ test_must_be_empty sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
test_sparse_match test_must_fail git add folder1/new &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_match git reset update-folder1 -- folder1 &&
git -C full-checkout reset update-folder1 -- folder1 &&
- test_sparse_match git status --porcelain=v2 &&
- test_all_match git rev-parse HEAD:folder1 &&
+ test_all_match git ls-files -s -- folder1 &&
test_sparse_match git reset update-folder2 -- folder2/a &&
git -C full-checkout reset update-folder2 -- folder2/a &&
- test_sparse_match git status --porcelain=v2 &&
- test_all_match git rev-parse HEAD:folder2/a
+ test_all_match git ls-files -s -- folder2/a
'
test_expect_success 'reset with wildcard pathspec' '
test_all_match git ls-files -s -- folder1
'
+test_expect_success 'update-index modify outside sparse definition' '
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
+ echo text >>$1
+ EOF
+
+ # Create & modify folder1/a
+ # Note that this setup is a manual way of reaching the erroneous
+ # condition in which a `skip-worktree` enabled, outside-of-cone file
+ # exists on disk. It is used here to ensure `update-index` is stable
+ # and behaves predictably if such a condition occurs.
+ run_on_sparse mkdir -p folder1 &&
+ run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
+ run_on_all ../edit-contents folder1/a &&
+
+ # If file has skip-worktree enabled, but the file is present, it is
+ # treated the same as if skip-worktree is disabled
+ test_all_match git status --porcelain=v2 &&
+ test_all_match git update-index folder1/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # When skip-worktree is disabled (even on files outside sparse cone), file
+ # is updated in the index
+ test_sparse_match git update-index --no-skip-worktree folder1/a &&
+ test_all_match git status --porcelain=v2 &&
+ test_all_match git update-index folder1/a &&
+ test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'update-index --add outside sparse definition' '
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
+ echo text >>$1
+ EOF
+
+ # Create folder1, add new file
+ run_on_sparse mkdir -p folder1 &&
+ run_on_all ../edit-contents folder1/b &&
+
+ # The *untracked* out-of-cone file is added to the index because it does
+ # not have a `skip-worktree` bit to signal that it should be ignored
+ # (unlike in `git add`, which will fail due to the file being outside
+ # the sparse checkout definition).
+ test_all_match git update-index --add folder1/b &&
+ test_all_match git status --porcelain=v2
+'
+
+# NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore
+# `skip-worktree` entries by default and will remove them from the index.
+# The `--ignore-skip-worktree-entries` flag must be used in conjunction with
+# `--remove` to ignore the `skip-worktree` entries and prevent their removal
+# from the index.
+test_expect_success 'update-index --remove outside sparse definition' '
+ init_repos &&
+
+ # When --ignore-skip-worktree-entries is _not_ specified:
+ # out-of-cone, not-on-disk files are removed from the index
+ test_sparse_match git update-index --remove folder1/a &&
+ cat >expect <<-EOF &&
+ D folder1/a
+ EOF
+ test_sparse_match git diff --cached --name-status &&
+ test_cmp expect sparse-checkout-out &&
+
+ # Reset the state
+ test_all_match git reset --hard &&
+
+ # When --ignore-skip-worktree-entries is specified, out-of-cone
+ # (skip-worktree) files are ignored
+ test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
+ test_sparse_match git diff --cached --name-status &&
+ test_must_be_empty sparse-checkout-out &&
+
+ # Reset the state
+ test_all_match git reset --hard &&
+
+ # --force-remove supercedes --ignore-skip-worktree-entries, removing
+ # a skip-worktree file from the index (and disk) when both are specified
+ # with --remove
+ test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
+ cat >expect <<-EOF &&
+ D folder1/a
+ EOF
+ test_sparse_match git diff --cached --name-status &&
+ test_cmp expect sparse-checkout-out
+'
+
+test_expect_success 'update-index with directories' '
+ init_repos &&
+
+ # update-index will exit silently when provided with a directory name
+ # containing a trailing slash
+ test_all_match git update-index deep/ folder1/ &&
+ grep "Ignoring path deep/" sparse-checkout-err &&
+ grep "Ignoring path folder1/" sparse-checkout-err &&
+
+ # When update-index is given a directory name WITHOUT a trailing slash, it will
+ # behave in different ways depending on the status of the directory on disk:
+ # * if it exists, the command exits with an error ("add individual files instead")
+ # * if it does NOT exist (e.g., in a sparse-checkout), it is assumed to be a
+ # file and either triggers an error ("does not exist and --remove not passed")
+ # or is ignored completely (when using --remove)
+ test_all_match test_must_fail git update-index deep &&
+ run_on_all test_must_fail git update-index folder1 &&
+ test_must_fail git -C full-checkout update-index --remove folder1 &&
+ test_sparse_match git update-index --remove folder1 &&
+ test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'update-index --again file outside sparse definition' '
+ init_repos &&
+
+ test_all_match git checkout -b test-reupdate &&
+
+ # Update HEAD without modifying the index to introduce a difference in
+ # folder1/a
+ test_sparse_match git reset --soft update-folder1 &&
+
+ # Because folder1/a differs in the index vs HEAD,
+ # `git update-index --no-skip-worktree --again` will effectively perform
+ # `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree
+ # flag from folder1/a
+ test_sparse_match git update-index --no-skip-worktree --again &&
+ test_sparse_match git status --porcelain=v2 &&
+
+ cat >expect <<-EOF &&
+ D folder1/a
+ EOF
+ test_sparse_match git diff --name-status &&
+ test_cmp expect sparse-checkout-out
+'
+
+test_expect_success 'update-index --cacheinfo' '
+ init_repos &&
+
+ deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
+ folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
+ folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
+
+ test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Cannot add sparse directory, even in sparse index case
+ test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
+
+ # Sparse match only: the new outside-of-cone entry is added *without* skip-worktree,
+ # so `git status` reports it as "deleted" in the worktree
+ test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
+ test_sparse_match git status --porcelain=v2 &&
+ cat >expect <<-EOF &&
+ MD folder1/a
+ EOF
+ test_sparse_match git status --short -- folder1/a &&
+ test_cmp expect sparse-checkout-out &&
+
+ # To return folder1/a to "normal" for a sparse checkout (ignored &
+ # outside-of-cone), add the skip-worktree flag.
+ test_sparse_match git update-index --skip-worktree folder1/a &&
+ cat >expect <<-EOF &&
+ S folder1/a
+ EOF
+ test_sparse_match git ls-files -t -- folder1/a &&
+ test_cmp expect sparse-checkout-out
+'
+
test_expect_success 'merge, cherry-pick, and rebase' '
init_repos &&
test_all_match test_must_fail git cherry-pick to-cherry-pick
'
+test_expect_success 'checkout-index inside sparse definition' '
+ init_repos &&
+
+ run_on_all rm -f deep/a &&
+ test_all_match git checkout-index -- deep/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ echo test >>new-a &&
+ run_on_all cp ../new-a a &&
+ test_all_match test_must_fail git checkout-index -- a &&
+ test_all_match git checkout-index -f -- a &&
+ test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'checkout-index outside sparse definition' '
+ init_repos &&
+
+ # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
+ # an error
+ test_sparse_match test_must_fail git checkout-index -- folder1/a &&
+ test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
+ test_path_is_missing folder1/a &&
+
+ # With --ignore-skip-worktree-bits, outside-of-cone files are checked out
+ test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
+ test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
+ test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
+
+ run_on_sparse rm -rf folder1 &&
+ echo test >new-a &&
+ run_on_sparse mkdir -p folder1 &&
+ run_on_all cp ../new-a folder1/a &&
+
+ test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
+ test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
+ test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
+ test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
+'
+
+test_expect_success 'checkout-index with folders' '
+ init_repos &&
+
+ # Inside checkout definition
+ test_all_match test_must_fail git checkout-index -f -- deep/ &&
+
+ # Outside checkout definition
+ # Note: although all tests fail (as expected), the messaging differs. For
+ # non-sparse index checkouts, the error is that the "file" does not appear
+ # in the index; for sparse checkouts, the error is explicitly that the
+ # entry is a sparse directory.
+ run_on_all test_must_fail git checkout-index -f -- folder1/ &&
+ test_cmp full-checkout-err sparse-checkout-err &&
+ ! test_cmp full-checkout-err sparse-index-err &&
+ grep "is a sparse directory" sparse-index-err
+'
+
+test_expect_success 'checkout-index --all' '
+ init_repos &&
+
+ test_all_match git checkout-index --all &&
+ test_sparse_match test_path_is_missing folder1 &&
+
+ # --ignore-skip-worktree-bits will cause `skip-worktree` files to be
+ # checked out, causing the outside-of-cone `folder1` to exist on-disk
+ test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
+ test_all_match test_path_exists folder1
+'
+
test_expect_success 'clean' '
init_repos &&
test_all_match git commit -m "ignore bogus files" &&
run_on_sparse mkdir folder1 &&
+ run_on_all mkdir -p deep/untracked-deep &&
run_on_all touch folder1/bogus &&
+ run_on_all touch folder1/untracked &&
+ run_on_all touch deep/untracked-deep/bogus &&
+ run_on_all touch deep/untracked-deep/untracked &&
test_all_match git status --porcelain=v2 &&
test_all_match git clean -f &&
test_all_match git status --porcelain=v2 &&
test_sparse_match ls &&
test_sparse_match ls folder1 &&
+ run_on_all test_path_exists folder1/bogus &&
+ run_on_all test_path_is_missing folder1/untracked &&
+ run_on_all test_path_exists deep/untracked-deep/bogus &&
+ run_on_all test_path_exists deep/untracked-deep/untracked &&
+
+ test_all_match git clean -fd &&
+ test_all_match git status --porcelain=v2 &&
+ test_sparse_match ls &&
+ test_sparse_match ls folder1 &&
+ run_on_all test_path_exists folder1/bogus &&
+ run_on_all test_path_exists deep/untracked-deep/bogus &&
+ run_on_all test_path_is_missing deep/untracked-deep/untracked &&
test_all_match git clean -xf &&
test_all_match git status --porcelain=v2 &&
test_sparse_match ls &&
test_sparse_match ls folder1 &&
+ run_on_all test_path_is_missing folder1/bogus &&
+ run_on_all test_path_exists deep/untracked-deep/bogus &&
test_all_match git clean -xdf &&
test_all_match git status --porcelain=v2 &&
test_sparse_match ls &&
test_sparse_match ls folder1 &&
+ run_on_all test_path_is_missing deep/untracked-deep/bogus &&
test_sparse_match test_path_is_dir folder1
'
echo >>sparse-index/untracked.txt &&
ensure_not_expanded add . &&
+ ensure_not_expanded checkout-index -f a &&
+ ensure_not_expanded checkout-index -f --all &&
for ref in update-deep update-folder1 update-folder2 update-deep
do
echo >>sparse-index/README.md &&
# Wildcard identifies only full sparse directories, no index expansion
ensure_not_expanded reset deepest -- folder\* &&
+ ensure_not_expanded clean -fd &&
+
ensure_not_expanded checkout -f update-deep &&
test_config -C sparse-index pull.twohead ort &&
(
ensure_not_expanded diff --cached
'
+test_expect_success 'sparse index is not expanded: update-index' '
+ init_repos &&
+
+ deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
+ ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
+
+ echo "test" >sparse-index/README.md &&
+ echo "test2" >sparse-index/a &&
+ rm -f sparse-index/deep/a &&
+
+ ensure_not_expanded update-index --add README.md &&
+ ensure_not_expanded update-index a &&
+ ensure_not_expanded update-index --remove deep/a &&
+
+ ensure_not_expanded reset --soft update-deep &&
+ ensure_not_expanded update-index --add --remove --again
+'
+
test_expect_success 'sparse index is not expanded: blame' '
init_repos &&
test_cmp dense sparse &&
# Set up a strange condition of having a file edit
- # outside of the sparse-checkout cone. This is just
- # to verify that sparse-checkout and sparse-index
- # behave the same in this case.
+ # outside of the sparse-checkout cone. We want to verify
+ # that all modes handle this the same, and detect the
+ # modification.
write_script edit-content <<-\EOF &&
- mkdir folder1 &&
+ mkdir -p folder1 &&
echo content >>folder1/a
EOF
- run_on_sparse ../edit-content &&
+ run_on_all ../edit-content &&
- # ls-files does not currently notice modified files whose
- # cache entries are marked SKIP_WORKTREE. This may change
- # in the future, but here we test that sparse index does
- # not accidentally create a change of behavior.
- test_sparse_match git ls-files --modified &&
- test_must_be_empty sparse-checkout-out &&
- test_must_be_empty sparse-index-out &&
+ test_all_match git ls-files --modified &&
git -C sparse-index ls-files --sparse --modified >sparse-index-out &&
- test_must_be_empty sparse-index-out &&
+ cat >expect <<-\EOF &&
+ folder1/a
+ EOF
+ test_cmp expect sparse-index-out &&
# Add folder1 to the sparse-checkout cone and
# check that ls-files shows the expanded files.
test_sparse_match git sparse-checkout add folder1 &&
- test_sparse_match git ls-files --modified &&
+ test_all_match git ls-files --modified &&
test_all_match git ls-files &&
git -C sparse-index ls-files --sparse >actual &&
test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent
'
+test_expect_success 'includeIf.hasconfig:remote.*.url' '
+ git init hasremoteurlTest &&
+ test_when_finished "rm -rf hasremoteurlTest" &&
+
+ cat >include-this <<-\EOF &&
+ [user]
+ this = this-is-included
+ EOF
+ cat >dont-include-that <<-\EOF &&
+ [user]
+ that = that-is-not-included
+ EOF
+ cat >>hasremoteurlTest/.git/config <<-EOF &&
+ [includeIf "hasconfig:remote.*.url:foourl"]
+ path = "$(pwd)/include-this"
+ [includeIf "hasconfig:remote.*.url:barurl"]
+ path = "$(pwd)/dont-include-that"
+ [remote "foo"]
+ url = foourl
+ EOF
+
+ echo this-is-included >expect-this &&
+ git -C hasremoteurlTest config --get user.this >actual-this &&
+ test_cmp expect-this actual-this &&
+
+ test_must_fail git -C hasremoteurlTest config --get user.that
+'
+
+test_expect_success 'includeIf.hasconfig:remote.*.url respects last-config-wins' '
+ git init hasremoteurlTest &&
+ test_when_finished "rm -rf hasremoteurlTest" &&
+
+ cat >include-two-three <<-\EOF &&
+ [user]
+ two = included-config
+ three = included-config
+ EOF
+ cat >>hasremoteurlTest/.git/config <<-EOF &&
+ [remote "foo"]
+ url = foourl
+ [user]
+ one = main-config
+ two = main-config
+ [includeIf "hasconfig:remote.*.url:foourl"]
+ path = "$(pwd)/include-two-three"
+ [user]
+ three = main-config
+ EOF
+
+ echo main-config >expect-main-config &&
+ echo included-config >expect-included-config &&
+
+ git -C hasremoteurlTest config --get user.one >actual &&
+ test_cmp expect-main-config actual &&
+
+ git -C hasremoteurlTest config --get user.two >actual &&
+ test_cmp expect-included-config actual &&
+
+ git -C hasremoteurlTest config --get user.three >actual &&
+ test_cmp expect-main-config actual
+'
+
+test_expect_success 'includeIf.hasconfig:remote.*.url globs' '
+ git init hasremoteurlTest &&
+ test_when_finished "rm -rf hasremoteurlTest" &&
+
+ printf "[user]\ndss = yes\n" >double-star-start &&
+ printf "[user]\ndse = yes\n" >double-star-end &&
+ printf "[user]\ndsm = yes\n" >double-star-middle &&
+ printf "[user]\nssm = yes\n" >single-star-middle &&
+ printf "[user]\nno = no\n" >no &&
+
+ cat >>hasremoteurlTest/.git/config <<-EOF &&
+ [remote "foo"]
+ url = https://foo/bar/baz
+ [includeIf "hasconfig:remote.*.url:**/baz"]
+ path = "$(pwd)/double-star-start"
+ [includeIf "hasconfig:remote.*.url:**/nomatch"]
+ path = "$(pwd)/no"
+ [includeIf "hasconfig:remote.*.url:https:/**"]
+ path = "$(pwd)/double-star-end"
+ [includeIf "hasconfig:remote.*.url:nomatch:/**"]
+ path = "$(pwd)/no"
+ [includeIf "hasconfig:remote.*.url:https:/**/baz"]
+ path = "$(pwd)/double-star-middle"
+ [includeIf "hasconfig:remote.*.url:https:/**/nomatch"]
+ path = "$(pwd)/no"
+ [includeIf "hasconfig:remote.*.url:https://*/bar/baz"]
+ path = "$(pwd)/single-star-middle"
+ [includeIf "hasconfig:remote.*.url:https://*/baz"]
+ path = "$(pwd)/no"
+ EOF
+
+ git -C hasremoteurlTest config --get user.dss &&
+ git -C hasremoteurlTest config --get user.dse &&
+ git -C hasremoteurlTest config --get user.dsm &&
+ git -C hasremoteurlTest config --get user.ssm &&
+ test_must_fail git -C hasremoteurlTest config --get user.no
+'
+
+test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' '
+ git init hasremoteurlTest &&
+ test_when_finished "rm -rf hasremoteurlTest" &&
+
+ cat >include-with-url <<-\EOF &&
+ [remote "bar"]
+ url = barurl
+ EOF
+ cat >>hasremoteurlTest/.git/config <<-EOF &&
+ [includeIf "hasconfig:remote.*.url:foourl"]
+ path = "$(pwd)/include-with-url"
+ EOF
+
+ # test with any Git command
+ test_must_fail git -C hasremoteurlTest status 2>err &&
+ grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
+'
+
test_done
test_must_fail git rev-parse refs/tags/new-tag --
'
+# In reftable, we keep the reflogs around for deleted refs.
+test_expect_success !REFFILES 'delete-reflog(FOO, refs/tags/new-tag)' '
+ $RUN delete-reflog FOO &&
+ $RUN delete-reflog refs/tags/new-tag
+'
+
test_expect_success 'rename_refs(main, new-main)' '
git rev-parse main >expected &&
$RUN rename-ref refs/heads/main refs/heads/new-main &&
test_must_fail git reflog exists HEAD
'
-test_expect_success 'create-reflog(HEAD)' '
+test_expect_success REFFILES 'create-reflog(HEAD)' '
$RUN create-reflog HEAD &&
git reflog exists HEAD
'
# Each line is 114 characters, so we need 75 to still have a few before the
# last 8K. The 89-character padding on the final entry lines up our
# newline exactly.
-test_expect_success SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
+test_expect_success REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
git checkout -b reflogskip &&
zf=$(test_oid zero_2) &&
ident="abc <xyz> 0000000001 +0000" &&
test_commit -C link-wt foobar &&
test_tick &&
git reflog expire --verbose --all --expire=$test_tick &&
- test_must_be_empty .git/worktrees/link-wt/logs/HEAD
+ test-tool ref-store worktree:link-wt for-each-reflog-ent HEAD >actual &&
+ test_must_be_empty actual
)
'
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook does not get called on packing refs' '
+ # Pack references first such that we are in a known state.
+ git pack-refs --all &&
+
+ write_script .git/hooks/reference-transaction <<-\EOF &&
+ echo "$@" >>actual
+ cat >>actual
+ EOF
+ rm -f actual &&
+
+ git update-ref refs/heads/unpacked-ref $POST_OID &&
+ git pack-refs --all &&
+
+ # We only expect a single hook invocation, which is the call to
+ # git-update-ref(1).
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID $POST_OID refs/heads/unpacked-ref
+ committed
+ $ZERO_OID $POST_OID refs/heads/unpacked-ref
+ EOF
+
+ test_cmp expect actual
+'
+
+test_expect_success 'deleting packed ref calls hook once' '
+ # Create a reference and pack it.
+ git update-ref refs/heads/to-be-deleted $POST_OID &&
+ git pack-refs --all &&
+
+ write_script .git/hooks/reference-transaction <<-\EOF &&
+ echo "$@" >>actual
+ cat >>actual
+ EOF
+ rm -f actual &&
+
+ git update-ref -d refs/heads/to-be-deleted $POST_OID &&
+
+ # We only expect a single hook invocation, which is the logical
+ # deletion.
+ cat >expect <<-EOF &&
+ prepared
+ $POST_OID $ZERO_OID refs/heads/to-be-deleted
+ committed
+ $POST_OID $ZERO_OID refs/heads/to-be-deleted
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
. ./test-lib.sh
+test_cmp_failed_rev_parse () {
+ dir=$1
+ rev=$2
+
+ cat >expect &&
+ test_must_fail git -C "$dir" rev-parse "$rev" 2>actual.raw &&
+ sed "s/\($rev\)[0-9a-f]*/\1.../" <actual.raw >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'ambiguous blob output' '
+ git init --bare blob.prefix &&
+ (
+ cd blob.prefix &&
+
+ # Both start with "dead..", under both SHA-1 and SHA-256
+ echo brocdnra | git hash-object -w --stdin &&
+ echo brigddsv | git hash-object -w --stdin &&
+
+ # Both start with "beef.."
+ echo 1agllotbh | git hash-object -w --stdin &&
+ echo 1bbfctrkc | git hash-object -w --stdin
+ ) &&
+
+ test_must_fail git -C blob.prefix rev-parse dead &&
+ test_cmp_failed_rev_parse blob.prefix beef <<-\EOF
+ error: short object ID beef... is ambiguous
+ hint: The candidates are:
+ hint: beef... blob
+ hint: beef... blob
+ fatal: ambiguous argument '\''beef...'\'': unknown revision or path not in the working tree.
+ Use '\''--'\'' to separate paths from revisions, like this:
+ '\''git <command> [<revision>...] -- [<file>...]'\''
+ EOF
+'
+
+test_expect_success 'ambiguous loose bad object parsed as OBJ_BAD' '
+ git init --bare blob.bad &&
+ (
+ cd blob.bad &&
+
+ # Both have the prefix "bad0"
+ echo xyzfaowcoh | git hash-object -t bad -w --stdin --literally &&
+ echo xyzhjpyvwl | git hash-object -t bad -w --stdin --literally
+ ) &&
+
+ test_cmp_failed_rev_parse blob.bad bad0 <<-\EOF
+ error: short object ID bad0... is ambiguous
+ fatal: invalid object type
+ EOF
+'
+
+test_expect_success POSIXPERM 'ambigous zlib corrupt loose blob' '
+ git init --bare blob.corrupt &&
+ (
+ cd blob.corrupt &&
+
+ # Both have the prefix "cafe"
+ echo bnkxmdwz | git hash-object -w --stdin &&
+ oid=$(echo bmwsjxzi | git hash-object -w --stdin) &&
+
+ oidf=objects/$(test_oid_to_path "$oid") &&
+ chmod 755 $oidf &&
+ echo broken >$oidf
+ ) &&
+
+ test_cmp_failed_rev_parse blob.corrupt cafe <<-\EOF
+ error: short object ID cafe... is ambiguous
+ error: inflate: data stream error (incorrect header check)
+ error: unable to unpack cafe... header
+ error: inflate: data stream error (incorrect header check)
+ error: unable to unpack cafe... header
+ hint: The candidates are:
+ hint: cafe... [bad object]
+ hint: cafe... blob
+ fatal: ambiguous argument '\''cafe...'\'': unknown revision or path not in the working tree.
+ Use '\''--'\'' to separate paths from revisions, like this:
+ '\''git <command> [<revision>...] -- [<file>...]'\''
+ EOF
+'
+
if ! test_have_prereq SHA1
then
skip_all='not using SHA-1 for objects'
--- /dev/null
+#!/bin/sh
+
+test_description='git-hook command'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'git hook usage' '
+ test_expect_code 129 git hook &&
+ test_expect_code 129 git hook run &&
+ test_expect_code 129 git hook run -h &&
+ test_expect_code 129 git hook run --unknown 2>err &&
+ grep "unknown option" err
+'
+
+test_expect_success 'git hook run: nonexistent hook' '
+ cat >stderr.expect <<-\EOF &&
+ error: cannot find a hook named test-hook
+ EOF
+ test_expect_code 1 git hook run test-hook 2>stderr.actual &&
+ test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
+ git hook run --ignore-missing does-not-exist 2>stderr.actual &&
+ test_must_be_empty stderr.actual
+'
+
+test_expect_success 'git hook run: basic' '
+ write_script .git/hooks/test-hook <<-EOF &&
+ echo Test hook
+ EOF
+
+ cat >expect <<-\EOF &&
+ Test hook
+ EOF
+ git hook run test-hook 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
+ write_script .git/hooks/test-hook <<-EOF &&
+ echo >&1 Will end up on stderr
+ echo >&2 Will end up on stderr
+ EOF
+
+ cat >stderr.expect <<-\EOF &&
+ Will end up on stderr
+ Will end up on stderr
+ EOF
+ git hook run test-hook >stdout.actual 2>stderr.actual &&
+ test_cmp stderr.expect stderr.actual &&
+ test_must_be_empty stdout.actual
+'
+
+test_expect_success 'git hook run: exit codes are passed along' '
+ write_script .git/hooks/test-hook <<-EOF &&
+ exit 1
+ EOF
+
+ test_expect_code 1 git hook run test-hook &&
+
+ write_script .git/hooks/test-hook <<-EOF &&
+ exit 2
+ EOF
+
+ test_expect_code 2 git hook run test-hook &&
+
+ write_script .git/hooks/test-hook <<-EOF &&
+ exit 128
+ EOF
+
+ test_expect_code 128 git hook run test-hook &&
+
+ write_script .git/hooks/test-hook <<-EOF &&
+ exit 129
+ EOF
+
+ test_expect_code 129 git hook run test-hook
+'
+
+test_expect_success 'git hook run arg u ments without -- is not allowed' '
+ test_expect_code 129 git hook run test-hook arg u ments
+'
+
+test_expect_success 'git hook run -- pass arguments' '
+ write_script .git/hooks/test-hook <<-\EOF &&
+ echo $1
+ echo $2
+ EOF
+
+ cat >expect <<-EOF &&
+ arg
+ u ments
+ EOF
+
+ git hook run test-hook -- arg "u ments" 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git hook run -- out-of-repo runs excluded' '
+ write_script .git/hooks/test-hook <<-EOF &&
+ echo Test hook
+ EOF
+
+ nongit test_must_fail git hook run test-hook
+'
+
+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
+ mkdir my-hooks &&
+ write_script my-hooks/test-hook <<-\EOF &&
+ echo Hook ran $1 >>actual
+ EOF
+
+ cat >expect <<-\EOF &&
+ Test hook
+ Hook ran one
+ Hook ran two
+ Hook ran three
+ Hook ran four
+ EOF
+
+ # Test various ways of specifying the path. See also
+ # t1350-config-hooks-path.sh
+ >actual &&
+ git hook run test-hook -- ignored 2>>actual &&
+ git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
+ git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
+ git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
+ git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
+ test_cmp expect actual
+'
+
+test_done
test_must_fail git symbolic-ref HEAD
'
+test_expect_success 'suggestion to detach' '
+ test_must_fail git switch main^{commit} 2>stderr &&
+ grep "try again with the --detach option" stderr
+'
+
+test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
+ test_config advice.suggestDetachingHead false &&
+ test_must_fail git switch main^{commit} 2>stderr &&
+ ! grep "try again with the --detach option" stderr
+'
+
test_expect_success 'switch and detach current branch' '
test_when_finished git switch main &&
git switch main &&
--- /dev/null
+#!/bin/sh
+
+test_description='update-index refresh tests related to racy timestamps'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+reset_files () {
+ echo content >file &&
+ echo content >other &&
+ test_set_magic_mtime file &&
+ test_set_magic_mtime other
+}
+
+update_assert_changed () {
+ test_set_magic_mtime .git/index &&
+ test_might_fail git update-index "$1" &&
+ ! test_is_magic_mtime .git/index
+}
+
+test_expect_success 'setup' '
+ reset_files &&
+ # we are calling reset_files() a couple of times during tests;
+ # test-tool chmtime does not change the ctime; to not weaken
+ # or even break our tests, disable ctime-checks entirely
+ git config core.trustctime false &&
+ git add file other &&
+ git commit -m "initial import"
+'
+
+test_expect_success '--refresh has no racy timestamps to fix' '
+ reset_files &&
+ # set the index time far enough to the future;
+ # it must be at least 3 seconds for VFAT
+ test_set_magic_mtime .git/index +60 &&
+ git update-index --refresh &&
+ test_is_magic_mtime .git/index +60
+'
+
+test_expect_success '--refresh should fix racy timestamp' '
+ reset_files &&
+ update_assert_changed --refresh
+'
+
+test_expect_success '--really-refresh should fix racy timestamp' '
+ reset_files &&
+ update_assert_changed --really-refresh
+'
+
+test_expect_success '--refresh should fix racy timestamp if other file needs update' '
+ reset_files &&
+ echo content2 >other &&
+ test_set_magic_mtime other &&
+ update_assert_changed --refresh
+'
+
+test_expect_success '--refresh should fix racy timestamp if racy file needs update' '
+ reset_files &&
+ echo content2 >file &&
+ test_set_magic_mtime file &&
+ update_assert_changed --refresh
+'
+
+test_done
(
git clone --bare . bare2 &&
cd bare2 &&
- git worktree add ../there3 main
- )
+ git worktree add ../there3 main &&
+ cd ../there3 &&
+ # Simple check that a Git command does not
+ # immediately fail with the current setup
+ git status
+ ) &&
+ cat >expect <<-EOF &&
+ init.t
+ EOF
+ ls there3 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '"add" to bare repo with worktree config' '
+ (
+ git clone --bare . bare3 &&
+ cd bare3 &&
+ git config extensions.worktreeconfig true &&
+
+ # Add config values that are erroneous to have in
+ # a config.worktree file outside of the main
+ # working tree, to check that Git filters them out
+ # when copying config during "git worktree add".
+ git config --worktree core.bare true &&
+ git config --worktree core.worktree "$(pwd)" &&
+
+ # We want to check that bogus.key is copied
+ git config --worktree bogus.key value &&
+ git config --unset core.bare &&
+ git worktree add ../there4 main &&
+ cd ../there4 &&
+
+ # Simple check that a Git command does not
+ # immediately fail with the current setup
+ git status &&
+ git worktree add --detach ../there5 &&
+ cd ../there5 &&
+ git status
+ ) &&
+
+ # the worktree has the arbitrary value copied.
+ test_cmp_config -C there4 value bogus.key &&
+ test_cmp_config -C there5 value bogus.key &&
+
+ # however, core.bare and core.worktree were removed.
+ test_must_fail git -C there4 config core.bare &&
+ test_must_fail git -C there4 config core.worktree &&
+
+ cat >expect <<-EOF &&
+ init.t
+ EOF
+
+ ls there4 >actual &&
+ test_cmp expect actual &&
+ ls there5 >actual &&
+ test_cmp expect actual
'
test_expect_success 'checkout with grafts' '
test_cmp expect actual
'
+test_expect_success '--stage' '
+ GITMODULES_HASH=$(git rev-parse HEAD:.gitmodules) &&
+ A_HASH=$(git rev-parse HEAD:a) &&
+ B_HASH=$(git rev-parse HEAD:b/b) &&
+ C_HASH=$(git -C submodule rev-parse HEAD:c) &&
+
+ cat >expect <<-EOF &&
+ 100644 $GITMODULES_HASH 0 .gitmodules
+ 100644 $A_HASH 0 a
+ 100644 $B_HASH 0 b/b
+ 100644 $C_HASH 0 submodule/c
+ EOF
+
+ git ls-files --stage --recurse-submodules >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'ls-files correctly outputs files in submodule with -z' '
lf_to_nul >expect <<-\EOF &&
.gitmodules
test_incompatible_with_recurse_submodules --deleted
test_incompatible_with_recurse_submodules --modified
test_incompatible_with_recurse_submodules --others
-test_incompatible_with_recurse_submodules --stage
test_incompatible_with_recurse_submodules --killed
test_incompatible_with_recurse_submodules --unmerged
git branch abc && test_path_is_file .git/refs/heads/abc
'
+test_expect_success 'git branch abc should fail when abc exists' '
+ test_must_fail git branch abc
+'
+
+test_expect_success 'git branch --force abc should fail when abc is checked out' '
+ test_when_finished git switch main &&
+ git switch abc &&
+ test_must_fail git branch --force abc HEAD~1
+'
+
+test_expect_success 'git branch --force abc should succeed when abc exists' '
+ git rev-parse HEAD~1 >expect &&
+ git branch --force abc HEAD~1 &&
+ git rev-parse abc >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'git branch a/b/c should create a branch' '
git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
'
--- /dev/null
+#!/bin/sh
+
+test_description='git branch submodule tests'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+pwd=$(pwd)
+
+# Creates a clean test environment in "pwd" by copying the repo setup
+# from test_dirs.
+reset_test () {
+ rm -fr super &&
+ rm -fr sub-sub-upstream &&
+ rm -fr sub-upstream &&
+ cp -r test_dirs/* .
+}
+
+# Tests that the expected branch does not exist
+test_no_branch () {
+ DIR=$1 &&
+ BRANCH_NAME=$2 &&
+ test_must_fail git -C "$DIR" rev-parse "$BRANCH_NAME" 2>err &&
+ grep "ambiguous argument .$BRANCH_NAME." err
+}
+
+test_expect_success 'setup superproject and submodule' '
+ mkdir test_dirs &&
+ (
+ cd test_dirs &&
+ git init super &&
+ test_commit -C super foo &&
+ git init sub-sub-upstream &&
+ test_commit -C sub-sub-upstream foo &&
+ git init sub-upstream &&
+ # Submodule in a submodule
+ git -C sub-upstream submodule add "${pwd}/test_dirs/sub-sub-upstream" sub-sub &&
+ git -C sub-upstream commit -m "add submodule" &&
+ # Regular submodule
+ git -C super submodule add "${pwd}/test_dirs/sub-upstream" sub &&
+ # Submodule in a subdirectory
+ git -C super submodule add "${pwd}/test_dirs/sub-sub-upstream" second/sub &&
+ git -C super commit -m "add submodule" &&
+ git -C super config submodule.propagateBranches true &&
+ git -C super/sub submodule update --init
+ ) &&
+ reset_test
+'
+
+# Test the argument parsing
+test_expect_success '--recurse-submodules should create branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a &&
+ git -C sub/sub-sub rev-parse branch-a &&
+ git -C second/sub rev-parse branch-a
+ )
+'
+
+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ echo "fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled" >expected &&
+ test_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&
+ test_cmp expected actual
+ )
+'
+
+test_expect_success '--recurse-submodules should fail when not creating branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ echo "fatal: --recurse-submodules can only be used to create branches" >expected &&
+ test_must_fail git branch --recurse-submodules -D branch-a 2>actual &&
+ test_cmp expected actual &&
+ # Assert that the branches were not deleted
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a
+ )
+'
+
+test_expect_success 'should respect submodule.recurse when creating branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -c submodule.recurse=true branch branch-a &&
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a
+ )
+'
+
+test_expect_success 'should ignore submodule.recurse when not creating branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git -c submodule.recurse=true branch -D branch-a &&
+ test_no_branch . branch-a &&
+ git -C sub rev-parse branch-a
+ )
+'
+
+# Test branch creation behavior
+test_expect_success 'should create branches based off commit id in superproject' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git checkout --recurse-submodules branch-a &&
+ git -C sub rev-parse HEAD >expected &&
+ # Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a
+ git -C sub checkout branch-a &&
+ test_commit -C sub bar &&
+ # Create a new branch-b branch with start-point=branch-a
+ git branch --recurse-submodules branch-b branch-a &&
+ git rev-parse branch-b &&
+ git -C sub rev-parse branch-b >actual &&
+ # Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a
+ test_cmp expected actual
+ )
+'
+
+test_expect_success 'should not create any branches if branch is not valid for all repos' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -C sub branch branch-a &&
+ test_must_fail git branch --recurse-submodules branch-a 2>actual &&
+ test_no_branch . branch-a &&
+ grep "submodule .sub.: fatal: a branch named .branch-a. already exists" actual
+ )
+'
+
+test_expect_success 'should create branches if branch exists and --force is given' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -C sub rev-parse HEAD >expected &&
+ test_commit -C sub baz &&
+ # branch-a in sub now points to a newer commit.
+ git -C sub branch branch-a HEAD &&
+ git -C sub rev-parse branch-a >actual-old-branch-a &&
+ git branch --recurse-submodules --force branch-a &&
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a >actual-new-branch-a &&
+ test_cmp expected actual-new-branch-a &&
+ # assert that branch --force actually moved the sub
+ # branch
+ ! test_cmp expected actual-old-branch-a
+ )
+'
+
+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch branch-a &&
+ git checkout -b branch-b &&
+ git submodule add ../sub-upstream sub2 &&
+ git -C sub2 submodule update --init &&
+ # branch-b now has a committed submodule not in branch-a
+ git commit -m "add second submodule" &&
+ git checkout branch-a &&
+ git branch --recurse-submodules branch-c branch-b &&
+ git checkout --recurse-submodules branch-c &&
+ git -C sub2 rev-parse branch-c &&
+ git -C sub2/sub-sub rev-parse branch-c
+ )
+'
+
+test_expect_success 'should not create branches in inactive submodules' '
+ test_when_finished "reset_test" &&
+ test_config -C super submodule.sub.active false &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git rev-parse branch-a &&
+ test_no_branch sub branch-a
+ )
+'
+
+test_expect_success 'should set up tracking of local branches with track=always' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&
+ git -C sub rev-parse main &&
+ test_cmp_config -C sub . branch.branch-a.remote &&
+ test_cmp_config -C sub refs/heads/main branch.branch-a.merge
+ )
+'
+
+test_expect_success 'should set up tracking of local branches with explicit track' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --track --recurse-submodules branch-a main &&
+ git -C sub rev-parse main &&
+ test_cmp_config -C sub . branch.branch-a.remote &&
+ test_cmp_config -C sub refs/heads/main branch.branch-a.merge
+ )
+'
+
+test_expect_success 'should not set up unnecessary tracking of local branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a main &&
+ git -C sub rev-parse main &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.remote &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.merge
+ )
+'
+
+reset_remote_test () {
+ rm -fr super-clone &&
+ reset_test
+}
+
+test_expect_success 'setup tests with remotes' '
+ (
+ cd test_dirs &&
+ (
+ cd super &&
+ git branch branch-a &&
+ git checkout -b branch-b &&
+ git submodule add ../sub-upstream sub2 &&
+ # branch-b now has a committed submodule not in branch-a
+ git commit -m "add second submodule"
+ ) &&
+ git clone --branch main --recurse-submodules super super-clone &&
+ git -C super-clone config submodule.propagateBranches true
+ ) &&
+ reset_remote_test
+'
+
+test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' '
+ test_when_finished "reset_remote_test" &&
+ (
+ cd super-clone &&
+ # This should succeed because super-clone has sub in .git/modules
+ git branch --recurse-submodules branch-a origin/branch-a &&
+ # This should fail because super-clone does not have sub2 .git/modules
+ test_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&
+ grep "fatal: submodule .sub2.: unable to find submodule" actual &&
+ test_no_branch . branch-b &&
+ test_no_branch sub branch-b &&
+ # User can fix themselves by initializing the submodule
+ git checkout origin/branch-b &&
+ git submodule update --init --recursive &&
+ git branch --recurse-submodules branch-b origin/branch-b
+ )
+'
+
+test_expect_success 'should set up tracking of remote-tracking branches' '
+ test_when_finished "reset_remote_test" &&
+ (
+ cd super-clone &&
+ git branch --recurse-submodules branch-a origin/branch-a &&
+ test_cmp_config origin branch.branch-a.remote &&
+ test_cmp_config refs/heads/branch-a branch.branch-a.merge &&
+ # "origin/branch-a" does not exist for "sub", but it matches the refspec
+ # so tracking should be set up
+ test_cmp_config -C sub origin branch.branch-a.remote &&
+ test_cmp_config -C sub refs/heads/branch-a branch.branch-a.merge &&
+ test_cmp_config -C sub/sub-sub origin branch.branch-a.remote &&
+ test_cmp_config -C sub/sub-sub refs/heads/branch-a branch.branch-a.merge
+ )
+'
+
+test_expect_success 'should not fail when unable to set up tracking in submodule' '
+ test_when_finished "reset_remote_test" &&
+ (
+ cd super-clone &&
+ git remote rename origin ex-origin &&
+ git branch --recurse-submodules branch-a ex-origin/branch-a &&
+ test_cmp_config ex-origin branch.branch-a.remote &&
+ test_cmp_config refs/heads/branch-a branch.branch-a.merge &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.remote &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.merge
+ )
+'
+
+test_done
test_cmp expect actual
'
+test_expect_success 'rebase --apply reflog' '
+ git checkout -b reflog-apply start &&
+ old_head_reflog="$(git log -g --format=%gs -1 HEAD)" &&
+
+ git rebase --apply Y &&
+
+ git log -g --format=%gs -4 HEAD >actual &&
+ cat >expect <<-EOF &&
+ rebase finished: returning to refs/heads/reflog-apply
+ rebase: Z
+ rebase: checkout Y
+ $old_head_reflog
+ EOF
+ test_cmp expect actual &&
+
+ git log -g --format=%gs -2 reflog-apply >actual &&
+ cat >expect <<-EOF &&
+ rebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)
+ branch: Created from start
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success 'rebase -i onto unrelated history' '
git init unrelated &&
test_commit -C unrelated 1 &&
log_with_names () {
git rev-list --topo-order --parents --pretty="tformat:%s" HEAD |
- git name-rev --stdin --name-only --refs=refs/heads/$1
+ git name-rev --annotate-stdin --name-only --refs=refs/heads/$1
}
test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
'
+test_orig_head_helper () {
+ test_when_finished 'git rebase --abort &&
+ git checkout topic &&
+ git reset --hard commit-new-file-F2-on-topic-branch' &&
+ git update-ref -d ORIG_HEAD &&
+ test_must_fail git rebase "$@" &&
+ test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch
+}
+
+test_orig_head () {
+ type=$1
+ test_expect_success "rebase $type sets ORIG_HEAD correctly" '
+ git checkout topic &&
+ git reset --hard commit-new-file-F2-on-topic-branch &&
+ test_orig_head_helper $type main
+ '
+
+ test_expect_success "rebase $type <upstream> <branch> sets ORIG_HEAD correctly" '
+ git checkout main &&
+ test_orig_head_helper $type main topic
+ '
+}
+
+test_orig_head --apply
+test_orig_head --merge
+
test_done
test_expect_success 'setup again' '
git reset --hard &&
test_chmod +x file &&
- echo content >>file
+ echo content >>file &&
+ test_write_lines A B C D>file2 &&
+ git add file2
'
# Write the patch file with a new line at the top and bottom
content
+lastline
\ No newline at end of file
+ diff --git a/file2 b/file2
+ index 8422d40..35b930a 100644
+ --- a/file2
+ +++ b/file2
+ @@ -1,4 +1,5 @@
+ -A
+ +Z
+ B
+ +Y
+ C
+ -D
+ +X
EOF
'
# Expected output, diff is similar to the patch but w/ diff at the top
test_expect_success 'setup expected' '
echo diff --git a/file b/file >expected &&
- cat patch |sed "/^index/s/ 100644/ 100755/" >>expected &&
+ sed -e "/^index 180b47c/s/ 100644/ 100755/" \
+ -e /1,5/s//1,4/ \
+ -e /Y/d patch >>expected &&
cat >expected-output <<-\EOF
--- a/file
+++ b/file
content
+lastline
\ No newline at end of file
+ --- a/file2
+ +++ b/file2
+ @@ -1,4 +1,5 @@
+ -A
+ +Z
+ B
+ +Y
+ C
+ -D
+ +X
+ @@ -1,2 +1,2 @@
+ -A
+ +Z
+ B
+ @@ -2,2 +2,3 @@
+ B
+ +Y
+ C
+ @@ -3,2 +4,2 @@
+ C
+ -D
+ +X
EOF
'
test_expect_success 'add first line works' '
git commit -am "clear local changes" &&
git apply patch &&
- printf "%s\n" s y y | git add -p file 2>error |
- sed -n -e "s/^([1-2]\/[1-2]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
- -e "/^[-+@ \\\\]"/p >output &&
+ test_write_lines s y y s y n y | git add -p 2>error >raw-output &&
+ sed -n -e "s/^([1-9]\/[1-9]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
+ -e "/^[-+@ \\\\]"/p raw-output >output &&
test_must_be_empty error &&
git diff --cached >diff &&
diff_cmp expected diff &&
fi &&
git add sparse_entry &&
git update-index --skip-worktree sparse_entry &&
+ git config core.sparseCheckout false &&
git commit --allow-empty -m "ensure sparse_entry exists at HEAD" &&
SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry)
}
'
test_expect_success 'git add --renormalize does not update sparse entries' '
+ test_when_finished rm .gitattributes &&
test_config core.autocrlf false &&
setup_sparse_entry "LINEONE\r\nLINETWO\r\n" &&
echo "sparse_entry text=auto" >.gitattributes &&
rm file &&
ln -s file2 file &&
git stash save "file to symlink" &&
- test -f file &&
+ test_path_is_file_not_symlink file &&
test bar = "$(cat file)" &&
git stash apply &&
- case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac
+ test_path_is_symlink file &&
+ test "$(test_readlink file)" = file2
'
test_expect_success SYMLINKS 'stash file to symlink (stage rm)' '
git rm file &&
ln -s file2 file &&
git stash save "file to symlink (stage rm)" &&
- test -f file &&
+ test_path_is_file_not_symlink file &&
test bar = "$(cat file)" &&
git stash apply &&
- case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac
+ test_path_is_symlink file &&
+ test "$(test_readlink file)" = file2
'
test_expect_success SYMLINKS 'stash file to symlink (full stage)' '
ln -s file2 file &&
git add file &&
git stash save "file to symlink (full stage)" &&
- test -f file &&
+ test_path_is_file_not_symlink file &&
test bar = "$(cat file)" &&
git stash apply &&
- case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac
+ test_path_is_symlink file &&
+ test "$(test_readlink file)" = file2
'
# This test creates a commit with a symlink used for the following tests
rm -fr dir &&
echo bar >dir &&
git stash save "directory to file" &&
- test -d dir &&
+ test_path_is_dir dir &&
test foo = "$(cat dir/file)" &&
test_must_fail git stash apply &&
test bar = "$(cat dir)" &&
mkdir file &&
echo foo >file/file &&
git stash save "file to directory" &&
- test -f file &&
+ test_path_is_file file &&
test bar = "$(cat file)" &&
git stash apply &&
- test -f file/file &&
+ test_path_is_file file/file &&
test foo = "$(cat file/file)"
'
test_cmp expect actual
'
+test_expect_success 'create when branch name has /' '
+ test_when_finished "git checkout main" &&
+ git checkout -b some/topic &&
+ >foo &&
+ git add foo &&
+ STASH_ID=$(git stash create "create test message") &&
+ echo "On some/topic: create test message" >expect &&
+ git show --pretty=%s -s ${STASH_ID} >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'create with multiple arguments for the message' '
>foo &&
git add foo &&
>2 &&
git add 2 &&
test_config user.useconfigonly true &&
- test_config stash.usebuiltin true &&
(
sane_unset GIT_AUTHOR_NAME &&
sane_unset GIT_AUTHOR_EMAIL &&
git rev-parse --verify refs/stash:A.t
'
-test_expect_success 'stash -c stash.useBuiltin=false warning ' '
- expected="stash.useBuiltin support has been removed" &&
-
- git -c stash.useBuiltin=false stash 2>err &&
- test_i18ngrep "$expected" err &&
- env GIT_TEST_STASH_USE_BUILTIN=false git stash 2>err &&
- test_i18ngrep "$expected" err &&
-
- git -c stash.useBuiltin=true stash 2>err &&
- test_must_be_empty err &&
- env GIT_TEST_STASH_USE_BUILTIN=true git stash 2>err &&
- test_must_be_empty err
-'
-
test_expect_success 'git stash succeeds despite directory/file change' '
test_create_repo directory_file_switch_v1 &&
(
--- /dev/null
+#!/bin/sh
+
+test_description='remerge-diff handling'
+
+. ./test-lib.sh
+
+# This test is ort-specific
+if test "${GIT_TEST_MERGE_ALGORITHM}" != ort
+then
+ skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
+ test_done
+fi
+
+test_expect_success 'setup basic merges' '
+ test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m base &&
+
+ git branch feature_a &&
+ git branch feature_b &&
+ git branch feature_c &&
+
+ git branch ab_resolution &&
+ git branch bc_resolution &&
+
+ git checkout feature_a &&
+ test_write_lines 1 2 three 4 5 6 7 eight 9 >numbers &&
+ git commit -a -m change_a &&
+
+ git checkout feature_b &&
+ test_write_lines 1 2 tres 4 5 6 7 8 9 >numbers &&
+ git commit -a -m change_b &&
+
+ git checkout feature_c &&
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+ git commit -a -m change_c &&
+
+ git checkout bc_resolution &&
+ git merge --ff-only feature_b &&
+ # no conflict
+ git merge feature_c &&
+
+ git checkout ab_resolution &&
+ git merge --ff-only feature_a &&
+ # conflicts!
+ test_must_fail git merge feature_b &&
+ # Resolve conflict...and make another change elsewhere
+ test_write_lines 1 2 drei 4 5 6 7 acht 9 >numbers &&
+ git add numbers &&
+ git merge --continue
+'
+
+test_expect_success 'remerge-diff on a clean merge' '
+ git log -1 --oneline bc_resolution >expect &&
+ git show --oneline --remerge-diff bc_resolution >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff with both a resolved conflict and an unrelated change' '
+ git log -1 --oneline ab_resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (content): Merge conflict in numbers
+ index a1fb731..6875544 100644
+ --- a/numbers
+ +++ b/numbers
+ @@ -1,13 +1,9 @@
+ 1
+ 2
+ -<<<<<<< b0ed5cb (change_a)
+ -three
+ -=======
+ -tres
+ ->>>>>>> 6cd3f82 (change_b)
+ +drei
+ 4
+ 5
+ 6
+ 7
+ -eight
+ +acht
+ 9
+ EOF
+ # Hashes above are sha1; rip them out so test works with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff ab_resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup non-content conflicts' '
+ git switch --orphan base &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+ test_write_lines a b c d e f g h i >letters &&
+ test_write_lines in the way >content &&
+ git add numbers letters content &&
+ git commit -m base &&
+
+ git branch side1 &&
+ git branch side2 &&
+
+ git checkout side1 &&
+ test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+ git mv letters letters_side1 &&
+ git mv content file_or_directory &&
+ git add numbers &&
+ git commit -m side1 &&
+
+ git checkout side2 &&
+ git rm numbers &&
+ git mv letters letters_side2 &&
+ mkdir file_or_directory &&
+ echo hello >file_or_directory/world &&
+ git add file_or_directory/world &&
+ git commit -m side2 &&
+
+ git checkout -b resolution side1 &&
+ test_must_fail git merge side2 &&
+ test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git add letters_side1 &&
+ git rm letters &&
+ git rm letters_side2 &&
+ git add file_or_directory~HEAD &&
+ git mv file_or_directory~HEAD wanted_content &&
+ git commit -m resolved
+'
+
+test_expect_success 'remerge-diff with non-content conflicts' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/file_or_directory~HASH (side1) b/wanted_content
+ similarity index 100%
+ rename from file_or_directory~HASH (side1)
+ rename to wanted_content
+ remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+ diff --git a/letters b/letters
+ remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+ diff --git a/letters_side2 b/letters_side2
+ deleted file mode 100644
+ index b236ae5..0000000
+ --- a/letters_side2
+ +++ /dev/null
+ @@ -1,9 +0,0 @@
+ -a
+ -b
+ -c
+ -d
+ -e
+ -f
+ -g
+ -h
+ -i
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree.
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ diff-filter=U: all conflict headers, no diff content' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/file_or_directory~HASH (side1) b/file_or_directory~HASH (side1)
+ remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+ diff --git a/letters b/letters
+ remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree.
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff --diff-filter=U resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ diff-filter=R: relevant file + conflict header' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/file_or_directory~HASH (side1) b/wanted_content
+ similarity index 100%
+ rename from file_or_directory~HASH (side1)
+ rename to wanted_content
+ remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff --diff-filter=R resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ pathspec: limits to relevant file including conflict header' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/letters b/letters
+ remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+ diff --git a/letters_side2 b/letters_side2
+ deleted file mode 100644
+ index b236ae5..0000000
+ --- a/letters_side2
+ +++ /dev/null
+ @@ -1,9 +0,0 @@
+ -a
+ -b
+ -c
+ -d
+ -e
+ -f
+ -g
+ -h
+ -i
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff resolution -- "letters*" >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup non-content conflicts' '
+ git switch --orphan newbase &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m base &&
+
+ git branch newside1 &&
+ git branch newside2 &&
+
+ git checkout newside1 &&
+ test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m side1 &&
+
+ git checkout newside2 &&
+ test_write_lines 1 2 drei 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m side2 &&
+
+ git checkout -b newresolution newside1 &&
+ test_must_fail git merge newside2 &&
+ git checkout --theirs numbers &&
+ git add -u numbers &&
+ git commit -m resolved
+'
+
+test_expect_success 'remerge-diff turns off history simplification' '
+ git log -1 --oneline newresolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (content): Merge conflict in numbers
+ index 070e9e7..5335e78 100644
+ --- a/numbers
+ +++ b/numbers
+ @@ -1,10 +1,6 @@
+ 1
+ 2
+ -<<<<<<< 96f1e45 (side1)
+ -three
+ -=======
+ drei
+ ->>>>>>> 4fd522f (side2)
+ 4
+ 5
+ 6
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff newresolution -- numbers >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_done
test_when_finished "git am --abort || :" &&
git checkout empty-commit^ &&
test_must_fail git am --empty empty-commit.patch 2>err &&
- echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+ echo "error: invalid value for '\''--empty'\'': '\''empty-commit.patch'\''" >expected &&
test_cmp expected err
'
'
+test_expect_success 'multiple --diff-filter bits' '
+
+ git log -M --pretty="format:%s" --diff-filter=R HEAD >expect &&
+ git log -M --pretty="format:%s" --diff-filter=Ra HEAD >actual &&
+ test_cmp expect actual &&
+ git log -M --pretty="format:%s" --diff-filter=aR HEAD >actual &&
+ test_cmp expect actual &&
+ git log -M --pretty="format:%s" \
+ --diff-filter=a --diff-filter=R HEAD >actual &&
+ test_cmp expect actual
+
+'
+
test_expect_success 'diff-filter=C' '
git log -C -C --pretty="format:%s" --diff-filter=C HEAD >actual &&
)
'
+for cmd in show whatchanged reflog format-patch
+do
+ case "$cmd" in
+ format-patch) myarg="HEAD~.." ;;
+ *) myarg= ;;
+ esac
+
+ test_expect_success "$cmd: understands grep.patternType, like 'log'" '
+ git init "pattern-type-$cmd" &&
+ (
+ cd "pattern-type-$cmd" &&
+ test_commit 1 file A &&
+ test_commit "(1|2)" file B 2 &&
+
+ git -c grep.patternType=fixed $cmd --grep="..." $myarg >actual &&
+ test_must_be_empty actual &&
+
+ git -c grep.patternType=basic $cmd --grep="..." $myarg >actual &&
+ test_file_not_empty actual
+ )
+ '
+done
+
test_expect_success 'log --author' '
cat >expect <<-\EOF &&
Author: <BOLD;RED>A U<RESET> Thor <author@example.com>
test_expect_success 'log --graph with full output' '
git log --graph --date-order --pretty=short |
- git name-rev --name-only --stdin |
+ git name-rev --name-only --annotate-stdin |
sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual &&
test_cmp expect actual
'
test_cmp_graph --name-only tangle..reach
'
+test_expect_success '--no-graph countermands --graph' '
+ git log >expect &&
+ git log --graph --no-graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--graph countermands --no-graph' '
+ git log --graph >expect &&
+ git log --no-graph --graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-graph does not unset --topo-order' '
+ git log --topo-order >expect &&
+ git log --topo-order --no-graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-graph does not unset --parents' '
+ git log --parents >expect &&
+ git log --parents --no-graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--reverse and --graph conflict' '
+ test_must_fail git log --reverse --graph 2>stderr &&
+ test_i18ngrep "cannot be used together" stderr
+'
+
+test_expect_success '--reverse --graph --no-graph works' '
+ git log --reverse >expect &&
+ git log --reverse --graph --no-graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--show-linear-break and --graph conflict' '
+ test_must_fail git log --show-linear-break --graph 2>stderr &&
+ test_i18ngrep "cannot be used together" stderr
+'
+
+test_expect_success '--show-linear-break --graph --no-graph works' '
+ git log --show-linear-break >expect &&
+ git log --show-linear-break --graph --no-graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-walk and --graph conflict' '
+ test_must_fail git log --no-walk --graph 2>stderr &&
+ test_i18ngrep "cannot be used together" stderr
+'
+
+test_expect_success '--no-walk --graph --no-graph works' '
+ git log --no-walk >expect &&
+ git log --no-walk --graph --no-graph >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--walk-reflogs and --graph conflict' '
+ test_must_fail git log --walk-reflogs --graph 2>stderr &&
+ (test_i18ngrep "cannot combine" stderr ||
+ test_i18ngrep "cannot be used together" stderr)
+'
+
+test_expect_success '--walk-reflogs --graph --no-graph works' '
+ git log --walk-reflogs >expect &&
+ git log --walk-reflogs --graph --no-graph >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'dotdot is a parent directory' '
mkdir -p a/b &&
( echo sixth && echo fifth ) >expect &&
git merge --no-ff -m msg signed_tag_x509_nokey &&
GNUPGHOME=. git log --graph --show-signature -n1 plain-x509-nokey >actual &&
grep "^|\\\ merged tag" actual &&
- grep "^| | gpgsm: certificate not found" actual
+ grep -e "^| | gpgsm: certificate not found" \
+ -e "^| | gpgsm: failed to find the certificate: Not found" actual
'
test_expect_success GPGSM 'log --graph --show-signature for merged tag x509 bad signature' '
shift
git patch-id "$@" >patch-id.output &&
sed "s/ .*//" patch-id.output >patch-id_"$patch_name" &&
- test_line_count -gt 0 patch-id_"$patch_name"
+ test_line_count -eq 1 patch-id_"$patch_name"
}
get_top_diff () {
)
'
-cat >nonl <<\EOF
-diff --git i/a w/a
-index e69de29..2e65efe 100644
---- i/a
-+++ w/a
-@@ -0,0 +1 @@
-+a
-\ No newline at end of file
-diff --git i/b w/b
-index e69de29..6178079 100644
---- i/b
-+++ w/b
-@@ -0,0 +1 @@
-+b
-EOF
-
-cat >withnl <<\EOF
-diff --git i/a w/a
-index e69de29..7898192 100644
---- i/a
-+++ w/a
-@@ -0,0 +1 @@
-+a
-diff --git i/b w/b
-index e69de29..6178079 100644
---- i/b
-+++ w/b
-@@ -0,0 +1 @@
-+b
-EOF
-
test_expect_success 'patch-id handles no-nl-at-eof markers' '
- cat nonl | calc_patch_id nonl &&
- cat withnl | calc_patch_id withnl &&
+ cat >nonl <<-\EOF &&
+ diff --git i/a w/a
+ index e69de29..2e65efe 100644
+ --- i/a
+ +++ w/a
+ @@ -0,0 +1 @@
+ +a
+ \ No newline at end of file
+ diff --git i/b w/b
+ index e69de29..6178079 100644
+ --- i/b
+ +++ w/b
+ @@ -0,0 +1 @@
+ +b
+ EOF
+ cat >withnl <<-\EOF &&
+ diff --git i/a w/a
+ index e69de29..7898192 100644
+ --- i/a
+ +++ w/a
+ @@ -0,0 +1 @@
+ +a
+ diff --git i/b w/b
+ index e69de29..6178079 100644
+ --- i/b
+ +++ w/b
+ @@ -0,0 +1 @@
+ +b
+ EOF
+ calc_patch_id nonl <nonl &&
+ calc_patch_id withnl <withnl &&
test_cmp patch-id_nonl patch-id_withnl
'
+
+test_expect_success 'patch-id handles diffs with one line of before/after' '
+ cat >diffu1 <<-\EOF &&
+ diff --git a/bar b/bar
+ index bdaf90f..31051f6 100644
+ --- a/bar
+ +++ b/bar
+ @@ -2 +2,2 @@
+ b
+ +c
+ diff --git a/car b/car
+ index 00750ed..2ae5e34 100644
+ --- a/car
+ +++ b/car
+ @@ -1 +1,2 @@
+ 3
+ +d
+ diff --git a/foo b/foo
+ index e439850..7146eb8 100644
+ --- a/foo
+ +++ b/foo
+ @@ -2 +2,2 @@
+ a
+ +e
+ EOF
+ calc_patch_id diffu1 <diffu1 &&
+ test_config patchid.stable true &&
+ calc_patch_id diffu1stable <diffu1
+'
test_done
test_i18ngrep "Resolving deltas" err
'
+test_expect_success 'too-large packs report the breach' '
+ pack=$(git pack-objects --all pack </dev/null) &&
+ sz="$(test_file_size pack-$pack.pack)" &&
+ test "$sz" -gt 20 &&
+ test_must_fail git index-pack --max-input-size=20 pack-$pack.pack 2>err &&
+ grep "maximum allowed size (20 bytes)" err
+'
+
test_done
)
'
+test_expect_success 'complains about multiple pack bitmaps' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+
+ git repack -adb &&
+ bitmap="$(ls .git/objects/pack/pack-*.bitmap)" &&
+ mv "$bitmap" "$bitmap.bak" &&
+
+ test_commit other &&
+ git repack -ab &&
+
+ mv "$bitmap.bak" "$bitmap" &&
+
+ find .git/objects/pack -type f -name "*.pack" >packs &&
+ find .git/objects/pack -type f -name "*.bitmap" >bitmaps &&
+ test_line_count = 2 packs &&
+ test_line_count = 2 bitmaps &&
+
+ git rev-list --use-bitmap-index HEAD 2>err &&
+ grep "ignoring extra bitmap file" err
+ )
+'
+
test_done
'
create_bogus_ref () {
- test_when_finished 'rm -f .git/refs/heads/bogus..name' &&
- echo $bogus >.git/refs/heads/bogus..name
+ test-tool ref-store main update-ref msg "refs/heads/bogus..name" $bogus $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+ test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/bogus..name"
}
test_expect_success 'create history reachable only from a bogus-named ref' '
# we do not want to count on running pack-refs to
# actually pack it, as it is perfectly reasonable to
# skip processing a broken ref
-test_expect_success 'create packed-refs file with broken ref' '
+test_expect_success REFFILES 'create packed-refs file with broken ref' '
rm -f .git/refs/heads/main &&
cat >.git/packed-refs <<-EOF &&
$missing refs/heads/main
test_cmp expect actual
'
-test_expect_success 'pack-refs does not silently delete broken packed ref' '
+test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' '
git pack-refs --all --prune &&
git rev-parse refs/heads/main >actual &&
test_cmp expect actual
'
-test_expect_success 'pack-refs does not drop broken refs during deletion' '
+test_expect_success REFFILES 'pack-refs does not drop broken refs during deletion' '
git update-ref -d refs/heads/other &&
git rev-parse refs/heads/main >actual &&
test_cmp expect actual
echo $cur &&
echo "$(git rev-parse :file) file"
} | git pack-objects --stdout >tmp &&
- git index-pack --stdin --fix-thin <tmp || return 1
+ GIT_TRACE2_EVENT=$PWD/trace \
+ git index-pack -v --stdin --fix-thin <tmp || return 1 &&
+ grep -c region_enter.*progress trace >enter &&
+ grep -c region_leave.*progress trace >leave &&
+ test_cmp enter leave &&
prev=$cur
done
'
GIT_TEST_MULTI_PACK_INDEX=0
GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
-objdir=.git/objects
-midx=$objdir/pack/multi-pack-index
+# This test exercise multi-pack bitmap functionality where the object order is
+# stored and read from a special chunk within the MIDX, so use the default
+# behavior here.
+sane_unset GIT_TEST_MIDX_WRITE_REV
+sane_unset GIT_TEST_MIDX_READ_RIDX
-# midx_pack_source <obj>
-midx_pack_source () {
- test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
-}
-
-setup_bitmap_history
-
-test_expect_success 'enable core.multiPackIndex' '
- git config core.multiPackIndex true
-'
-
-test_expect_success 'create single-pack midx with bitmaps' '
- git repack -ad &&
- git multi-pack-index write --bitmap &&
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests
-
-test_expect_success 'create new additional packs' '
- for i in $(test_seq 1 16)
- do
- test_commit "$i" &&
- git repack -d || return 1
- done &&
-
- git checkout -b other2 HEAD~8 &&
- for i in $(test_seq 1 8)
- do
- test_commit "side-$i" &&
- git repack -d || return 1
- done &&
- git checkout second
-'
-
-test_expect_success 'create multi-pack midx with bitmaps' '
- git multi-pack-index write --bitmap &&
-
- ls $objdir/pack/pack-*.pack >packs &&
- test_line_count = 25 packs &&
-
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests
-
-test_expect_success '--no-bitmap is respected when bitmaps exist' '
- git multi-pack-index write --bitmap &&
-
- test_commit respect--no-bitmap &&
- git repack -d &&
-
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
-
- git multi-pack-index write --no-bitmap &&
-
- test_path_is_file $midx &&
- test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_missing $midx-$(midx_checksum $objdir).rev
-'
-
-test_expect_success 'setup midx with base from later pack' '
- # Write a and b so that "a" is a delta on top of base "b", since Git
- # prefers to delete contents out of a base rather than add to a shorter
- # object.
- test_seq 1 128 >a &&
- test_seq 1 130 >b &&
-
- git add a b &&
- git commit -m "initial commit" &&
-
- a=$(git rev-parse HEAD:a) &&
- b=$(git rev-parse HEAD:b) &&
-
- # In the first pack, "a" is stored as a delta to "b".
- p1=$(git pack-objects .git/objects/pack/pack <<-EOF
- $a
- $b
- EOF
- ) &&
-
- # In the second pack, "a" is missing, and "b" is not a delta nor base to
- # any other object.
- p2=$(git pack-objects .git/objects/pack/pack <<-EOF
- $b
- $(git rev-parse HEAD)
- $(git rev-parse HEAD^{tree})
- EOF
- ) &&
-
- git prune-packed &&
- # Use the second pack as the preferred source, so that "b" occurs
- # earlier in the MIDX object order, rendering "a" unusable for pack
- # reuse.
- git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
-
- have_delta $a $b &&
- test $(midx_pack_source $a) != $(midx_pack_source $b)
-'
-
-rev_list_tests 'full bitmap with backwards delta'
-
-test_expect_success 'clone with bitmaps enabled' '
- git clone --no-local --bare . clone-reverse-delta.git &&
- test_when_finished "rm -fr clone-reverse-delta.git" &&
-
- git rev-parse HEAD >expect &&
- git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
- test_cmp expect actual
-'
+midx_bitmap_core
bitmap_reuse_tests() {
from=$1
)
'
-test_expect_success 'setup partial bitmaps' '
- test_commit packed &&
- git repack &&
- test_commit loose &&
- git multi-pack-index write --bitmap 2>err &&
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests HEAD~
+midx_bitmap_partial_tests
test_expect_success 'removing a MIDX clears stale bitmaps' '
rm -fr repo &&
# Write a MIDX and bitmap; remove the MIDX but leave the bitmap.
stale_bitmap=$midx-$(midx_checksum $objdir).bitmap &&
- stale_rev=$midx-$(midx_checksum $objdir).rev &&
rm $midx &&
# Then write a new MIDX.
test_path_is_file $midx &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
- test_path_is_missing $stale_bitmap &&
- test_path_is_missing $stale_rev
+ test_path_is_missing $stale_bitmap
)
'
git multi-pack-index write --bitmap &&
test_path_is_file $midx &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
test-tool bitmap list-commits | sort >bitmaps &&
comm -13 bitmaps commits >before &&
<before | git update-ref --stdin &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
git -c pack.preferBitmapTips=refs/tags/include \
grep "$(git rev-parse two)" bitmaps &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
# Then again, but with a refs snapshot which only sees
) >snapshot &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
git multi-pack-index write --bitmap --refs-snapshot=snapshot &&
)
'
+test_expect_success 'no .bitmap is written without any objects' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ empty="$(git pack-objects $objdir/pack/pack </dev/null)" &&
+ cat >packs <<-EOF &&
+ pack-$empty.idx
+ EOF
+
+ git multi-pack-index write --bitmap --stdin-packs \
+ <packs 2>err &&
+
+ grep "bitmap without any objects" err &&
+
+ test_path_is_file $midx &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).bitmap
+ )
+'
+
+test_expect_success 'graceful fallback when missing reverse index' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+
+ # write a pack and MIDX bitmap containing base
+ git repack -adb &&
+ git multi-pack-index write --bitmap &&
+
+ GIT_TEST_MIDX_READ_RIDX=0 \
+ git rev-list --use-bitmap-index HEAD 2>err &&
+ ! grep "ignoring extra bitmap file" err
+ )
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='exercise basic multi-pack bitmap functionality (.rev files)'
+
+. ./test-lib.sh
+. "${TEST_DIRECTORY}/lib-bitmap.sh"
+
+# We'll be writing our own midx and bitmaps, so avoid getting confused by the
+# automatic ones.
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
+# Unlike t5326, this test exercise multi-pack bitmap functionality where the
+# object order is stored in a separate .rev file.
+GIT_TEST_MIDX_WRITE_REV=1
+GIT_TEST_MIDX_READ_RIDX=0
+export GIT_TEST_MIDX_WRITE_REV
+export GIT_TEST_MIDX_READ_RIDX
+
+midx_bitmap_core rev
+midx_bitmap_partial_tests rev
+
+test_done
test $old = $new && test $flag = 0
'
-test_expect_success 'post-checkout is triggered on rebase' '
- test_when_finished "rm -f .git/post-checkout.args" &&
- git checkout -b rebase-test main &&
- rm -f .git/post-checkout.args &&
- git rebase rebase-on-me &&
- read old new flag <.git/post-checkout.args &&
- test $old != $new && test $flag = 1
-'
+test_rebase () {
+ args="$*" &&
+ test_expect_success "post-checkout is triggered on rebase $args" '
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout -B rebase-test main &&
+ rm -f .git/post-checkout.args &&
+ git rebase $args rebase-on-me &&
+ read old new flag <.git/post-checkout.args &&
+ test_cmp_rev main $old &&
+ test_cmp_rev rebase-on-me $new &&
+ test $flag = 1
+ '
+
+ test_expect_success "post-checkout is triggered on rebase $args with fast-forward" '
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout -B ff-rebase-test rebase-on-me^ &&
+ rm -f .git/post-checkout.args &&
+ git rebase $args rebase-on-me &&
+ read old new flag <.git/post-checkout.args &&
+ test_cmp_rev rebase-on-me^ $old &&
+ test_cmp_rev rebase-on-me $new &&
+ test $flag = 1
+ '
+
+ test_expect_success "rebase $args fast-forward branch checkout runs post-checkout hook" '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git update-ref refs/heads/rebase-fast-forward three &&
+ git checkout two &&
+ rm -f .git/post-checkout.args &&
+ git rebase $args HEAD rebase-fast-forward &&
+ read old new flag <.git/post-checkout.args &&
+ test_cmp_rev two $old &&
+ test_cmp_rev three $new &&
+ test $flag = 1
+ '
+
+ test_expect_success "rebase $args checkout does not remove untracked files" '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git update-ref refs/heads/rebase-fast-forward three &&
+ git checkout two &&
+ rm -f .git/post-checkout.args &&
+ echo untracked >three.t &&
+ test_when_finished "rm three.t" &&
+ test_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&
+ grep "untracked working tree files would be overwritten by checkout" err &&
+ test_path_is_missing .git/post-checkout.args
-test_expect_success 'post-checkout is triggered on rebase with fast-forward' '
- test_when_finished "rm -f .git/post-checkout.args" &&
- git checkout -b ff-rebase-test rebase-on-me^ &&
- rm -f .git/post-checkout.args &&
- git rebase rebase-on-me &&
- read old new flag <.git/post-checkout.args &&
- test $old != $new && test $flag = 1
'
+}
+
+test_rebase --apply &&
+test_rebase --merge
test_expect_success 'post-checkout hook is triggered by clone' '
mkdir -p templates/hooks &&
)
'
-test_expect_success 'use ref advertisement to prune "have" lines sent' '
+test_negotiation_algorithm_default () {
+ test_when_finished rm -rf clientv0 clientv2 &&
rm -rf server client &&
git init server &&
test_commit -C server both_have_1 &&
rm -f trace &&
cp -r client clientv0 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv0 \
- fetch origin server_has both_have_2 &&
+ "$@" fetch origin server_has both_have_2 &&
grep "have $(git -C client rev-parse client_has)" trace &&
grep "have $(git -C client rev-parse both_have_2)" trace &&
! grep "have $(git -C client rev-parse both_have_2^)" trace &&
rm -f trace &&
cp -r client clientv2 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv2 -c protocol.version=2 \
- fetch origin server_has both_have_2 &&
+ "$@" fetch origin server_has both_have_2 &&
grep "have $(git -C client rev-parse client_has)" trace &&
grep "have $(git -C client rev-parse both_have_2)" trace &&
! grep "have $(git -C client rev-parse both_have_2^)" trace
+}
+
+test_expect_success 'use ref advertisement to prune "have" lines sent' '
+ test_negotiation_algorithm_default
+'
+
+test_expect_success 'same as last but with config overrides' '
+ test_negotiation_algorithm_default \
+ -c feature.experimental=true \
+ -c fetch.negotiationAlgorithm=consecutive
+'
+
+test_expect_success 'ensure bogus fetch.negotiationAlgorithm yields error' '
+ test_when_finished rm -rf clientv0 &&
+ cp -r client clientv0 &&
+ test_must_fail git -C clientv0 --fetch.negotiationAlgorithm=bogus \
+ fetch origin server_has both_have_2
'
test_expect_success 'filtering by size' '
test_cmp expect actual
'
+test_expect_success 'atomic fetch with failing backfill' '
+ git init clone3 &&
+
+ # We want to test whether a failure when backfilling tags correctly
+ # aborts the complete transaction when `--atomic` is passed: we should
+ # neither create the branch nor should we create the tag when either
+ # one of both fails to update correctly.
+ #
+ # To trigger failure we simply abort when backfilling a tag.
+ write_script clone3/.git/hooks/reference-transaction <<-\EOF &&
+ while read oldrev newrev reference
+ do
+ if test "$reference" = refs/tags/tag1
+ then
+ exit 1
+ fi
+ done
+ EOF
+
+ test_must_fail git -C clone3 fetch --atomic .. $B:refs/heads/something &&
+ test_must_fail git -C clone3 rev-parse --verify refs/heads/something &&
+ test_must_fail git -C clone3 rev-parse --verify refs/tags/tag2
+'
+
+test_expect_success 'atomic fetch with backfill should use single transaction' '
+ git init clone4 &&
+
+ # Fetching with the `--atomic` flag should update all references in a
+ # single transaction, including backfilled tags. We thus expect to see
+ # a single reference transaction for the created branch and tags.
+ cat >expected <<-EOF &&
+ prepared
+ $ZERO_OID $B refs/heads/something
+ $ZERO_OID $S refs/tags/tag2
+ $ZERO_OID $T refs/tags/tag1
+ committed
+ $ZERO_OID $B refs/heads/something
+ $ZERO_OID $S refs/tags/tag2
+ $ZERO_OID $T refs/tags/tag1
+ EOF
+
+ write_script clone4/.git/hooks/reference-transaction <<-\EOF &&
+ ( echo "$*" && cat ) >>actual
+ EOF
+
+ git -C clone4 fetch --atomic .. $B:refs/heads/something &&
+ test_cmp expected clone4/actual
+'
+
+test_expect_success 'backfill failure causes command to fail' '
+ git init clone5 &&
+
+ # Create a tag that is nested below the tag we are about to fetch via
+ # the backfill mechanism. This causes a D/F conflict when backfilling
+ # and should thus cause the command to fail.
+ empty_blob=$(git -C clone5 hash-object -w --stdin </dev/null) &&
+ git -C clone5 update-ref refs/tags/tag1/nested $empty_blob &&
+
+ test_must_fail git -C clone5 fetch .. $B:refs/heads/something &&
+ test $B = $(git -C clone5 rev-parse --verify refs/heads/something) &&
+ test $S = $(git -C clone5 rev-parse --verify tag2) &&
+ test_must_fail git -C clone5 rev-parse --verify tag1
+'
+
test_done
git rev-parse sometag
'
+test_expect_success REFFILES 'fetch --prune fails to delete branches' '
+ cd "$D" &&
+ git clone . prune-fail &&
+ cd prune-fail &&
+ git update-ref refs/remotes/origin/extrabranch main &&
+ : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds &&
+ >.git/packed-refs.new &&
+
+ test_must_fail git fetch --prune origin
+'
+
test_expect_success 'fetch --atomic works with a single branch' '
test_when_finished "rm -rf \"$D\"/atomic" &&
test_cmp expected atomic/.git/FETCH_HEAD
'
+test_expect_success 'fetch --atomic --prune executes a single reference transaction only' '
+ test_when_finished "rm -rf \"$D\"/atomic" &&
+
+ cd "$D" &&
+ git branch scheduled-for-deletion &&
+ git clone . atomic &&
+ git branch -D scheduled-for-deletion &&
+ git branch new-branch &&
+ head_oid=$(git rev-parse HEAD) &&
+
+ # Fetching with the `--atomic` flag should update all references in a
+ # single transaction.
+ cat >expected <<-EOF &&
+ prepared
+ $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion
+ $ZERO_OID $head_oid refs/remotes/origin/new-branch
+ committed
+ $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion
+ $ZERO_OID $head_oid refs/remotes/origin/new-branch
+ EOF
+
+ write_script atomic/.git/hooks/reference-transaction <<-\EOF &&
+ ( echo "$*" && cat ) >>actual
+ EOF
+
+ git -C atomic fetch --atomic --prune origin &&
+ test_cmp expected atomic/actual
+'
+
test_expect_success '--refmap="" ignores configured refspec' '
cd "$TRASH_DIRECTORY" &&
git clone "$D" remote-refs &&
test_description='refspec parsing'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_refspec () {
test_i18ngrep "push negotiation failed" err
'
+test_expect_success 'push with negotiation does not attempt to fetch submodules' '
+ mk_empty submodule_upstream &&
+ test_commit -C submodule_upstream submodule_commit &&
+ git submodule add ./submodule_upstream submodule &&
+ mk_empty testrepo &&
+ git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+ test_commit -C testrepo unrelated_commit &&
+ git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
+ git -c submodule.recurse=true -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err &&
+ ! grep "Fetching submodule" err
+'
+
test_expect_success 'push without wildcard' '
mk_empty testrepo &&
git -C bare.git fetch -u .. HEAD:wt
'
+test_expect_success 'refuse to push a hidden ref, and make sure do not pollute the repository' '
+ mk_empty testrepo &&
+ git -C testrepo config receive.hiderefs refs/hidden &&
+ git -C testrepo config receive.unpackLimit 1 &&
+ test_must_fail git push testrepo HEAD:refs/hidden/foo &&
+ test_dir_is_empty testrepo/.git/objects/pack
+'
+
test_done
test_cmp_rev HEAD to-rebase-ff
'
+test_expect_success '--rebase with rebase.autostash succeeds on ff' '
+ test_when_finished "rm -fr src dst actual" &&
+ git init src &&
+ test_commit -C src "initial" file "content" &&
+ git clone src dst &&
+ test_commit -C src --printf "more_content" file "more content\ncontent\n" &&
+ echo "dirty" >>dst/file &&
+ test_config -C dst rebase.autostash true &&
+ git -C dst pull --rebase >actual 2>&1 &&
+ grep -q "Fast-forward" actual &&
+ grep -q "Applied autostash." actual
+'
+
test_expect_success '--rebase with conflicts shows advice' '
test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
git checkout -b seq &&
HOOK="$HOOKDIR/pre-push"
mkdir -p "$HOOKDIR"
write_script "$HOOK" <<EOF
-cat >/dev/null
+cat >actual
exit 0
EOF
git init --bare repo1 &&
git remote add parent1 repo1 &&
test_commit one &&
- git push parent1 HEAD:foreign
+ cat >expect <<-EOF &&
+ HEAD $(git rev-parse HEAD) refs/heads/foreign $(test_oid zero)
+ EOF
+
+ test_when_finished "rm actual" &&
+ git push parent1 HEAD:foreign &&
+ test_cmp expect actual
'
write_script "$HOOK" <<EOF
-cat >/dev/null
+cat >actual
exit 1
EOF
test_expect_success 'push with failing hook' '
test_commit two &&
- test_must_fail git push parent1 HEAD
+ cat >expect <<-EOF &&
+ HEAD $(git rev-parse HEAD) refs/heads/main $(test_oid zero)
+ EOF
+
+ test_when_finished "rm actual" &&
+ test_must_fail git push parent1 HEAD &&
+ test_cmp expect actual
'
test_expect_success '--no-verify bypasses hook' '
- git push --no-verify parent1 HEAD
+ git push --no-verify parent1 HEAD &&
+ test_path_is_missing actual
'
COMMIT2="$(git rev-parse HEAD)"
cat >>actual
EOF
-cat >expected <<EOF
-parent1
-repo1
-refs/heads/main $COMMIT2 refs/heads/foreign $COMMIT1
-EOF
-
test_expect_success 'push with hook' '
+ cat >expect <<-EOF &&
+ parent1
+ repo1
+ refs/heads/main $COMMIT2 refs/heads/foreign $COMMIT1
+ EOF
+
git push parent1 main:foreign &&
- diff expected actual
+ test_cmp expect actual
'
test_expect_success 'add a branch' '
COMMIT3="$(git rev-parse HEAD)"
export COMMIT3
-cat >expected <<EOF
-parent1
-repo1
-refs/heads/other $COMMIT3 refs/heads/foreign $COMMIT2
-EOF
-
test_expect_success 'push to default' '
+ cat >expect <<-EOF &&
+ parent1
+ repo1
+ refs/heads/other $COMMIT3 refs/heads/foreign $COMMIT2
+ EOF
git push &&
- diff expected actual
+ test_cmp expect actual
'
-cat >expected <<EOF
-parent1
-repo1
-refs/tags/one $COMMIT1 refs/tags/tag1 $ZERO_OID
-HEAD~ $COMMIT2 refs/heads/prev $ZERO_OID
-EOF
-
test_expect_success 'push non-branches' '
+ cat >expect <<-EOF &&
+ parent1
+ repo1
+ refs/tags/one $COMMIT1 refs/tags/tag1 $ZERO_OID
+ HEAD~ $COMMIT2 refs/heads/prev $ZERO_OID
+ EOF
+
git push parent1 one:tag1 HEAD~:refs/heads/prev &&
- diff expected actual
+ test_cmp expect actual
'
-cat >expected <<EOF
-parent1
-repo1
-(delete) $ZERO_OID refs/heads/prev $COMMIT2
-EOF
-
test_expect_success 'push delete' '
+ cat >expect <<-EOF &&
+ parent1
+ repo1
+ (delete) $ZERO_OID refs/heads/prev $COMMIT2
+ EOF
+
git push parent1 :prev &&
- diff expected actual
+ test_cmp expect actual
'
-cat >expected <<EOF
-repo1
-repo1
-HEAD $COMMIT3 refs/heads/other $ZERO_OID
-EOF
-
test_expect_success 'push to URL' '
+ cat >expect <<-EOF &&
+ repo1
+ repo1
+ HEAD $COMMIT3 refs/heads/other $ZERO_OID
+ EOF
+
git push repo1 HEAD &&
- diff expected actual
+ test_cmp expect actual
'
test_expect_success 'set up many-ref tests' '
)
'
+# bare clone giving "srv.bare" for use as our server.
+test_expect_success 'setup bare clone for server' '
+ git clone --bare "file://$(pwd)/." srv.bare &&
+ git -C srv.bare config --local uploadpack.allowfilter 1 &&
+ git -C srv.bare config --local uploadpack.allowanysha1inwant 1
+'
+
test_expect_success 'clone with --no-remote-submodules' '
test_when_finished "rm -rf super_clone" &&
git clone --recurse-submodules --no-remote-submodules "file://$pwd/." super_clone &&
)
'
+# do basic partial clone from "srv.bare"
+# confirm partial clone was registered in the local config for super and sub.
+test_expect_success 'clone with --filter' '
+ git clone --recurse-submodules \
+ --filter blob:none --also-filter-submodules \
+ "file://$pwd/srv.bare" super_clone &&
+ test_cmp_config -C super_clone true remote.origin.promisor &&
+ test_cmp_config -C super_clone blob:none remote.origin.partialclonefilter &&
+ test_cmp_config -C super_clone/sub true remote.origin.promisor &&
+ test_cmp_config -C super_clone/sub blob:none remote.origin.partialclonefilter
+'
+
+# check that clone.filterSubmodules works (--also-filter-submodules can be
+# omitted)
+test_expect_success 'filters applied with clone.filterSubmodules' '
+ test_config_global clone.filterSubmodules true &&
+ git clone --recurse-submodules --filter blob:none \
+ "file://$pwd/srv.bare" super_clone2 &&
+ test_cmp_config -C super_clone2 true remote.origin.promisor &&
+ test_cmp_config -C super_clone2 blob:none remote.origin.partialclonefilter &&
+ test_cmp_config -C super_clone2/sub true remote.origin.promisor &&
+ test_cmp_config -C super_clone2/sub blob:none remote.origin.partialclonefilter
+'
+
+test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodules=true' '
+ test_config_global clone.filterSubmodules true &&
+ git clone --recurse-submodules --filter blob:none \
+ --no-also-filter-submodules \
+ "file://$pwd/srv.bare" super_clone3 &&
+ test_cmp_config -C super_clone3 true remote.origin.promisor &&
+ test_cmp_config -C super_clone3 blob:none remote.origin.partialclonefilter &&
+ test_cmp_config -C super_clone3/sub false --default false remote.origin.promisor
+'
+
test_done
grep "push< version 1" log
'
+test_expect_success 'cloning branchless tagless but not refless remote' '
+ rm -rf server client &&
+
+ git -c init.defaultbranch=main init server &&
+ echo foo >server/foo.txt &&
+ git -C server add foo.txt &&
+ git -C server commit -m "message" &&
+ git -C server update-ref refs/notbranch/alsonottag HEAD &&
+ git -C server checkout --detach &&
+ git -C server branch -D main &&
+ git -C server symbolic-ref HEAD refs/heads/nonexistentbranch &&
+
+ git -c protocol.version=1 clone "file://$(pwd)/server" client
+'
+
# Test protocol v1 with 'ssh://' transport
#
test_expect_success 'setup ssh wrapper' '
setup_negotiate_only "$SERVER" "$URI" &&
cat >err.expect <<-\EOF &&
- fatal: --negotiate-only needs one or more --negotiate-tip=*
+ fatal: --negotiate-only needs one or more --negotiation-tip=*
EOF
test_must_fail git -c protocol.version=2 -C client fetch \
test_cmp err.expect err.actual
'
+test_expect_success 'usage: --negotiate-only with --recurse-submodules' '
+ cat >err.expect <<-\EOF &&
+ fatal: options '\''--negotiate-only'\'' and '\''--recurse-submodules'\'' cannot be used together
+ EOF
+
+ test_must_fail git -c protocol.version=2 -C client fetch \
+ --negotiate-only \
+ --recurse-submodules \
+ origin 2>err.actual &&
+ test_cmp err.expect err.actual
+'
+
test_expect_success 'file:// --negotiate-only' '
SERVER="server" &&
URI="file://$(pwd)/server" &&
test_expect_success '--left-right' '
git rev-list --left-right B...C > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success '--cherry-pick bar does not come up empty' '
git rev-list --left-right --cherry-pick B...C -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success 'bar does not come up empty' '
git rev-list --left-right B...C -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success '--cherry-pick bar does not come up empty (II)' '
git rev-list --left-right --cherry-pick F...E -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success 'name-rev multiple --refs combine inclusive' '
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
- git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" \
<actual >actual.named &&
test_cmp expect actual.named
'
test_expect_success 'name-rev --refs excludes non-matched patterns' '
git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
- git name-rev --stdin --name-only --refs="*tags/F" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/F" \
<actual >actual.named &&
test_cmp expect actual.named
'
test_expect_success 'name-rev --exclude excludes matched patterns' '
git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
- git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" --exclude="*E" \
<actual >actual.named &&
test_cmp expect actual.named
'
test_expect_success 'name-rev --no-refs clears the refs list' '
git rev-list --left-right --cherry-pick F...E -- bar >expect &&
- git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
<expect >actual &&
test_cmp expect actual
'
test_expect_success '--cherry-mark' '
git rev-list --cherry-mark F...E -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success '--cherry-mark --left-right' '
git rev-list --cherry-mark --left-right F...E -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success '--cherry-pick --right-only' '
git rev-list --cherry-pick --right-only F...E -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success '--cherry-pick --left-only' '
git rev-list --cherry-pick --left-only E...F -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
test_expect_success '--cherry' '
git rev-list --cherry F...E -- bar > actual &&
- git name-rev --stdin --name-only --refs="*tags/*" \
+ git name-rev --annotate-stdin --name-only --refs="*tags/*" \
< actual > actual.named &&
test_cmp expect actual.named
'
}
unnote () {
- git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g"
+ git name-rev --tags --annotate-stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g"
}
#
-# Create a test repo with interesting commit graph:
+# Create a test repo with an interesting commit graph:
#
-# A--B----------G--H--I--K--L
-# \ \ / /
-# \ \ / /
-# C------E---F J
-# \_/
+# A-----B-----G--H--I--K--L
+# \ \ / /
+# \ \ / /
+# C--D--E--F J
#
# The commits are laid out from left-to-right starting with
# the root commit A and terminating at the tip commit L.
check_result 'H' --first-parent -- another-file
check_result 'H' --first-parent --topo-order -- another-file
+check_result 'L K I H G B A' --first-parent L
+check_result 'F E D C' --exclude-first-parent-only F ^L
+check_result '' F ^L
+check_result 'L K I H G J' L ^F
+check_result 'L K I H G B J' --exclude-first-parent-only L ^F
+check_result 'L K I H G B' --exclude-first-parent-only --first-parent L ^F
+
check_result 'E C B A' --full-history E -- lost
test_expect_success 'full history simplification without parent' '
printf "%s\n" E C B A >expect &&
git bisect reset
'
+test_expect_success 'bisect run accepts exit code 126 as bad' '
+ test_when_finished "git bisect reset" &&
+ write_script test_script.sh <<-\EOF &&
+ ! grep Another hello || exit 126 >/dev/null
+ EOF
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ git bisect run ./test_script.sh >my_bisect_log.txt &&
+ grep "$HASH3 is the first bad commit" my_bisect_log.txt
+'
+
+test_expect_success POSIXPERM 'bisect run fails with non-executable test script' '
+ test_when_finished "git bisect reset" &&
+ >not-executable.sh &&
+ chmod -x not-executable.sh &&
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ test_must_fail git bisect run ./not-executable.sh >my_bisect_log.txt &&
+ ! grep "is the first bad commit" my_bisect_log.txt
+'
+
+test_expect_success 'bisect run accepts exit code 127 as bad' '
+ test_when_finished "git bisect reset" &&
+ write_script test_script.sh <<-\EOF &&
+ ! grep Another hello || exit 127 >/dev/null
+ EOF
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ git bisect run ./test_script.sh >my_bisect_log.txt &&
+ grep "$HASH3 is the first bad commit" my_bisect_log.txt
+'
+
+test_expect_success 'bisect run fails with missing test script' '
+ test_when_finished "git bisect reset" &&
+ rm -f does-not-exist.sh &&
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ test_must_fail git bisect run ./does-not-exist.sh >my_bisect_log.txt &&
+ ! grep "is the first bad commit" my_bisect_log.txt
+'
+
# $HASH1 is good, $HASH5 is bad, we skip $HASH3
# but $HASH4 is good,
# so we should find $HASH5 as the first bad commit
}
unnote () {
- git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g"
+ git name-rev --tags --annotate-stdin | \
+ sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g"
}
test_expect_success setup '
test_cmp expect actual
'
-test_expect_success 'name-rev --stdin' '
+test_expect_success 'name-rev --annotate-stdin' '
>expect.unsorted &&
for rev in $(git rev-list --all)
do
echo "$rev ($name)" >>expect.unsorted || return 1
done &&
sort <expect.unsorted >expect &&
- git rev-list --all | git name-rev --stdin >actual.unsorted &&
+ git rev-list --all | git name-rev --annotate-stdin >actual.unsorted &&
sort <actual.unsorted >actual &&
test_cmp expect actual
'
+test_expect_success 'name-rev --stdin deprecated' "
+ git rev-list --all | git name-rev --stdin 2>actual &&
+ grep -E 'warning: --stdin is deprecated' actual
+"
+
test_expect_success 'describe --contains with the exact tags' '
echo "A^0" >expect &&
tag_object=$(git rev-parse refs/tags/A) &&
printf "\0\0" >binary-file &&
git add binary-file &&
git commit -m binary2 &&
- test_must_fail git merge F >merge.out 2>merge.err &&
- grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge F >merge_output
+ else
+ test_must_fail git merge F 2>merge_output
+ fi &&
+ grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge_output
'
test_expect_success 'mark rename/delete as unmerged' '
printf "two\0" >bin.txt &&
git commit -am two &&
- test_must_fail git merge bin-main 2>stderr &&
- grep -i "warning.*cannot merge.*HEAD vs. bin-main" stderr
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge bin-main >output
+ else
+ test_must_fail git merge bin-main 2>output
+ fi &&
+ grep -i "warning.*cannot merge.*HEAD vs. bin-main" output
'
test_done
)
'
-test_expect_merge_algorithm failure success 'present-despite-SKIP_WORKTREE handled reasonably' '
+test_expect_success 'present-despite-SKIP_WORKTREE handled reasonably' '
test_setup_numerals in_the_way &&
(
cd numerals_in_the_way &&
test_must_fail git merge -s recursive B^0 &&
- git ls-files -t >index_files &&
- test_cmp expected-index index_files &&
+ test_path_is_missing .git/MERGE_HEAD &&
- test_path_is_file README &&
test_path_is_file numerals &&
- test_cmp expected-merge numerals &&
-
- # There should still be a file with "foobar" in it
- grep foobar * &&
-
- # 5 other files:
- # * expected-merge
- # * expected-index
- # * index_files
- # * others
- # * whatever name was given to the numerals file that had
- # "foobar" in it
- git ls-files -o >others &&
- test_line_count = 5 others
+ # numerals should still have "foobar" in it
+ echo foobar >expect &&
+ test_cmp expect numerals
)
'
)
'
+#
+# The following testcase just creates two simple renames (slightly modified
+# on both sides but without conflicting changes), and a directory full of
+# files that are otherwise uninteresting. The setup is as follows:
+#
+# base: unrelated/<BUNCH OF FILES>
+# numbers
+# values
+# upstream: modify: numbers
+# modify: values
+# topic: add: unrelated/foo
+# modify: numbers
+# modify: values
+# rename: numbers -> sequence
+# rename: values -> progression
+#
+# This is a trivial rename case, but we're curious what happens with a very
+# low renameLimit interacting with the restart optimization trying to notice
+# that unrelated/ looks like a trivial merge candidate.
+#
+test_expect_success 'avoid assuming we detected renames' '
+ git init redo-weirdness &&
+ (
+ cd redo-weirdness &&
+
+ mkdir unrelated &&
+ for i in $(test_seq 1 10)
+ do
+ >unrelated/$i
+ done &&
+ test_seq 2 10 >numbers &&
+ test_seq 12 20 >values &&
+ git add numbers values unrelated/ &&
+ git commit -m orig &&
+
+ git branch upstream &&
+ git branch topic &&
+
+ git switch upstream &&
+ test_seq 1 10 >numbers &&
+ test_seq 11 20 >values &&
+ git add numbers &&
+ git commit -m "Some tweaks" &&
+
+ git switch topic &&
+
+ >unrelated/foo &&
+ test_seq 2 12 >numbers &&
+ test_seq 12 22 >values &&
+ git add numbers values unrelated/ &&
+ git mv numbers sequence &&
+ git mv values progression &&
+ git commit -m A &&
+
+ #
+ # Actual testing
+ #
+
+ git switch --detach topic^0 &&
+
+ test_must_fail git -c merge.renameLimit=1 rebase upstream &&
+
+ git ls-files -u >actual &&
+ ! test_file_is_empty actual
+ )
+'
+
test_done
# Put a file in the working directory in the way
echo in the way >modified &&
- git stash apply &&
+ test_must_fail git stash apply 2>error&&
- # Ensure stash vivifies modifies paths...
- cat >expect <<-EOF &&
- H addme
- H modified
- H removeme
- H subdir/A
- S untouched
- EOF
- git ls-files -t >actual &&
- test_cmp expect actual &&
+ grep "changes.*would be overwritten by merge" error &&
- # ...and that the paths show up in status as changed...
- cat >expect <<-EOF &&
- A addme
- M modified
- D removeme
- M subdir/A
- ?? actual
- ?? expect
- ?? modified.stash.XXXXXX
- EOF
- git status --porcelain | \
- sed -e s/stash......./stash.XXXXXX/ >actual &&
- test_cmp expect actual &&
+ echo in the way >expect &&
+ test_cmp expect modified &&
+ git diff --quiet HEAD ":!modified" &&
# ...and that working directory reflects the files correctly
- test_path_is_file addme &&
+ test_path_is_missing addme &&
test_path_is_file modified &&
test_path_is_missing removeme &&
test_path_is_file subdir/A &&
- test_path_is_missing untouched &&
-
- # ...including that we have the expected "modified" file...
- cat >expect <<-EOF &&
- modified
- tweaked
- EOF
- test_cmp expect modified &&
-
- # ...and that the other "modified" file is still present...
- echo in the way >expect &&
- test_cmp expect modified.stash.*
+ test_path_is_missing untouched
)
'
'
test_expect_success '--fixup=reword: -F give error message' '
- echo "fatal: Only one of -c/-C/-F/--fixup can be used." >expect &&
+ echo "fatal: options '\''-F'\'' and '\''--fixup'\'' cannot be used together" >expect &&
test_must_fail git commit --fixup=reword:HEAD~ -F msg 2>actual &&
test_cmp expect actual
'
'
test_expect_success '--no-optional-locks prevents index update' '
- test-tool chmtime =1234567890 .git/index &&
+ test_set_magic_mtime .git/index &&
git --no-optional-locks status &&
- test-tool chmtime --get .git/index >out &&
- grep ^1234567890 out &&
+ test_is_magic_mtime .git/index &&
git status &&
- test-tool chmtime --get .git/index >out &&
- ! grep ^1234567890 out
+ ! test_is_magic_mtime .git/index
+'
+
+test_expect_success 'racy timestamps will be fixed for clean worktree' '
+ echo content >racy-dirty &&
+ echo content >racy-racy &&
+ git add racy* &&
+ git commit -m "racy test files" &&
+ # let status rewrite the index, if necessary; after that we expect
+ # no more index writes unless caused by racy timestamps; note that
+ # timestamps may already be racy now (depending on previous tests)
+ git status &&
+ test_set_magic_mtime .git/index &&
+ git status &&
+ ! test_is_magic_mtime .git/index
+'
+
+test_expect_success 'racy timestamps will be fixed for dirty worktree' '
+ echo content2 >racy-dirty &&
+ git status &&
+ test_set_magic_mtime .git/index &&
+ git status &&
+ ! test_is_magic_mtime .git/index
'
test_done
cd dot-git &&
mkdir -p .git/hooks &&
: >tracked &&
+ test-tool chmtime =-60 tracked &&
: >modified &&
+ test-tool chmtime =-60 modified &&
mkdir dir1 &&
: >dir1/tracked &&
+ test-tool chmtime =-60 dir1/tracked &&
: >dir1/modified &&
+ test-tool chmtime =-60 dir1/modified &&
mkdir dir2 &&
: >dir2/tracked &&
+ test-tool chmtime =-60 dir2/tracked &&
: >dir2/modified &&
+ test-tool chmtime =-60 dir2/modified &&
write_integration_script &&
git config core.fsmonitor .git/hooks/fsmonitor-test &&
git update-index --untracked-cache &&
git update-index --fsmonitor &&
+ git status &&
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-before" \
git status &&
test-tool dump-untracked-cache >../before
checksum=$(midx_checksum $objdir) &&
test_path_is_file $midx &&
test_path_is_file $midx-$checksum.bitmap &&
- test_path_is_file $midx-$checksum.rev &&
test_commit repack-3 &&
GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
test_path_is_file $midx &&
test_path_is_missing $midx-$checksum.bitmap &&
- test_path_is_missing $midx-$checksum.rev &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
test_commit repack-4 &&
GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb &&
test_line_count = 1 before &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
# instead of constructing the snapshot ourselves (c.f., the test
test_invalid_grep_expression --and -e A
+test_pattern_type () {
+ H=$1 &&
+ HC=$2 &&
+ L=$3 &&
+ type=$4 &&
+ shift 4 &&
+
+ expected_str= &&
+ case "$type" in
+ BRE)
+ expected_str="${HC}ab:a+bc"
+ ;;
+ ERE)
+ expected_str="${HC}ab:abc"
+ ;;
+ FIX)
+ expected_str="${HC}ab:a+b*c"
+ ;;
+ *)
+ BUG "unknown pattern type '$type'"
+ ;;
+ esac &&
+ config_str="$@" &&
+
+ test_expect_success "grep $L with '$config_str' interpreted as $type" '
+ echo $expected_str >expected &&
+ git $config_str grep "a+b*c" $H ab >actual &&
+ test_cmp expected actual
+ '
+}
+
for H in HEAD ''
do
case "$H" in
git grep --no-recursive -n -e vvv $H -- t . >actual &&
test_cmp expected actual
'
- test_expect_success "grep $L with grep.extendedRegexp=false" '
- echo "${HC}ab:a+bc" >expected &&
- git -c grep.extendedRegexp=false grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
- test_expect_success "grep $L with grep.extendedRegexp=true" '
- echo "${HC}ab:abc" >expected &&
- git -c grep.extendedRegexp=true grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
- test_expect_success "grep $L with grep.patterntype=basic" '
- echo "${HC}ab:a+bc" >expected &&
- git -c grep.patterntype=basic grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.patterntype=extended" '
- echo "${HC}ab:abc" >expected &&
- git -c grep.patterntype=extended grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.patterntype=fixed" '
- echo "${HC}ab:a+b*c" >expected &&
- git -c grep.patterntype=fixed grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
+ test_pattern_type "$H" "$HC" "$L" BRE -c grep.extendedRegexp=false
+ test_pattern_type "$H" "$HC" "$L" ERE -c grep.extendedRegexp=true
+ test_pattern_type "$H" "$HC" "$L" BRE -c grep.patternType=basic
+ test_pattern_type "$H" "$HC" "$L" ERE -c grep.patternType=extended
+ test_pattern_type "$H" "$HC" "$L" FIX -c grep.patternType=fixed
test_expect_success PCRE "grep $L with grep.patterntype=perl" '
echo "${HC}ab:a+b*c" >expected &&
test_must_fail git -c grep.patterntype=perl grep "foo.*bar"
'
- test_expect_success "grep $L with grep.patternType=default and grep.extendedRegexp=true" '
- echo "${HC}ab:abc" >expected &&
- git \
- -c grep.patternType=default \
- -c grep.extendedRegexp=true \
- grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.extendedRegexp=true and grep.patternType=default" '
- echo "${HC}ab:abc" >expected &&
- git \
- -c grep.extendedRegexp=true \
- -c grep.patternType=default \
- grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.patternType=extended and grep.extendedRegexp=false" '
- echo "${HC}ab:abc" >expected &&
- git \
- -c grep.patternType=extended \
- -c grep.extendedRegexp=false \
- grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.patternType=basic and grep.extendedRegexp=true" '
- echo "${HC}ab:a+bc" >expected &&
- git \
- -c grep.patternType=basic \
- -c grep.extendedRegexp=true \
- grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.extendedRegexp=false and grep.patternType=extended" '
- echo "${HC}ab:abc" >expected &&
- git \
- -c grep.extendedRegexp=false \
- -c grep.patternType=extended \
- grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
-
- test_expect_success "grep $L with grep.extendedRegexp=true and grep.patternType=basic" '
- echo "${HC}ab:a+bc" >expected &&
- git \
- -c grep.extendedRegexp=true \
- -c grep.patternType=basic \
- grep "a+b*c" $H ab >actual &&
- test_cmp expected actual
- '
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.patternType=default \
+ -c grep.extendedRegexp=true
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=default
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.patternType=extended \
+ -c grep.extendedRegexp=false
+ test_pattern_type "$H" "$HC" "$L" BRE \
+ -c grep.patternType=basic \
+ -c grep.extendedRegexp=true
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.extendedRegexp=false \
+ -c grep.patternType=extended
+ test_pattern_type "$H" "$HC" "$L" BRE \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=basic
+
+ # grep.extendedRegexp is last-one-wins
+ test_pattern_type "$H" "$HC" "$L" BRE \
+ -c grep.extendedRegexp=true \
+ -c grep.extendedRegexp=false
+
+ # grep.patternType=basic pays no attention to grep.extendedRegexp
+ test_pattern_type "$H" "$HC" "$L" BRE \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=basic \
+ -c grep.extendedRegexp=false
+
+ # grep.patternType=extended pays no attention to grep.extendedRegexp
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=extended \
+ -c grep.extendedRegexp=false
+
+ # grep.extendedRegexp is used with a last-one-wins grep.patternType=default
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.patternType=fixed \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=default
+
+ # grep.extendedRegexp is used with earlier grep.patternType=default
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.extendedRegexp=false \
+ -c grep.patternType=default \
+ -c grep.extendedRegexp=true
+
+ # grep.extendedRegexp is used with a last-one-loses grep.patternType=default
+ test_pattern_type "$H" "$HC" "$L" ERE \
+ -c grep.extendedRegexp=false \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=default
+
+ # grep.extendedRegexp and grep.patternType are both last-one-wins independently
+ test_pattern_type "$H" "$HC" "$L" BRE \
+ -c grep.patternType=default \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=basic
+
+ # grep.patternType=extended and grep.patternType=default
+ test_pattern_type "$H" "$HC" "$L" BRE \
+ -c grep.patternType=extended \
+ -c grep.patternType=default
+
+ # grep.patternType=[extended -> default -> fixed] (BRE)" '
+ test_pattern_type "$H" "$HC" "$L" FIX \
+ -c grep.patternType=extended \
+ -c grep.patternType=default \
+ -c grep.patternType=fixed
test_expect_success "grep --count $L" '
echo ${HC}ab:3 >expected &&
test_path_is_file "$sub_textconv_cache"
'
+test_expect_success 'grep partially-cloned submodule' '
+ # Set up clean superproject and submodule for partial cloning.
+ git init super &&
+ git init super/sub &&
+ (
+ cd super &&
+ test_commit --no-tag "Add file in superproject" \
+ super-file "Some content for super-file" &&
+ test_commit -C sub --no-tag "Add file in submodule" \
+ sub-file "Some content for sub-file" &&
+ git submodule add ./sub &&
+ git commit -m "Add other as submodule sub" &&
+ test_tick &&
+ test_commit -C sub --no-tag --append "Update file in submodule" \
+ sub-file "Some more content for sub-file" &&
+ git add sub &&
+ git commit -m "Update submodule" &&
+ test_tick &&
+ git config --local uploadpack.allowfilter 1 &&
+ git config --local uploadpack.allowanysha1inwant 1 &&
+ git -C sub config --local uploadpack.allowfilter 1 &&
+ git -C sub config --local uploadpack.allowanysha1inwant 1
+ ) &&
+ # Clone the superproject & submodule, then make sure we can lazy-fetch submodule objects.
+ git clone --filter=blob:none --also-filter-submodules \
+ --recurse-submodules "file://$(pwd)/super" partial &&
+ (
+ cd partial &&
+ cat >expect <<-\EOF &&
+ HEAD^:sub/sub-file:Some content for sub-file
+ HEAD^:super-file:Some content for super-file
+ EOF
+
+ GIT_TRACE2_EVENT="$(pwd)/trace2.log" git grep -e content \
+ --recurse-submodules HEAD^ >actual &&
+ test_cmp expect actual &&
+ # Verify that we actually fetched data from the promisor remote:
+ grep \"category\":\"promisor\",\"key\":\"fetch_count\",\"value\":\"1\" trace2.log
+ )
+'
+
test_done
# The test below covers a special case: the sparsity patterns exclude '/b' and
# sparse checkout is enabled, but the path exists in the working tree (e.g.
-# manually created after `git sparse-checkout init`). git grep should skip it.
+# manually created after `git sparse-checkout init`). Although b is marked
+# as SKIP_WORKTREE, git grep should notice it IS present in the worktree and
+# report it.
test_expect_success 'working tree grep honors sparse checkout' '
cat >expect <<-EOF &&
a:text
+ b:new-text
EOF
test_when_finished "rm -f b" &&
echo "new-text" >b &&
'
# Note that sub2/ is present in the worktree but it is excluded by the sparsity
-# patterns, so grep should not recurse into it.
+# patterns. We also explicitly mark it as SKIP_WORKTREE in case it got cleared
+# by previous git commands. Thus sub2 starts as SKIP_WORKTREE but since it is
+# present in the working tree, grep should recurse into it.
test_expect_success 'grep --recurse-submodules honors sparse checkout in submodule' '
cat >expect <<-EOF &&
a:text
sub/B/b:text
+ sub2/a:text
EOF
+ git update-index --skip-worktree sub2 &&
git grep --recurse-submodules "text" >actual &&
test_cmp expect actual
'
GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
'
+test_expect_success 'usage: <bad rev>' '
+ cat >expect <<-\EOF &&
+ fatal: Not a valid object name HEAD2
+ EOF
+ test_must_fail git cat-file --textconv HEAD2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'usage: <bad rev>:<bad path>' '
+ cat >expect <<-\EOF &&
+ fatal: invalid object name '\''HEAD2'\''.
+ EOF
+ test_must_fail git cat-file --textconv HEAD2:two.bin 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'usage: <rev>:<bad path>' '
+ cat >expect <<-\EOF &&
+ fatal: path '\''two.bin'\'' does not exist in '\''HEAD'\''
+ EOF
+ test_must_fail git cat-file --textconv HEAD:two.bin 2>actual &&
+ test_cmp expect actual
+'
+
+
+test_expect_success 'usage: <rev> with no <path>' '
+ cat >expect <<-\EOF &&
+ fatal: <object>:<path> required, only <object> '\''HEAD'\'' given
+ EOF
+ test_must_fail git cat-file --textconv HEAD 2>actual &&
+ test_cmp expect actual
+'
+
+
+test_expect_success 'usage: <bad rev>:<good (in HEAD) path>' '
+ cat >expect <<-\EOF &&
+ fatal: invalid object name '\''HEAD2'\''.
+ EOF
+ test_must_fail git cat-file --textconv HEAD2:one.bin 2>actual &&
+ test_cmp expect actual
+'
+
cat >expected <<EOF
bin: test version 2
EOF
test_path_is_file my-hooks.ran &&
cat >expect <<-EOF &&
fatal: longline.patch: rejected by sendemail-validate hook
- fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+ fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
warning: no patches were sent
EOF
test_cmp expect actual
test_path_is_file my-hooks.ran &&
cat >expect <<-EOF &&
fatal: longline.patch: rejected by sendemail-validate hook
- fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+ fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
warning: no patches were sent
EOF
test_cmp expect actual
#!/bin/sh
test_description='git svn rmdir'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
test_expect_success 'initialize repo' '
test_description='git svn respects rewriteRoot during rebuild'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
mkdir import
test_description='git svn partial-rebuild tests'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
test_expect_success 'initialize svnrepo' '
test_description='git svn branch for subproject clones'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
test_expect_success 'initialize svnrepo' '
EOF
'
+test_expect_success 'setup sparse-checkout tests' '
+ # set up sparse-checkout repo
+ git init sparse-checkout &&
+ (
+ cd sparse-checkout &&
+ mkdir -p folder1/0/1 folder2/0 folder3 &&
+ touch folder1/0/1/t.txt &&
+ touch folder2/0/t.txt &&
+ touch folder3/t.txt &&
+ git add . &&
+ git commit -am "Initial commit"
+ )
+'
+
+test_expect_success 'sparse-checkout completes subcommands' '
+ test_completion "git sparse-checkout " <<-\EOF
+ list Z
+ init Z
+ set Z
+ add Z
+ reapply Z
+ disable Z
+ EOF
+'
+
+test_expect_success 'cone mode sparse-checkout completes directory names' '
+ # initialize sparse-checkout definitions
+ git -C sparse-checkout sparse-checkout set --cone folder1/0 folder3 &&
+
+ # test tab completion
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set f" <<-\EOF
+ folder1/
+ folder2/
+ folder3/
+ EOF
+ ) &&
+
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set folder1/" <<-\EOF
+ folder1/0/
+ EOF
+ ) &&
+
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set folder1/0/" <<-\EOF
+ folder1/0/1/
+ EOF
+ ) &&
+
+ (
+ cd sparse-checkout/folder1 &&
+ test_completion "git sparse-checkout add 0" <<-\EOF
+ 0/
+ EOF
+ )
+'
+
+test_expect_success 'cone mode sparse-checkout completes directory names with spaces and accents' '
+ # reset sparse-checkout
+ git -C sparse-checkout sparse-checkout disable &&
+ (
+ cd sparse-checkout &&
+ mkdir "directory with spaces" &&
+ mkdir "directory-with-áccent" &&
+ >"directory with spaces/randomfile" &&
+ >"directory-with-áccent/randomfile" &&
+ git add . &&
+ git commit -m "Add directory with spaces and directory with accent" &&
+ git sparse-checkout set --cone "directory with spaces" \
+ "directory-with-áccent" &&
+ test_completion "git sparse-checkout add dir" <<-\EOF &&
+ directory with spaces/
+ directory-with-áccent/
+ EOF
+ rm -rf "directory with spaces" &&
+ rm -rf "directory-with-áccent" &&
+ git add . &&
+ git commit -m "Remove directory with spaces and directory with accent"
+ )
+'
+
+# use FUNNYNAMES to avoid running on Windows, which doesn't permit backslashes or tabs in paths
+test_expect_success FUNNYNAMES 'cone mode sparse-checkout completes directory names with backslashes and tabs' '
+ # reset sparse-checkout
+ git -C sparse-checkout sparse-checkout disable &&
+ (
+ cd sparse-checkout &&
+ mkdir "directory\with\backslashes" &&
+ mkdir "$(printf "directory\twith\ttabs")" &&
+ >"directory\with\backslashes/randomfile" &&
+ >"$(printf "directory\twith\ttabs")/randomfile" &&
+ git add . &&
+ git commit -m "Add directory with backslashes and directory with tabs" &&
+ git sparse-checkout set --cone "directory\with\backslashes" \
+ "$(printf "directory\twith\ttabs")" &&
+ test_completion "git sparse-checkout add dir" <<-\EOF &&
+ directory\with\backslashes/
+ directory with tabs/
+ EOF
+ rm -rf "directory\with\backslashes" &&
+ rm -rf "$(printf "directory\twith\ttabs")" &&
+ git add . &&
+ git commit -m "Remove directory with backslashes and directory with tabs"
+ )
+'
+
+test_expect_success 'non-cone mode sparse-checkout uses bash completion' '
+ # reset sparse-checkout repo to non-cone mode
+ git -C sparse-checkout sparse-checkout disable &&
+ git -C sparse-checkout sparse-checkout set --no-cone &&
+
+ (
+ cd sparse-checkout &&
+ # expected to be empty since we have not configured
+ # custom completion for non-cone mode
+ test_completion "git sparse-checkout set f" <<-\EOF
+
+ EOF
+ )
+'
+
+test_expect_success 'git sparse-checkout set --cone completes directory names' '
+ git -C sparse-checkout sparse-checkout disable &&
+
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set --cone f" <<-\EOF
+ folder1/
+ folder2/
+ folder3/
+ EOF
+ )
+'
+
test_expect_success 'git switch - with -d, complete all references' '
test_completion "git switch -d " <<-\EOF
HEAD Z
'
test_expect_success 'sourcing the completion script clears cached commands' '
- __git_compute_all_commands &&
- verbose test -n "$__git_all_commands" &&
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- verbose test -z "$__git_all_commands"
+ (
+ __git_compute_all_commands &&
+ verbose test -n "$__git_all_commands" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_all_commands"
+ )
'
test_expect_success 'sourcing the completion script clears cached merge strategies' '
- __git_compute_merge_strategies &&
- verbose test -n "$__git_merge_strategies" &&
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- verbose test -z "$__git_merge_strategies"
+ (
+ __git_compute_merge_strategies &&
+ verbose test -n "$__git_merge_strategies" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_merge_strategies"
+ )
'
test_expect_success 'sourcing the completion script clears cached --options' '
- __gitcomp_builtin checkout &&
- verbose test -n "$__gitcomp_builtin_checkout" &&
- __gitcomp_builtin notes_edit &&
- verbose test -n "$__gitcomp_builtin_notes_edit" &&
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- verbose test -z "$__gitcomp_builtin_checkout" &&
- verbose test -z "$__gitcomp_builtin_notes_edit"
+ (
+ __gitcomp_builtin checkout &&
+ verbose test -n "$__gitcomp_builtin_checkout" &&
+ __gitcomp_builtin notes_edit &&
+ verbose test -n "$__gitcomp_builtin_notes_edit" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__gitcomp_builtin_checkout" &&
+ verbose test -z "$__gitcomp_builtin_notes_edit"
+ )
'
test_expect_success 'option aliases are not shown by default' '
'
test_expect_success 'option aliases are shown with GIT_COMPLETION_SHOW_ALL' '
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL &&
- test_completion "git clone --recurs" <<-\EOF
- --recurse-submodules Z
- --recursive Z
- EOF
+ (
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL &&
+ test_completion "git clone --recurs" <<-\EOF
+ --recurse-submodules Z
+ --recursive Z
+ EOF
+ )
+'
+
+test_expect_success 'plumbing commands are excluded without GIT_COMPLETION_SHOW_ALL_COMMANDS' '
+ (
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ sane_unset GIT_TESTING_PORCELAIN_COMMAND_LIST &&
+
+ # Just mainporcelain, not plumbing commands
+ run_completion "git c" &&
+ grep checkout out &&
+ ! grep cat-file out
+ )
+'
+
+test_expect_success 'all commands are shown with GIT_COMPLETION_SHOW_ALL_COMMANDS (also main non-builtin)' '
+ (
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ GIT_COMPLETION_SHOW_ALL_COMMANDS=1 &&
+ export GIT_COMPLETION_SHOW_ALL_COMMANDS &&
+ sane_unset GIT_TESTING_PORCELAIN_COMMAND_LIST &&
+
+ # Both mainporcelain and plumbing commands
+ run_completion "git c" &&
+ grep checkout out &&
+ grep cat-file out &&
+
+ # Check "gitk", a "main" command, but not a built-in + more plumbing
+ run_completion "git g" &&
+ grep gitk out &&
+ grep get-tar-commit-id out
+ )
'
test_expect_success '__git_complete' '
fi
}
+test_path_is_file_not_symlink () {
+ test "$#" -ne 1 && BUG "1 param"
+ test_path_is_file "$1" &&
+ if test -h "$1"
+ then
+ echo "$1 shouldn't be a symbolic link"
+ false
+ fi
+}
+
test_path_is_dir () {
test "$#" -ne 1 && BUG "1 param"
if ! test -d "$1"
fi
}
+test_path_is_dir_not_symlink () {
+ test "$#" -ne 1 && BUG "1 param"
+ test_path_is_dir "$1" &&
+ if test -h "$1"
+ then
+ echo "$1 shouldn't be a symbolic link"
+ false
+ fi
+}
+
test_path_exists () {
test "$#" -ne 1 && BUG "1 param"
if ! test -e "$1"
fi
}
+test_path_is_symlink () {
+ test "$#" -ne 1 && BUG "1 param"
+ if ! test -h "$1"
+ then
+ echo "Symbolic link $1 doesn't exist"
+ false
+ fi
+}
+
# Check if the directory exists and is empty as expected, barf otherwise.
test_dir_is_empty () {
test "$#" -ne 1 && BUG "1 param"
test_readlink () {
perl -le 'print readlink($_) for @ARGV' "$@"
}
+
+# Set mtime to a fixed "magic" timestamp in mid February 2009, before we
+# run an operation that may or may not touch the file. If the file was
+# touched, its timestamp will not accidentally have such an old timestamp,
+# as long as your filesystem clock is reasonably correct. To verify the
+# timestamp, follow up with test_is_magic_mtime.
+#
+# An optional increment to the magic timestamp may be specified as second
+# argument.
+test_set_magic_mtime () {
+ local inc=${2:-0} &&
+ local mtime=$((1234567890 + $inc)) &&
+ test-tool chmtime =$mtime "$1" &&
+ test_is_magic_mtime "$1" $inc
+}
+
+# Test whether the given file has the "magic" mtime set. This is meant to
+# be used in combination with test_set_magic_mtime.
+#
+# An optional increment to the magic timestamp may be specified as second
+# argument. Usually, this should be the same increment which was used for
+# the associated test_set_magic_mtime.
+test_is_magic_mtime () {
+ local inc=${2:-0} &&
+ local mtime=$((1234567890 + $inc)) &&
+ echo $mtime >.git/test-mtime-expect &&
+ test-tool chmtime --get "$1" >.git/test-mtime-actual &&
+ test_cmp .git/test-mtime-expect .git/test-mtime-actual
+ local ret=$?
+ rm -f .git/test-mtime-expect
+ rm -f .git/test-mtime-actual
+ return $ret
+}
# t/ subdirectory and are run in 'trash directory' subdirectory.
if test -z "$TEST_DIRECTORY"
then
- # We allow tests to override this, in case they want to run tests
- # outside of t/, e.g. for running tests on the test library
- # itself.
- TEST_DIRECTORY=$(pwd)
-else
# ensure that TEST_DIRECTORY is an absolute path so that it
# is valid even if the current working directory is changed
+ TEST_DIRECTORY=$(pwd)
+else
+ # The TEST_DIRECTORY will always be the path to the "t"
+ # directory in the git.git checkout. This is overridden by
+ # e.g. t/lib-subtest.sh, but only because its $(pwd) is
+ # different. Those tests still set "$TEST_DIRECTORY" to the
+ # same path.
+ #
+ # See use of "$GIT_BUILD_DIR" and "$TEST_DIRECTORY" below for
+ # hard assumptions about "$GIT_BUILD_DIR/t" existing and being
+ # the "$TEST_DIRECTORY", and e.g. "$TEST_DIRECTORY/helper"
+ # needing to exist.
TEST_DIRECTORY=$(cd "$TEST_DIRECTORY" && pwd) || exit 1
fi
if test -z "$TEST_OUTPUT_DIRECTORY"
# elsewhere
TEST_OUTPUT_DIRECTORY=$TEST_DIRECTORY
fi
-GIT_BUILD_DIR="$TEST_DIRECTORY"/..
+GIT_BUILD_DIR="${TEST_DIRECTORY%/t}"
+if test "$TEST_DIRECTORY" = "$GIT_BUILD_DIR"
+then
+ echo "PANIC: Running in a $TEST_DIRECTORY that doesn't end in '/t'?" >&2
+ exit 1
+fi
+
+# Prepend a string to a VAR using an arbitrary ":" delimiter, not
+# adding the delimiter if VAR or VALUE is empty. I.e. a generalized:
+#
+# VAR=$1${VAR:+${1:+$2}$VAR}
+#
+# Usage (using ":" as the $2 delimiter):
+#
+# prepend_var VAR : VALUE
+prepend_var () {
+ eval "$1=$3\${$1:+${3:+$2}\$$1}"
+}
+
+# If [AL]SAN is in effect we want to abort so that we notice
+# problems. The GIT_SAN_OPTIONS variable can be used to set common
+# defaults shared between [AL]SAN_OPTIONS.
+prepend_var GIT_SAN_OPTIONS : abort_on_error=1
+prepend_var GIT_SAN_OPTIONS : strip_path_prefix=\"$GIT_BUILD_DIR/\"
# If we were built with ASAN, it may complain about leaks
# of program-lifetime variables. Disable it by default to lower
# the noise level. This needs to happen at the start of the script,
# before we even do our "did we build git yet" check (since we don't
# want that one to complain to stderr).
-: ${ASAN_OPTIONS=detect_leaks=0:abort_on_error=1}
+prepend_var ASAN_OPTIONS : $GIT_SAN_OPTIONS
+prepend_var ASAN_OPTIONS : detect_leaks=0
export ASAN_OPTIONS
-# If LSAN is in effect we _do_ want leak checking, but we still
-# want to abort so that we notice the problems.
-: ${LSAN_OPTIONS=abort_on_error=1}
+prepend_var LSAN_OPTIONS : $GIT_SAN_OPTIONS
+prepend_var LSAN_OPTIONS : fast_unwind_on_malloc=0
export LSAN_OPTIONS
if test ! -f "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS
unset XDG_CACHE_HOME
unset XDG_CONFIG_HOME
unset GITPERLLIB
+unset GIT_TRACE2_PARENT_NAME
+unset GIT_TRACE2_PARENT_SID
TEST_AUTHOR_LOCALNAME=author
TEST_AUTHOR_DOMAIN=example.com
GIT_AUTHOR_EMAIL=${TEST_AUTHOR_LOCALNAME}@${TEST_AUTHOR_DOMAIN}
-# make and install sample templates
-
-ifndef V
- QUIET = @
-endif
+# Import tree-wide shared Makefile behavior and libraries
+include ../shared.mak
+# make and install sample templates
INSTALL ?= install
TAR ?= tar
RM ?= rm -f
raise(signo);
}
+void tmp_objdir_discard_objects(struct tmp_objdir *t)
+{
+ remove_dir_recursively(&t->path, REMOVE_DIR_KEEP_TOPLEVEL);
+}
+
/*
* These env_* functions are for setting up the child environment; the
* "replace" variant overrides the value of any existing variable with that
*/
int tmp_objdir_destroy(struct tmp_objdir *);
+/*
+ * Remove all objects from the temporary object directory, while leaving it
+ * around so more objects can be added.
+ */
+void tmp_objdir_discard_objects(struct tmp_objdir *);
+
/*
* Add the temporary object directory as an alternate object store in the
* current process.
gettimeofday(&tv, NULL);
secs = tv.tv_sec;
localtime_r(&secs, &tm);
- strbuf_addf(buf, "%02d:%02d:%02d.%06ld ", tm.tm_hour, tm.tm_min,
- tm.tm_sec, (long) tv.tv_usec);
-
-#ifdef HAVE_VARIADIC_MACROS
- /* print file:line */
- strbuf_addf(buf, "%s:%d ", file, line);
+ strbuf_addf(buf, "%02d:%02d:%02d.%06ld %s:%d", tm.tm_hour, tm.tm_min,
+ tm.tm_sec, (long) tv.tv_usec, file, line);
/* align trace output (column 40 catches most files names in git) */
while (buf->len < 40)
strbuf_addch(buf, ' ');
-#endif
return 1;
}
strbuf_release(&buf);
}
-#ifndef HAVE_VARIADIC_MACROS
-
-void trace_printf(const char *format, ...)
-{
- va_list ap;
- va_start(ap, format);
- trace_vprintf_fl(NULL, 0, &trace_default_key, format, ap);
- va_end(ap);
-}
-
-void trace_printf_key(struct trace_key *key, const char *format, ...)
-{
- va_list ap;
- va_start(ap, format);
- trace_vprintf_fl(NULL, 0, key, format, ap);
- va_end(ap);
-}
-
-void trace_argv_printf(const char **argv, const char *format, ...)
-{
- va_list ap;
- va_start(ap, format);
- trace_argv_vprintf_fl(NULL, 0, argv, format, ap);
- va_end(ap);
-}
-
-void trace_strbuf(struct trace_key *key, const struct strbuf *data)
-{
- trace_strbuf_fl(NULL, 0, key, data);
-}
-
-void trace_performance(uint64_t nanos, const char *format, ...)
-{
- va_list ap;
- va_start(ap, format);
- trace_performance_vprintf_fl(NULL, 0, nanos, format, ap);
- va_end(ap);
-}
-
-void trace_performance_since(uint64_t start, const char *format, ...)
-{
- va_list ap;
- va_start(ap, format);
- trace_performance_vprintf_fl(NULL, 0, getnanotime() - start,
- format, ap);
- va_end(ap);
-}
-
-void trace_performance_leave(const char *format, ...)
-{
- va_list ap;
- uint64_t since;
-
- if (perf_indent)
- perf_indent--;
-
- if (!format) /* Allow callers to leave without tracing anything */
- return;
-
- since = perf_start_times[perf_indent];
- va_start(ap, format);
- trace_performance_vprintf_fl(NULL, 0, getnanotime() - since,
- format, ap);
- va_end(ap);
-}
-
-#else
-
void trace_printf_key_fl(const char *file, int line, struct trace_key *key,
const char *format, ...)
{
va_end(ap);
}
-#endif /* HAVE_VARIADIC_MACROS */
-
-
static const char *quote_crnl(const char *path)
{
static struct strbuf new_path = STRBUF_INIT;
void trace_verbatim(struct trace_key *key, const void *buf, unsigned len);
uint64_t trace_performance_enter(void);
-#ifndef HAVE_VARIADIC_MACROS
-
-/**
- * Prints a formatted message, similar to printf.
- */
-__attribute__((format (printf, 1, 2)))
-void trace_printf(const char *format, ...);
-
-__attribute__((format (printf, 2, 3)))
-void trace_printf_key(struct trace_key *key, const char *format, ...);
-
-/**
- * Prints a formatted message, followed by a quoted list of arguments.
- */
-__attribute__((format (printf, 2, 3)))
-void trace_argv_printf(const char **argv, const char *format, ...);
-
-/**
- * Prints the strbuf, without additional formatting (i.e. doesn't
- * choke on `%` or even `\0`).
- */
-void trace_strbuf(struct trace_key *key, const struct strbuf *data);
-
-/**
- * Prints elapsed time (in nanoseconds) if GIT_TRACE_PERFORMANCE is enabled.
- *
- * Example:
- * ------------
- * uint64_t t = 0;
- * for (;;) {
- * // ignore
- * t -= getnanotime();
- * // code section to measure
- * t += getnanotime();
- * // ignore
- * }
- * trace_performance(t, "frotz");
- * ------------
- */
-__attribute__((format (printf, 2, 3)))
-void trace_performance(uint64_t nanos, const char *format, ...);
-
-/**
- * Prints elapsed time since 'start' if GIT_TRACE_PERFORMANCE is enabled.
- *
- * Example:
- * ------------
- * uint64_t start = getnanotime();
- * // code section to measure
- * trace_performance_since(start, "foobar");
- * ------------
- */
-__attribute__((format (printf, 2, 3)))
-void trace_performance_since(uint64_t start, const char *format, ...);
-
-__attribute__((format (printf, 1, 2)))
-void trace_performance_leave(const char *format, ...);
-
-#else
-
-/*
- * Macros to add file:line - see above for C-style declarations of how these
- * should be used.
- */
-
/*
* TRACE_CONTEXT may be set to __FUNCTION__ if the compiler supports it. The
* default is __FILE__, as it is consistent with assert(), and static function
# define TRACE_CONTEXT __FILE__
#endif
-/*
+/**
+ * Macros to add the file:line of the calling code, instead of that of
+ * the trace function itself.
+ *
* Note: with C99 variadic macros, __VA_ARGS__ must include the last fixed
* parameter ('format' in this case). Otherwise, a call without variable
* arguments will have a surplus ','. E.g.:
* comma, but this is non-standard.
*/
+/**
+ * trace_printf(), accepts "const char *format, ...".
+ *
+ * Prints a formatted message, similar to printf.
+ */
+#define trace_printf(...) trace_printf_key(&trace_default_key, __VA_ARGS__)
+
+/**
+ * trace_printf_key(), accepts "struct trace_key *key, const char *format, ...".
+ */
#define trace_printf_key(key, ...) \
do { \
if (trace_pass_fl(key)) \
__VA_ARGS__); \
} while (0)
-#define trace_printf(...) trace_printf_key(&trace_default_key, __VA_ARGS__)
-
+/**
+ * trace_argv_printf(), accepts "struct trace_key *key, const char *format, ...)".
+ *
+ * Prints a formatted message, followed by a quoted list of arguments.
+ */
#define trace_argv_printf(argv, ...) \
do { \
if (trace_pass_fl(&trace_default_key)) \
argv, __VA_ARGS__); \
} while (0)
+/**
+ * trace_strbuf(), accepts "struct trace_key *key, const struct strbuf *data".
+ *
+ * Prints the strbuf, without additional formatting (i.e. doesn't
+ * choke on `%` or even `\0`).
+ */
#define trace_strbuf(key, data) \
do { \
if (trace_pass_fl(key)) \
trace_strbuf_fl(TRACE_CONTEXT, __LINE__, key, data);\
} while (0)
+/**
+ * trace_performance(), accepts "uint64_t nanos, const char *format, ...".
+ *
+ * Prints elapsed time (in nanoseconds) if GIT_TRACE_PERFORMANCE is enabled.
+ *
+ * Example:
+ * ------------
+ * uint64_t t = 0;
+ * for (;;) {
+ * // ignore
+ * t -= getnanotime();
+ * // code section to measure
+ * t += getnanotime();
+ * // ignore
+ * }
+ * trace_performance(t, "frotz");
+ * ------------
+ */
#define trace_performance(nanos, ...) \
do { \
if (trace_pass_fl(&trace_perf_key)) \
__VA_ARGS__); \
} while (0)
+/**
+ * trace_performance_since(), accepts "uint64_t start, const char *format, ...".
+ *
+ * Prints elapsed time since 'start' if GIT_TRACE_PERFORMANCE is enabled.
+ *
+ * Example:
+ * ------------
+ * uint64_t start = getnanotime();
+ * // code section to measure
+ * trace_performance_since(start, "foobar");
+ * ------------
+ */
#define trace_performance_since(start, ...) \
do { \
if (trace_pass_fl(&trace_perf_key)) \
__VA_ARGS__); \
} while (0)
+/**
+ * trace_performance_leave(), accepts "const char *format, ...".
+ */
#define trace_performance_leave(...) \
do { \
if (trace_pass_fl(&trace_perf_key)) \
return key->fd || !key->initialized;
}
-#endif /* HAVE_VARIADIC_MACROS */
-
#endif /* TRACE_H */
va_end(ap);
}
-#ifndef HAVE_VARIADIC_MACROS
-void trace2_region_enter_printf(const char *category, const char *label,
- const struct repository *repo, const char *fmt,
- ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- trace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,
- ap);
- va_end(ap);
-}
-#endif
-
void trace2_region_leave_printf_va_fl(const char *file, int line,
const char *category, const char *label,
const struct repository *repo,
va_end(ap);
}
-#ifndef HAVE_VARIADIC_MACROS
-void trace2_region_leave_printf(const char *category, const char *label,
- const struct repository *repo, const char *fmt,
- ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- trace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,
- ap);
- va_end(ap);
-}
-#endif
-
void trace2_data_string_fl(const char *file, int line, const char *category,
const struct repository *repo, const char *key,
const char *value)
va_end(ap);
}
-#ifndef HAVE_VARIADIC_MACROS
-void trace2_printf(const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- trace2_printf_va_fl(NULL, 0, fmt, ap);
- va_end(ap);
-}
-#endif
-
const char *trace2_session_id(void)
{
return tr2_sid_get();
const struct repository *repo,
const char *fmt, ...);
-#ifdef HAVE_VARIADIC_MACROS
#define trace2_region_enter_printf(category, label, repo, ...) \
trace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \
(repo), __VA_ARGS__)
-#else
-/* clang-format off */
-__attribute__((format (region_enter_printf, 4, 5)))
-void trace2_region_enter_printf(const char *category, const char *label,
- const struct repository *repo, const char *fmt,
- ...);
-/* clang-format on */
-#endif
/**
* Emit a 'region_leave' event for <category>.<label> with optional
const struct repository *repo,
const char *fmt, ...);
-#ifdef HAVE_VARIADIC_MACROS
#define trace2_region_leave_printf(category, label, repo, ...) \
trace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \
(repo), __VA_ARGS__)
-#else
-/* clang-format off */
-__attribute__((format (region_leave_printf, 4, 5)))
-void trace2_region_leave_printf(const char *category, const char *label,
- const struct repository *repo, const char *fmt,
- ...);
-/* clang-format on */
-#endif
/**
* Emit a key-value pair 'data' event of the form <category>.<key> = <value>.
void trace2_printf_fl(const char *file, int line, const char *fmt, ...);
-#ifdef HAVE_VARIADIC_MACROS
#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)
-#else
-/* clang-format off */
-__attribute__((format (printf, 1, 2)))
-void trace2_printf(const char *fmt, ...);
-/* clang-format on */
-#endif
/*
* Optional platform-specific code to dump information about the
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
-static struct tr2_dst tr2dst_event = { TR2_SYSENV_EVENT, 0, 0, 0, 0 };
+static struct tr2_dst tr2dst_event = {
+ .sysenv_var = TR2_SYSENV_EVENT,
+};
/*
* The version number of the JSON data generated by the EVENT target in this
}
struct tr2_tgt tr2_tgt_event = {
- &tr2dst_event,
-
- fn_init,
- fn_term,
-
- fn_version_fl,
- fn_start_fl,
- fn_exit_fl,
- fn_signal,
- fn_atexit,
- fn_error_va_fl,
- fn_command_path_fl,
- fn_command_ancestry_fl,
- fn_command_name_fl,
- fn_command_mode_fl,
- fn_alias_fl,
- fn_child_start_fl,
- fn_child_exit_fl,
- fn_child_ready_fl,
- fn_thread_start_fl,
- fn_thread_exit_fl,
- fn_exec_fl,
- fn_exec_result_fl,
- fn_param_fl,
- fn_repo_fl,
- fn_region_enter_printf_va_fl,
- fn_region_leave_printf_va_fl,
- fn_data_fl,
- fn_data_json_fl,
- NULL, /* printf */
+ .pdst = &tr2dst_event,
+
+ .pfn_init = fn_init,
+ .pfn_term = fn_term,
+
+ .pfn_version_fl = fn_version_fl,
+ .pfn_start_fl = fn_start_fl,
+ .pfn_exit_fl = fn_exit_fl,
+ .pfn_signal = fn_signal,
+ .pfn_atexit = fn_atexit,
+ .pfn_error_va_fl = fn_error_va_fl,
+ .pfn_command_path_fl = fn_command_path_fl,
+ .pfn_command_ancestry_fl = fn_command_ancestry_fl,
+ .pfn_command_name_fl = fn_command_name_fl,
+ .pfn_command_mode_fl = fn_command_mode_fl,
+ .pfn_alias_fl = fn_alias_fl,
+ .pfn_child_start_fl = fn_child_start_fl,
+ .pfn_child_exit_fl = fn_child_exit_fl,
+ .pfn_child_ready_fl = fn_child_ready_fl,
+ .pfn_thread_start_fl = fn_thread_start_fl,
+ .pfn_thread_exit_fl = fn_thread_exit_fl,
+ .pfn_exec_fl = fn_exec_fl,
+ .pfn_exec_result_fl = fn_exec_result_fl,
+ .pfn_param_fl = fn_param_fl,
+ .pfn_repo_fl = fn_repo_fl,
+ .pfn_region_enter_printf_va_fl = fn_region_enter_printf_va_fl,
+ .pfn_region_leave_printf_va_fl = fn_region_leave_printf_va_fl,
+ .pfn_data_fl = fn_data_fl,
+ .pfn_data_json_fl = fn_data_json_fl,
+ .pfn_printf_va_fl = NULL,
};
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
-static struct tr2_dst tr2dst_normal = { TR2_SYSENV_NORMAL, 0, 0, 0, 0 };
+static struct tr2_dst tr2dst_normal = {
+ .sysenv_var = TR2_SYSENV_NORMAL,
+};
/*
* Use the TR2_SYSENV_NORMAL_BRIEF setting to omit the "<time> <file>:<line>"
}
struct tr2_tgt tr2_tgt_normal = {
- &tr2dst_normal,
-
- fn_init,
- fn_term,
-
- fn_version_fl,
- fn_start_fl,
- fn_exit_fl,
- fn_signal,
- fn_atexit,
- fn_error_va_fl,
- fn_command_path_fl,
- fn_command_ancestry_fl,
- fn_command_name_fl,
- fn_command_mode_fl,
- fn_alias_fl,
- fn_child_start_fl,
- fn_child_exit_fl,
- fn_child_ready_fl,
- NULL, /* thread_start */
- NULL, /* thread_exit */
- fn_exec_fl,
- fn_exec_result_fl,
- fn_param_fl,
- fn_repo_fl,
- NULL, /* region_enter */
- NULL, /* region_leave */
- NULL, /* data */
- NULL, /* data_json */
- fn_printf_va_fl,
+ .pdst = &tr2dst_normal,
+
+ .pfn_init = fn_init,
+ .pfn_term = fn_term,
+
+ .pfn_version_fl = fn_version_fl,
+ .pfn_start_fl = fn_start_fl,
+ .pfn_exit_fl = fn_exit_fl,
+ .pfn_signal = fn_signal,
+ .pfn_atexit = fn_atexit,
+ .pfn_error_va_fl = fn_error_va_fl,
+ .pfn_command_path_fl = fn_command_path_fl,
+ .pfn_command_ancestry_fl = fn_command_ancestry_fl,
+ .pfn_command_name_fl = fn_command_name_fl,
+ .pfn_command_mode_fl = fn_command_mode_fl,
+ .pfn_alias_fl = fn_alias_fl,
+ .pfn_child_start_fl = fn_child_start_fl,
+ .pfn_child_exit_fl = fn_child_exit_fl,
+ .pfn_child_ready_fl = fn_child_ready_fl,
+ .pfn_thread_start_fl = NULL,
+ .pfn_thread_exit_fl = NULL,
+ .pfn_exec_fl = fn_exec_fl,
+ .pfn_exec_result_fl = fn_exec_result_fl,
+ .pfn_param_fl = fn_param_fl,
+ .pfn_repo_fl = fn_repo_fl,
+ .pfn_region_enter_printf_va_fl = NULL,
+ .pfn_region_leave_printf_va_fl = NULL,
+ .pfn_data_fl = NULL,
+ .pfn_data_json_fl = NULL,
+ .pfn_printf_va_fl = fn_printf_va_fl,
};
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
-static struct tr2_dst tr2dst_perf = { TR2_SYSENV_PERF, 0, 0, 0, 0 };
+static struct tr2_dst tr2dst_perf = {
+ .sysenv_var = TR2_SYSENV_PERF,
+};
/*
* Use TR2_SYSENV_PERF_BRIEF to omit the "<time> <file>:<line>"
}
struct tr2_tgt tr2_tgt_perf = {
- &tr2dst_perf,
-
- fn_init,
- fn_term,
-
- fn_version_fl,
- fn_start_fl,
- fn_exit_fl,
- fn_signal,
- fn_atexit,
- fn_error_va_fl,
- fn_command_path_fl,
- fn_command_ancestry_fl,
- fn_command_name_fl,
- fn_command_mode_fl,
- fn_alias_fl,
- fn_child_start_fl,
- fn_child_exit_fl,
- fn_child_ready_fl,
- fn_thread_start_fl,
- fn_thread_exit_fl,
- fn_exec_fl,
- fn_exec_result_fl,
- fn_param_fl,
- fn_repo_fl,
- fn_region_enter_printf_va_fl,
- fn_region_leave_printf_va_fl,
- fn_data_fl,
- fn_data_json_fl,
- fn_printf_va_fl,
+ .pdst = &tr2dst_perf,
+
+ .pfn_init = fn_init,
+ .pfn_term = fn_term,
+
+ .pfn_version_fl = fn_version_fl,
+ .pfn_start_fl = fn_start_fl,
+ .pfn_exit_fl = fn_exit_fl,
+ .pfn_signal = fn_signal,
+ .pfn_atexit = fn_atexit,
+ .pfn_error_va_fl = fn_error_va_fl,
+ .pfn_command_path_fl = fn_command_path_fl,
+ .pfn_command_ancestry_fl = fn_command_ancestry_fl,
+ .pfn_command_name_fl = fn_command_name_fl,
+ .pfn_command_mode_fl = fn_command_mode_fl,
+ .pfn_alias_fl = fn_alias_fl,
+ .pfn_child_start_fl = fn_child_start_fl,
+ .pfn_child_exit_fl = fn_child_exit_fl,
+ .pfn_child_ready_fl = fn_child_ready_fl,
+ .pfn_thread_start_fl = fn_thread_start_fl,
+ .pfn_thread_exit_fl = fn_thread_exit_fl,
+ .pfn_exec_fl = fn_exec_fl,
+ .pfn_exec_result_fl = fn_exec_result_fl,
+ .pfn_param_fl = fn_param_fl,
+ .pfn_repo_fl = fn_repo_fl,
+ .pfn_region_enter_printf_va_fl = fn_region_enter_printf_va_fl,
+ .pfn_region_leave_printf_va_fl = fn_region_leave_printf_va_fl,
+ .pfn_data_fl = fn_data_fl,
+ .pfn_data_json_fl = fn_data_json_fl,
+ .pfn_printf_va_fl = fn_printf_va_fl,
};
unsigned get_refs_from_bundle_called : 1;
};
-static struct ref *get_refs_from_bundle(struct transport *transport,
- int for_push,
- struct transport_ls_refs_options *transport_options)
+static void get_refs_from_bundle_inner(struct transport *transport)
{
struct bundle_transport_data *data = transport->data;
- struct ref *result = NULL;
- int i;
-
- if (for_push)
- return NULL;
data->get_refs_from_bundle_called = 1;
die(_("could not read bundle '%s'"), transport->url);
transport->hash_algo = data->header.hash_algo;
+}
+
+static struct ref *get_refs_from_bundle(struct transport *transport,
+ int for_push,
+ struct transport_ls_refs_options *transport_options)
+{
+ struct bundle_transport_data *data = transport->data;
+ struct ref *result = NULL;
+ int i;
+
+ if (for_push)
+ return NULL;
+
+ get_refs_from_bundle_inner(transport);
for (i = 0; i < data->header.references.nr; i++) {
struct string_list_item *e = data->header.references.items + i;
strvec_push(&extra_index_pack_args, "-v");
if (!data->get_refs_from_bundle_called)
- get_refs_from_bundle(transport, 0, NULL);
+ get_refs_from_bundle_inner(transport);
ret = unbundle(the_repository, &data->header, data->fd,
&extra_index_pack_args);
transport->hash_algo = data->header.hash_algo;
&transport_options);
trace2_region_leave("transport_push", "get_refs_list", r);
- strvec_clear(&transport_options.ref_prefixes);
+ transport_ls_refs_options_release(&transport_options);
if (flags & TRANSPORT_PUSH_ALL)
match_flags |= MATCH_REFS_ALL;
return transport->remote_refs;
}
+void transport_ls_refs_options_release(struct transport_ls_refs_options *opts)
+{
+ strvec_clear(&opts->ref_prefixes);
+ free((char *)opts->unborn_head_target);
+}
+
int transport_fetch_refs(struct transport *transport, struct ref *refs)
{
int rc;
/*
* If unborn_head_target is not NULL, and the remote reports HEAD as
* pointing to an unborn branch, transport_get_remote_refs() stores the
- * unborn branch in unborn_head_target. It should be freed by the
- * caller.
+ * unborn branch in unborn_head_target.
*/
- char *unborn_head_target;
+ const char *unborn_head_target;
};
#define TRANSPORT_LS_REFS_OPTIONS_INIT { \
.ref_prefixes = STRVEC_INIT, \
}
+/**
+ * Release the "struct transport_ls_refs_options".
+ */
+void transport_ls_refs_options_release(struct transport_ls_refs_options *opts);
+
/*
* Retrieve refs from a remote.
*/
int verify_uptodate(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
- if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
+ if (!o->skip_sparse_checkout &&
+ (ce->ce_flags & CE_SKIP_WORKTREE) &&
+ (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
}
const char *arg;
if (skip_prefix(line, "want ", &arg)) {
struct object_id oid;
+ struct commit *commit;
struct object *o;
if (get_oid_hex(arg, &oid))
die("git upload-pack: protocol error, "
"expected to get oid, not '%s'", line);
- o = parse_object(the_repository, &oid);
+ commit = lookup_commit_in_graph(the_repository, &oid);
+ if (commit)
+ o = &commit->object;
+ else
+ o = parse_object(the_repository, &oid);
+
if (!o) {
packet_writer_error(writer,
"upload-pack: not our ref %s",
if (skip_prefix(line, "want-ref ", &refname_nons)) {
struct object_id oid;
struct string_list_item *item;
- struct object *o;
+ struct object *o = NULL;
struct strbuf refname = STRBUF_INIT;
strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
item = string_list_append(wanted_refs, refname_nons);
item->util = oiddup(&oid);
- o = parse_object_or_die(&oid, refname_nons);
+ if (!starts_with(refname_nons, "refs/tags/")) {
+ struct commit *commit = lookup_commit_in_graph(the_repository, &oid);
+ if (commit)
+ o = &commit->object;
+ }
+
+ if (!o)
+ o = parse_object_or_die(&oid, refname_nons);
+
if (!(o->flags & WANTED)) {
o->flags |= WANTED;
add_object_array(o, NULL, want_obj);
strbuf_release(&synthkey);
return retval;
}
+
+void urlmatch_config_release(struct urlmatch_config *config)
+{
+ string_list_clear(&config->vars, 1);
+}
}
int urlmatch_config_entry(const char *var, const char *value, void *cb);
+void urlmatch_config_release(struct urlmatch_config *config);
#endif /* URL_MATCH_H */
va_copy(params_copy, params);
/* truncation via snprintf is OK here */
- if (file)
- snprintf(prefix, sizeof(prefix), "BUG: %s:%d: ", file, line);
- else
- snprintf(prefix, sizeof(prefix), "BUG: ");
+ snprintf(prefix, sizeof(prefix), "BUG: %s:%d: ", file, line);
vreportf(prefix, fmt, params);
abort();
}
-#ifdef HAVE_VARIADIC_MACROS
NORETURN void BUG_fl(const char *file, int line, const char *fmt, ...)
{
va_list ap;
BUG_vfl(file, line, fmt, ap);
va_end(ap);
}
-#else
-NORETURN void BUG(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- BUG_vfl(NULL, 0, fmt, ap);
- va_end(ap);
-}
-#endif
#ifdef SUPPRESS_ANNOTATED_LEAKS
void unleak_memory(const void *ptr, size_t len)
static int ndrivers;
static int drivers_alloc;
-#define PATTERNS(name, pattern, word_regex) \
- { name, NULL, -1, { pattern, REG_EXTENDED }, \
- word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
-#define IPATTERN(name, pattern, word_regex) \
- { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \
- word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
+#define PATTERNS(lang, rx, wrx) { \
+ .name = lang, \
+ .binary = -1, \
+ .funcname = { \
+ .pattern = rx, \
+ .cflags = REG_EXTENDED, \
+ }, \
+ .word_regex = wrx "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+", \
+}
+#define IPATTERN(lang, rx, wrx) { \
+ .name = lang, \
+ .binary = -1, \
+ .funcname = { \
+ .pattern = rx, \
+ .cflags = REG_EXTENDED | REG_ICASE, \
+ }, \
+ .word_regex = wrx "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+", \
+}
/*
* Built-in drivers for various languages, sorted by their names
#undef IPATTERN
static struct userdiff_driver driver_true = {
- "diff=true",
- NULL,
- 0,
- { NULL, 0 }
+ .name = "diff=true",
+ .binary = 0,
};
static struct userdiff_driver driver_false = {
- "!diff",
- NULL,
- 1,
- { NULL, 0 }
+ .name = "!diff",
+ .binary = 1,
};
struct find_by_namelen_data {
#include "worktree.h"
#include "dir.h"
#include "wt-status.h"
+#include "config.h"
void free_worktrees(struct worktree **worktrees)
{
{
int flags;
const char *target;
- int ignore_errno;
target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
"HEAD",
0,
- &wt->head_oid, &flags,
- &ignore_errno);
+ &wt->head_oid, &flags);
if (!target)
return;
const char *symref_target;
struct ref_store *refs;
int flags;
- int ignore_errno;
if (wt->is_bare)
continue;
refs = get_worktree_ref_store(wt);
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
- NULL, &flags,
- &ignore_errno);
+ NULL, &flags);
if ((flags & REF_ISSYMREF) &&
symref_target && !strcmp(symref_target, target)) {
existing = wt;
struct worktree *wt = *p;
struct object_id oid;
int flag;
- int ignore_errno;
if (wt->is_current)
continue;
if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
refname.buf,
RESOLVE_REF_READING,
- &oid, &flag, &ignore_errno))
+ &oid, &flag))
ret = fn(refname.buf, &oid, flag, cb_data);
if (ret)
break;
*wtpath = path;
return 0;
}
+
+static int move_config_setting(const char *key, const char *value,
+ const char *from_file, const char *to_file)
+{
+ if (git_config_set_in_file_gently(to_file, key, value))
+ return error(_("unable to set %s in '%s'"), key, to_file);
+ if (git_config_set_in_file_gently(from_file, key, NULL))
+ return error(_("unable to unset %s in '%s'"), key, from_file);
+ return 0;
+}
+
+int init_worktree_config(struct repository *r)
+{
+ int res = 0;
+ int bare = 0;
+ struct config_set cs = { { 0 } };
+ const char *core_worktree;
+ char *common_config_file;
+ char *main_worktree_file;
+
+ /*
+ * If the extension is already enabled, then we can skip the
+ * upgrade process.
+ */
+ if (repository_format_worktree_config)
+ return 0;
+ if ((res = git_config_set_gently("extensions.worktreeConfig", "true")))
+ return error(_("failed to set extensions.worktreeConfig setting"));
+
+ common_config_file = xstrfmt("%s/config", r->commondir);
+ main_worktree_file = xstrfmt("%s/config.worktree", r->commondir);
+
+ git_configset_init(&cs);
+ git_configset_add_file(&cs, common_config_file);
+
+ /*
+ * If core.bare is true in the common config file, then we need to
+ * move it to the main worktree's config file or it will break all
+ * worktrees. If it is false, then leave it in place because it
+ * _could_ be negating a global core.bare=true.
+ */
+ if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
+ if ((res = move_config_setting("core.bare", "true",
+ common_config_file,
+ main_worktree_file)))
+ goto cleanup;
+ }
+ /*
+ * If core.worktree is set, then the main worktree is located
+ * somewhere different than the parent of the common Git dir.
+ * Relocate that value to avoid breaking all worktrees with this
+ * upgrade to worktree config.
+ */
+ if (!git_configset_get_value(&cs, "core.worktree", &core_worktree)) {
+ if ((res = move_config_setting("core.worktree", core_worktree,
+ common_config_file,
+ main_worktree_file)))
+ goto cleanup;
+ }
+
+ /*
+ * Ensure that we use worktree config for the remaining lifetime
+ * of the current process.
+ */
+ repository_format_worktree_config = 1;
+
+cleanup:
+ git_configset_clear(&cs);
+ free(common_config_file);
+ free(main_worktree_file);
+ return res;
+}
struct strbuf *sb,
const char *refname);
+/**
+ * Enable worktree config for the first time. This will make the following
+ * adjustments:
+ *
+ * 1. Add extensions.worktreeConfig=true in the common config file.
+ *
+ * 2. If the common config file has a core.worktree value, then that value
+ * is moved to the main worktree's config.worktree file.
+ *
+ * 3. If the common config file has a core.bare enabled, then that value
+ * is moved to the main worktree's config.worktree file.
+ *
+ * If extensions.worktreeConfig is already true, then this method
+ * terminates early without any of the above steps. The existing config
+ * arrangement is assumed to be intentional.
+ *
+ * Returns 0 on success. Reports an error message and returns non-zero
+ * if any of these steps fail.
+ */
+int init_worktree_config(struct repository *r);
+
#endif
static const int num_letters = ARRAY_SIZE(letters) - 1;
static const char x_pattern[] = "XXXXXX";
static const int num_x = ARRAY_SIZE(x_pattern) - 1;
- uint64_t value;
- struct timeval tv;
char *filename_template;
size_t len;
int fd, count;
* Replace pattern's XXXXXX characters with randomness.
* Try TMP_MAX different filenames.
*/
- gettimeofday(&tv, NULL);
- value = ((uint64_t)tv.tv_usec << 16) ^ tv.tv_sec ^ getpid();
filename_template = &pattern[len - num_x - suffix_len];
for (count = 0; count < TMP_MAX; ++count) {
- uint64_t v = value;
int i;
+ uint64_t v;
+ if (csprng_bytes(&v, sizeof(v)) < 0)
+ return error_errno("unable to get random bytes for temporary file");
+
/* Fill in the random bits. */
for (i = 0; i < num_x; i++) {
filename_template[i] = letters[v % num_letters];
*/
if (errno != EEXIST)
break;
- /*
- * This is a random value. It is only necessary that
- * the next TMP_MAX values generated by adding 7777 to
- * VALUE are different with (module 2^32).
- */
- value += 7777;
}
/* We return the null string if we can't find a unique file name. */
pattern[0] = '\0';
return open(path, flags);
#endif
}
+
+int csprng_bytes(void *buf, size_t len)
+{
+#if defined(HAVE_ARC4RANDOM) || defined(HAVE_ARC4RANDOM_LIBBSD)
+ /* This function never returns an error. */
+ arc4random_buf(buf, len);
+ return 0;
+#elif defined(HAVE_GETRANDOM)
+ ssize_t res;
+ char *p = buf;
+ while (len) {
+ res = getrandom(p, len, 0);
+ if (res < 0)
+ return -1;
+ len -= res;
+ p += res;
+ }
+ return 0;
+#elif defined(HAVE_GETENTROPY)
+ int res;
+ char *p = buf;
+ while (len) {
+ /* getentropy has a maximum size of 256 bytes. */
+ size_t chunk = len < 256 ? len : 256;
+ res = getentropy(p, chunk);
+ if (res < 0)
+ return -1;
+ len -= chunk;
+ p += chunk;
+ }
+ return 0;
+#elif defined(HAVE_RTLGENRANDOM)
+ if (!RtlGenRandom(buf, len))
+ return -1;
+ return 0;
+#elif defined(HAVE_OPENSSL_CSPRNG)
+ int res = RAND_bytes(buf, len);
+ if (res == 1)
+ return 0;
+ if (res == -1)
+ errno = ENOTSUP;
+ else
+ errno = EIO;
+ return -1;
+#else
+ ssize_t res;
+ char *p = buf;
+ int fd, err;
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0)
+ return -1;
+ while (len) {
+ res = xread(fd, p, len);
+ if (res < 0) {
+ err = errno;
+ close(fd);
+ errno = err;
+ return -1;
+ }
+ len -= res;
+ p += res;
+ }
+ close(fd);
+ return 0;
+#endif
+}
long *kvd, *kvdf, *kvdb;
xdalgoenv_t xenv;
diffdata_t dd1, dd2;
+ int res;
- if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF)
- return xdl_do_patience_diff(mf1, mf2, xpp, xe);
-
- if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
- return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+ if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0)
+ return -1;
- if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF) {
+ res = xdl_do_patience_diff(mf1, mf2, xpp, xe);
+ goto out;
+ }
- return -1;
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF) {
+ res = xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+ goto out;
}
/*
dd2.rchg = xe->xdf2.rchg;
dd2.rindex = xe->xdf2.rindex;
- if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
- kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) {
-
- xdl_free(kvd);
- xdl_free_env(xe);
- return -1;
- }
-
+ res = xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
+ kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0,
+ &xenv);
xdl_free(kvd);
+ out:
+ if (res < 0)
+ xdl_free_env(xe);
- return 0;
+ return res;
}
int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2,
xpparam_t const *xpp, xdfenv_t *env)
{
- if (xdl_prepare_env(file1, file2, xpp, env) < 0)
- return -1;
-
return histogram_diff(xpp, env,
env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1,
env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1);
int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
xmparam_t const *xmp, mmbuffer_t *result)
{
- xdchange_t *xscr1, *xscr2;
+ xdchange_t *xscr1 = NULL, *xscr2 = NULL;
xdfenv_t xe1, xe2;
- int status;
+ int status = -1;
xpparam_t const *xpp = &xmp->xpp;
result->ptr = NULL;
result->size = 0;
- if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) {
+ if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0)
return -1;
- }
- if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) {
- xdl_free_env(&xe1);
- return -1;
- }
+
+ if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0)
+ goto free_xe1; /* avoid double free of xe2 */
+
if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 ||
xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 ||
- xdl_build_script(&xe1, &xscr1) < 0) {
- xdl_free_env(&xe1);
- return -1;
- }
+ xdl_build_script(&xe1, &xscr1) < 0)
+ goto out;
+
if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 ||
xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 ||
- xdl_build_script(&xe2, &xscr2) < 0) {
- xdl_free_script(xscr1);
- xdl_free_env(&xe1);
- xdl_free_env(&xe2);
- return -1;
- }
- status = 0;
+ xdl_build_script(&xe2, &xscr2) < 0)
+ goto out;
+
if (!xscr1) {
result->ptr = xdl_malloc(mf2->size);
+ if (!result->ptr)
+ goto out;
+ status = 0;
memcpy(result->ptr, mf2->ptr, mf2->size);
result->size = mf2->size;
} else if (!xscr2) {
result->ptr = xdl_malloc(mf1->size);
+ if (!result->ptr)
+ goto out;
+ status = 0;
memcpy(result->ptr, mf1->ptr, mf1->size);
result->size = mf1->size;
} else {
&xe2, xscr2,
xmp, result);
}
+ out:
xdl_free_script(xscr1);
xdl_free_script(xscr2);
- xdl_free_env(&xe1);
xdl_free_env(&xe2);
+ free_xe1:
+ xdl_free_env(&xe1);
return status;
}
* item per sequence length: the sequence with the smallest last
* element (in terms of line2).
*/
-static struct entry *find_longest_common_sequence(struct hashmap *map)
+static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
{
struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
int longest = 0, i;
*/
int anchor_i = -1;
+ if (!sequence)
+ return -1;
+
for (entry = map->first; entry; entry = entry->next) {
if (!entry->line2 || entry->line2 == NON_UNIQUE)
continue;
/* No common unique lines were found */
if (!longest) {
+ *res = NULL;
xdl_free(sequence);
- return NULL;
+ return 0;
}
/* Iterate starting at the last element, adjusting the "next" members */
entry->previous->next = entry;
entry = entry->previous;
}
+ *res = entry;
xdl_free(sequence);
- return entry;
+ return 0;
}
static int match(struct hashmap *map, int line1, int line2)
return 0;
}
- first = find_longest_common_sequence(&map);
+ result = find_longest_common_sequence(&map, &first);
+ if (result)
+ goto out;
if (first)
result = walk_common_sequence(&map, first,
line1, count1, line2, count2);
else
result = fall_back_to_classic_diff(&map,
line1, count1, line2, count2);
-
+ out:
xdl_free(map.entries);
return result;
}
int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
xpparam_t const *xpp, xdfenv_t *env)
{
- if (xdl_prepare_env(file1, file2, xpp, env) < 0)
- return -1;
-
- /* environment is cleaned up in xdl_diff() */
return patience_diff(file1, file2, xpp, env,
1, env->xdf1.nrec, 1, env->xdf2.nrec);
}